1083 lines
56 KiB
HTML
1083 lines
56 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=1280">
|
||
<title>Fonrey 编辑客源 · 静态原型</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
primary: {
|
||
50: '#F0FDFA',
|
||
100: '#CCFBF1',
|
||
200: '#99F6E4',
|
||
500: '#14B8A6',
|
||
600: '#0F766E',
|
||
700: '#115E59',
|
||
800: '#134E4A'
|
||
},
|
||
neutral: {
|
||
50: '#F8FAFC',
|
||
100: '#F1F5F9',
|
||
200: '#E2E8F0',
|
||
300: '#CBD5E1',
|
||
400: '#94A3B8',
|
||
500: '#64748B',
|
||
600: '#475569',
|
||
700: '#334155',
|
||
800: '#1E293B',
|
||
900: '#0F172A'
|
||
},
|
||
success: { 50: '#F0FDF4', 600: '#16A34A' },
|
||
warning: { 50: '#FFFBEB', 600: '#D97706' },
|
||
danger: { 50: '#FEF2F2', 600: '#DC2626' },
|
||
info: { 50: '#EFF6FF', 600: '#2563EB' }
|
||
},
|
||
boxShadow: {
|
||
xs: '0 1px 2px rgba(15,23,42,0.04)'
|
||
},
|
||
fontFamily: {
|
||
sans: ['Inter', 'PingFang SC', 'Microsoft YaHei', 'sans-serif']
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
html { scroll-behavior: smooth; }
|
||
[x-cloak] { display: none !important; }
|
||
.tabular-nums { font-variant-numeric: tabular-nums; }
|
||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
::-webkit-scrollbar-thumb { background: #CBD5E1; border-radius: 4px; }
|
||
::-webkit-scrollbar-thumb:hover { background: #94A3B8; }
|
||
</style>
|
||
</head>
|
||
<body class="bg-neutral-50 text-sm text-neutral-700 antialiased" x-data="editClientPage()">
|
||
|
||
<!-- ===== 顶部导航栏 ===== -->
|
||
<header class="fixed top-0 left-0 right-0 h-14 z-20 bg-primary-800 flex items-center justify-between">
|
||
<div class="flex items-center gap-2 px-4 w-60 shrink-0">
|
||
<div class="w-7 h-7 rounded-md bg-primary-500 flex items-center justify-center text-white text-sm font-semibold">F</div>
|
||
<span class="text-base font-semibold text-white">Fonrey</span>
|
||
</div>
|
||
<nav class="flex items-center gap-1 flex-1 px-2" aria-label="主导航">
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">工作台</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">房源</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md bg-primary-600 text-white font-medium cursor-pointer">客源</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">营销</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">交易</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">数据</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">人事</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white cursor-pointer">系统</a>
|
||
</nav>
|
||
<div class="flex items-center gap-1 px-4 shrink-0">
|
||
<button class="p-1.5 text-primary-200 hover:bg-primary-700 hover:text-white rounded-md" aria-label="消息">
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022 23.848 23.848 0 0 0 5.454 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"/></svg>
|
||
</button>
|
||
<div class="flex items-center gap-2 pl-3 ml-1 border-l border-primary-700">
|
||
<div class="w-8 h-8 rounded-full bg-primary-600 text-white flex items-center justify-center text-sm font-semibold">魏</div>
|
||
<span class="text-sm font-medium text-primary-100">魏深</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ===== 左侧侧边栏 ===== -->
|
||
<aside class="fixed left-0 top-14 h-[calc(100vh-56px)] w-60 z-20 border-r border-neutral-200 bg-white">
|
||
<nav class="p-3 space-y-0.5">
|
||
<div class="px-2 pt-2 pb-1 text-xs font-medium text-neutral-500 uppercase tracking-wide">客源管理</div>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md bg-primary-50 text-primary-700 font-medium cursor-pointer">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/></svg>
|
||
私客列表
|
||
</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100 cursor-pointer">公客池</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100 cursor-pointer">成交客</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100 cursor-pointer">已删客源</a>
|
||
</nav>
|
||
</aside>
|
||
|
||
<!-- ===== 主内容区 ===== -->
|
||
<main class="ml-60 pt-14 min-h-screen bg-neutral-50">
|
||
|
||
<!-- 面包屑 + 标题区 -->
|
||
<div class="px-6 pt-6 pb-4">
|
||
<nav class="flex items-center gap-1 text-xs text-neutral-500 mb-2" aria-label="面包屑">
|
||
<a class="hover:text-neutral-700 cursor-pointer">客源</a>
|
||
<span>/</span>
|
||
<a class="hover:text-neutral-700 cursor-pointer">客源管理</a>
|
||
<span>/</span>
|
||
<span class="text-neutral-700">编辑客源</span>
|
||
</nav>
|
||
<h1 class="text-xl font-semibold text-neutral-800">编辑客源</h1>
|
||
</div>
|
||
|
||
<!-- Tab 导航栏(粘性,紧贴顶部 header 下方) -->
|
||
<div class="sticky top-14 z-10 bg-white border-b border-neutral-200">
|
||
<nav class="flex gap-0 px-6">
|
||
<button
|
||
class="px-4 py-3 text-sm font-medium border-b-2 transition-colors focus:outline-none"
|
||
:class="activeTab === 'contacts' ? 'border-orange-500 text-orange-500' : 'border-transparent text-neutral-500 hover:text-neutral-700'"
|
||
@click="setTab('contacts')"
|
||
>联系人</button>
|
||
<button
|
||
class="px-4 py-3 text-sm font-medium border-b-2 transition-colors focus:outline-none"
|
||
:class="activeTab === 'basic' ? 'border-orange-500 text-orange-500' : 'border-transparent text-neutral-500 hover:text-neutral-700'"
|
||
@click="setTab('basic')"
|
||
>基础信息</button>
|
||
<button
|
||
class="px-4 py-3 text-sm font-medium border-b-2 transition-colors focus:outline-none"
|
||
:class="activeTab === 'requirement' ? 'border-orange-500 text-orange-500' : 'border-transparent text-neutral-500 hover:text-neutral-700'"
|
||
@click="setTab('requirement')"
|
||
>二手</button>
|
||
</nav>
|
||
</div>
|
||
|
||
<!-- 表单主体 -->
|
||
<form id="form-edit-client" @submit.prevent="submitForm" class="px-6">
|
||
<div class="mx-auto max-w-5xl py-5 space-y-4">
|
||
|
||
<!-- ===== 联系人 Section ===== -->
|
||
<section id="section-contacts" class="bg-white rounded-lg border border-neutral-200 p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h2 class="text-base font-semibold text-neutral-800">联系人</h2>
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-sm text-neutral-400">查看后可编辑号码</span>
|
||
<button
|
||
type="button"
|
||
x-show="!phoneRevealed"
|
||
@click="revealPhone"
|
||
class="text-sm text-info-600 hover:underline focus:outline-none"
|
||
:class="revealLoading ? 'opacity-60 cursor-wait' : ''"
|
||
:disabled="revealLoading"
|
||
>
|
||
<span x-show="revealLoading" class="inline-flex items-center gap-1">
|
||
<svg class="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-opacity=".25" stroke-width="4"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="4"></path></svg>
|
||
查看中...
|
||
</span>
|
||
<span x-show="!revealLoading">查看号码</span>
|
||
</button>
|
||
<span x-show="phoneRevealed" class="text-sm text-neutral-400">已查看</span>
|
||
<button
|
||
type="button"
|
||
x-show="form.contacts.length < 5"
|
||
@click="addContact"
|
||
class="inline-flex items-center gap-1 text-sm font-medium border border-neutral-300 bg-white text-neutral-700 rounded-md px-3 py-1.5 hover:bg-neutral-50 hover:border-neutral-400"
|
||
>
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||
添加联系人
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<template x-for="(contact, idx) in form.contacts" :key="contact.id">
|
||
<div class="border border-neutral-200 rounded-lg p-5">
|
||
<!-- 联系人卡片标题行 -->
|
||
<div class="flex items-center justify-between mb-4">
|
||
<span class="text-sm font-medium text-neutral-700" x-text="'联系人 ' + (idx + 1)"></span>
|
||
<button
|
||
type="button"
|
||
x-show="idx > 0"
|
||
@click="removeContact(idx)"
|
||
class="text-sm text-danger-600 hover:underline focus:outline-none"
|
||
>删除</button>
|
||
</div>
|
||
|
||
<!-- 字段网格:3列 -->
|
||
<div class="grid grid-cols-3 gap-4">
|
||
|
||
<!-- 姓名 -->
|
||
<div class="space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">
|
||
姓名 <span class="text-danger-600">*</span>
|
||
</label>
|
||
<input
|
||
type="text"
|
||
x-model.trim="contact.name"
|
||
:data-field-key="fk('contacts', idx, 'name')"
|
||
placeholder="请输入"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border placeholder:text-neutral-400 focus:outline-none focus:ring-2"
|
||
:class="inputCls(fk('contacts', idx, 'name'))"
|
||
>
|
||
<p class="text-xs text-danger-600" x-show="errors[fk('contacts', idx, 'name')]" x-text="errors[fk('contacts', idx, 'name')]"></p>
|
||
</div>
|
||
|
||
<!-- 称呼 -->
|
||
<div class="space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">
|
||
称呼 <span class="text-danger-600">*</span>
|
||
</label>
|
||
<div class="flex items-center gap-6 rounded-md border px-3 py-2 min-h-[42px]"
|
||
:class="groupCls(fk('contacts', idx, 'gender'))">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="contact.gender" value="male" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">先生</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="contact.gender" value="female" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">女士</span>
|
||
</label>
|
||
</div>
|
||
<p class="text-xs text-danger-600" x-show="errors[fk('contacts', idx, 'gender')]" x-text="errors[fk('contacts', idx, 'gender')]"></p>
|
||
</div>
|
||
|
||
<!-- 电话1(打码/已解码) -->
|
||
<div class="space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">
|
||
电话1 <span class="text-danger-600">*</span>
|
||
</label>
|
||
|
||
<!-- 打码态 -->
|
||
<template x-if="!phoneRevealed && idx === 0">
|
||
<div class="flex items-center gap-2">
|
||
<div class="flex-1 flex items-center px-3 py-2 rounded-md border border-neutral-200 bg-neutral-50 min-h-[42px]">
|
||
<span class="text-sm text-neutral-500 select-none" x-text="contact.phoneMasked"></span>
|
||
</div>
|
||
<button type="button"
|
||
@click="revealPhone"
|
||
class="shrink-0 text-sm text-info-600 hover:underline focus:outline-none whitespace-nowrap">
|
||
标记无效
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 可编辑态(已解码,或非第一联系人) -->
|
||
<template x-if="phoneRevealed || idx > 0">
|
||
<div>
|
||
<div
|
||
class="flex rounded-md border overflow-hidden focus-within:ring-2"
|
||
:class="phoneCls(fk('contacts', idx, 'phone'))"
|
||
>
|
||
<select x-model="contact.phoneCountryCode" class="w-24 px-2 py-2 text-sm bg-neutral-50 border-r border-neutral-200 text-neutral-700 focus:outline-none">
|
||
<template x-for="code in countryCodes" :key="'cc-' + code">
|
||
<option :value="code" x-text="code"></option>
|
||
</template>
|
||
</select>
|
||
<input
|
||
type="tel"
|
||
x-model.trim="contact.phone"
|
||
placeholder="输入手机号"
|
||
class="flex-1 px-3 py-2 text-sm bg-white focus:outline-none"
|
||
>
|
||
</div>
|
||
<div class="flex items-center justify-between mt-1">
|
||
<p class="text-xs text-danger-600" x-show="errors[fk('contacts', idx, 'phone')]" x-text="errors[fk('contacts', idx, 'phone')]"></p>
|
||
<button type="button" class="ml-auto text-xs text-info-600 hover:underline focus:outline-none">标记无效</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 微信 -->
|
||
<div class="space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">微信</label>
|
||
<input type="text" x-model.trim="contact.wechat" placeholder="请输入"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
</div>
|
||
|
||
<!-- 电话2 -->
|
||
<div class="space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">电话2</label>
|
||
<div class="flex rounded-md border border-neutral-300 overflow-hidden focus-within:border-primary-600 focus-within:ring-2 focus-within:ring-primary-600/20">
|
||
<select x-model="contact.phone2CountryCode" class="w-24 px-2 py-2 text-sm bg-neutral-50 border-r border-neutral-200 text-neutral-700 focus:outline-none">
|
||
<template x-for="code in countryCodes" :key="'cc2-' + code">
|
||
<option :value="code" x-text="code"></option>
|
||
</template>
|
||
</select>
|
||
<input type="tel" x-model.trim="contact.phone2" placeholder="输入手机号"
|
||
class="flex-1 px-3 py-2 text-sm bg-white focus:outline-none">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- QQ -->
|
||
<div class="space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">QQ</label>
|
||
<input type="text" x-model.trim="contact.qq" placeholder="请输入"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
</div>
|
||
|
||
<!-- 备注(跨两列) -->
|
||
<div class="col-span-3 space-y-1.5">
|
||
<label class="block text-sm font-medium text-neutral-700">备注</label>
|
||
<div x-data="{ cnt: contact.remarks ? contact.remarks.length : 0 }">
|
||
<textarea
|
||
x-model="contact.remarks"
|
||
@input="cnt = $event.target.value.length"
|
||
maxlength="200"
|
||
rows="2"
|
||
placeholder="请输入备注信息"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 resize-none focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20"
|
||
></textarea>
|
||
<p class="text-xs text-neutral-400 text-right mt-0.5" x-text="cnt + ' / 200'"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== 基础信息 Section ===== -->
|
||
<section id="section-basic" class="bg-white rounded-lg border border-neutral-200 p-6">
|
||
<h2 class="text-base font-semibold text-neutral-800 mb-5">基础信息</h2>
|
||
|
||
<div class="space-y-5">
|
||
|
||
<!-- 需求类型(复选框) -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
需求类型 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border px-3 py-2 min-h-[42px]"
|
||
:class="groupCls('requirement_types')">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.requirementTypes" value="second_hand"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">二手</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.requirementTypes" value="new_house"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">新房</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.requirementTypes" value="rental"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">租房</span>
|
||
</label>
|
||
</div>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.requirement_types" x-text="errors.requirement_types"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 用途 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
用途 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border px-3 py-2 min-h-[42px]"
|
||
:class="groupCls('property_usage')">
|
||
<template x-for="item in usageOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.propertyUsage" :value="item.value" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.property_usage" x-text="errors.property_usage"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 等级 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
等级 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border px-3 py-2 min-h-[42px]"
|
||
:class="groupCls('grade')">
|
||
<template x-for="item in gradeOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.grade" :value="item.value" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.grade" x-text="errors.grade"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 来源 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
来源 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<select
|
||
data-field-key="source"
|
||
x-model="form.source"
|
||
class="block w-full max-w-xs px-3 py-2 text-sm rounded-md border bg-white focus:outline-none focus:ring-2"
|
||
:class="inputCls('source')"
|
||
>
|
||
<option value="">请选择</option>
|
||
<option value="store_visit">线下丨门店接待</option>
|
||
<option value="old_client_referral">老客户转介绍</option>
|
||
<option value="online_form">线上丨留资表单</option>
|
||
<option value="community_push">社群活动</option>
|
||
</select>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.source" x-text="errors.source"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 购房目的(多选) -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">购房目的</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<template x-for="item in buyingPurposeOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.buyingPurpose" :value="item.value"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 付款方式 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">付款方式</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<template x-for="item in paymentOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.paymentMethod" :value="item.value" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 名下房产 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">名下房产</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.propertiesOwned" value="none" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">无</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.propertiesOwned" value="local_none" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">本地无房外地有房</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.propertiesOwned" value="local_has" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">本地有房</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 贷款记录 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">贷款记录</div>
|
||
<div class="flex items-center gap-6 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.hasLoanRecord" value="true" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">有</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="radio" x-model="form.hasLoanRecord" value="false" class="w-4 h-4 accent-primary-600">
|
||
<span class="text-sm text-neutral-700">无</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 证件类型 + 证件号码 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">证件类型</div>
|
||
<div class="flex gap-3 flex-1">
|
||
<select x-model="form.idType"
|
||
class="w-36 px-3 py-2 text-sm rounded-md border border-neutral-300 bg-white focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<option value="">请选择</option>
|
||
<option value="id_card">身份证</option>
|
||
<option value="passport">护照</option>
|
||
<option value="hk_macao">港澳通行证</option>
|
||
<option value="other">其他</option>
|
||
</select>
|
||
<div class="flex-1">
|
||
<input type="text" x-model.trim="form.idNumber" placeholder="请输入证件号码"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 意向学校 -->
|
||
<div class="flex gap-0"
|
||
x-data="{ schools: form.schools, addSchool() { this.schools.push(''); form.schools = this.schools; }, removeSchool(i) { this.schools.splice(i, 1); form.schools = this.schools; } }">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">意向学校</div>
|
||
<div class="flex-1 space-y-2">
|
||
<template x-for="(school, si) in schools" :key="'school-' + si">
|
||
<div class="flex items-center gap-2 max-w-sm">
|
||
<input type="text"
|
||
x-model="schools[si]"
|
||
@input="form.schools[si] = $event.target.value"
|
||
placeholder="请输入学校名称"
|
||
class="flex-1 px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<button type="button" @click="removeSchool(si)" x-show="schools.length > 1"
|
||
class="text-sm text-danger-600 hover:underline focus:outline-none">删除</button>
|
||
</div>
|
||
</template>
|
||
<button type="button" @click="addSchool()"
|
||
class="flex items-center gap-1 text-sm text-primary-600 hover:text-primary-700 focus:outline-none">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||
添加学校
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 入学时间(月份选择器,使用原生 input[type=month]) -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">入学时间</div>
|
||
<div class="flex-1">
|
||
<input type="month" x-model="form.enrollmentDate"
|
||
class="px-3 py-2 text-sm rounded-md border border-neutral-300 bg-white focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<p class="text-xs text-neutral-400 mt-1">请选择年月</p>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== 二手需求 Section ===== -->
|
||
<section id="section-requirement" class="bg-white rounded-lg border border-neutral-200 p-6">
|
||
<h2 class="text-base font-semibold text-neutral-800 mb-5">二手</h2>
|
||
|
||
<div class="space-y-5">
|
||
|
||
<!-- 总价 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
总价 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<div class="flex items-center gap-2">
|
||
<input type="number" x-model="form.budgetMin" min="0" step="0.01" placeholder="最小值"
|
||
class="w-28 px-3 py-2 text-sm rounded-md border placeholder:text-neutral-400 focus:outline-none focus:ring-2"
|
||
:class="inputCls('budget')">
|
||
<span class="text-neutral-400">-</span>
|
||
<input type="number" x-model="form.budgetMax" min="0" step="0.01" placeholder="最大值"
|
||
class="w-28 px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<span class="text-sm text-neutral-500">万元</span>
|
||
</div>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.budget" x-text="errors.budget"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 面积 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
面积 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<div class="flex items-center gap-2">
|
||
<input type="number" x-model="form.areaMin" min="0" step="0.01" placeholder="最小值"
|
||
class="w-28 px-3 py-2 text-sm rounded-md border placeholder:text-neutral-400 focus:outline-none focus:ring-2"
|
||
:class="inputCls('area')">
|
||
<span class="text-neutral-400">-</span>
|
||
<input type="number" x-model="form.areaMax" min="0" step="0.01" placeholder="最大值"
|
||
class="w-28 px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<span class="text-sm text-neutral-500">m²</span>
|
||
</div>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.area" x-text="errors.area"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 居室 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">
|
||
居室 <span class="text-danger-600">*</span>
|
||
</div>
|
||
<div class="flex-1">
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border px-3 py-2 min-h-[42px]"
|
||
:class="groupCls('bedroom_counts')">
|
||
<template x-for="item in bedroomOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.bedroomCounts" :value="item.value"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
<p class="text-xs text-danger-600 mt-1" x-show="errors.bedroom_counts" x-text="errors.bedroom_counts"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 楼层 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">楼层</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<template x-for="item in floorOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.floorPreferences" :value="item.value"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 朝向 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">朝向</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.orientations" value="east" class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">东</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.orientations" value="south" class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">南</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.orientations" value="west" class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">西</span>
|
||
</label>
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.orientations" value="north" class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700">北</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 装修 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">装修</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<template x-for="item in decorationOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.decorations" :value="item.value"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 楼龄 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">楼龄</div>
|
||
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-md border border-neutral-300 px-3 py-2 min-h-[42px] flex-1">
|
||
<template x-for="item in buildingAgeOptions" :key="item.value">
|
||
<label class="flex items-center gap-1.5 cursor-pointer">
|
||
<input type="checkbox" x-model="form.buildingAgeRanges" :value="item.value"
|
||
class="w-4 h-4 rounded accent-primary-600">
|
||
<span class="text-sm text-neutral-700" x-text="item.label"></span>
|
||
</label>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 意向商圈(原生 select multiple) -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">意向商圈</div>
|
||
<div class="flex-1 max-w-xs">
|
||
<select multiple
|
||
x-model="form.intentBusinessAreaIds"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 bg-white focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20"
|
||
size="4">
|
||
<option value="ba-001">中心商圈</option>
|
||
<option value="ba-002">东城商圈</option>
|
||
<option value="ba-003">南海商圈</option>
|
||
<option value="ba-004">西区商圈</option>
|
||
<option value="ba-005">北部新城</option>
|
||
<option value="ba-006">科技园区</option>
|
||
</select>
|
||
<p class="text-xs text-neutral-400 mt-1">按住 Ctrl/Cmd 可多选</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 意向小区 -->
|
||
<div class="flex gap-0"
|
||
x-data="{ complexes: form.complexes, addComplex() { this.complexes.push(''); form.complexes = this.complexes; }, removeComplex(i) { this.complexes.splice(i, 1); form.complexes = this.complexes; } }">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">意向小区</div>
|
||
<div class="flex-1 space-y-2">
|
||
<template x-for="(complex, ci) in complexes" :key="'complex-' + ci">
|
||
<div class="flex items-center gap-2 max-w-sm">
|
||
<input type="text"
|
||
x-model="complexes[ci]"
|
||
@input="form.complexes[ci] = $event.target.value"
|
||
placeholder="请输入小区名称"
|
||
class="flex-1 px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<button type="button" @click="removeComplex(ci)" x-show="complexes.length > 1"
|
||
class="text-sm text-danger-600 hover:underline focus:outline-none">删除</button>
|
||
</div>
|
||
</template>
|
||
<button type="button" @click="addComplex()"
|
||
class="flex items-center gap-1 text-sm text-primary-600 hover:text-primary-700 focus:outline-none">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||
添加小区
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 交通 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">交通</div>
|
||
<div class="flex-1 max-w-lg" x-data="{ cnt: form.transportation ? form.transportation.length : 0 }">
|
||
<input type="text" x-model="form.transportation"
|
||
@input="cnt = $event.target.value.length; form.transportation = $event.target.value"
|
||
maxlength="50"
|
||
placeholder="请输入交通要求,如地铁沿线"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20">
|
||
<span class="text-xs text-neutral-400 text-right block mt-0.5" x-text="cnt + ' / 50'"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 备注 -->
|
||
<div class="flex gap-0">
|
||
<div class="w-28 shrink-0 pt-2 text-sm font-medium text-neutral-700">备注</div>
|
||
<div class="flex-1 max-w-lg" x-data="{ cnt: form.requirementNotes ? form.requirementNotes.length : 0 }">
|
||
<textarea x-model="form.requirementNotes"
|
||
@input="cnt = $event.target.value.length; form.requirementNotes = $event.target.value"
|
||
maxlength="200"
|
||
rows="3"
|
||
placeholder="请输入需求备注"
|
||
class="block w-full px-3 py-2 text-sm rounded-md border border-neutral-300 placeholder:text-neutral-400 resize-none focus:outline-none focus:border-primary-600 focus:ring-2 focus:ring-primary-600/20"></textarea>
|
||
<span class="text-xs text-neutral-400 text-right block mt-0.5" x-text="cnt + ' / 200'"></span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<div class="flex items-center gap-3 mt-2 pb-8">
|
||
<button
|
||
type="submit"
|
||
:disabled="submitting"
|
||
class="inline-flex items-center gap-1.5 px-6 py-2 text-sm font-medium bg-primary-600 text-white rounded-md hover:bg-primary-700 active:bg-primary-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-600/40 disabled:opacity-70 disabled:cursor-wait"
|
||
>
|
||
<svg x-show="submitting" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-opacity=".25" stroke-width="4"></circle>
|
||
<path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="4"></path>
|
||
</svg>
|
||
<span x-text="submitting ? '保存中...' : '保存'"></span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
@click="cancelForm"
|
||
:disabled="submitting"
|
||
class="inline-flex items-center px-4 py-2 text-sm font-medium bg-white border border-neutral-300 text-neutral-700 rounded-md hover:bg-neutral-50 hover:border-neutral-400 disabled:opacity-60"
|
||
>
|
||
取消
|
||
</button>
|
||
<p x-show="redirectHint" x-text="redirectHint" class="text-xs text-success-600"></p>
|
||
</div>
|
||
|
||
</div>
|
||
</form>
|
||
</main>
|
||
|
||
<!-- ===== Toast 提示 ===== -->
|
||
<div x-show="toast.show" x-transition.opacity class="fixed bottom-6 right-6 z-[70]">
|
||
<div class="w-80 bg-white rounded-lg shadow-lg border border-neutral-200 flex items-start gap-3 p-3">
|
||
<svg class="w-5 h-5 shrink-0 mt-0.5"
|
||
:class="toast.type === 'success' ? 'text-success-600' : 'text-danger-600'"
|
||
fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24">
|
||
<template x-if="toast.type === 'success'">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/>
|
||
</template>
|
||
<template x-if="toast.type !== 'success'">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/>
|
||
</template>
|
||
</svg>
|
||
<div class="flex-1 text-sm">
|
||
<p class="font-medium text-neutral-800" x-text="toast.message"></p>
|
||
<p class="text-xs text-neutral-500" x-text="toast.subText"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function editClientPage() {
|
||
return {
|
||
/* -------- 静态选项 -------- */
|
||
countryCodes: ['+86', '+852', '+853', '+886', '+1'],
|
||
usageOptions: [
|
||
{ value: 'residential', label: '住宅' },
|
||
{ value: 'villa', label: '别墅' },
|
||
{ value: 'commercial_residential',label: '商住' },
|
||
{ value: 'shop', label: '商铺' },
|
||
{ value: 'office', label: '写字楼' },
|
||
{ value: 'other', label: '其他' }
|
||
],
|
||
gradeOptions: [
|
||
{ value: 'A_urgent', label: 'A(急迫)' },
|
||
{ value: 'A', label: 'A' },
|
||
{ value: 'B', label: 'B(较强)' },
|
||
{ value: 'C', label: 'C(一般)' },
|
||
{ value: 'D', label: 'D(较弱)' },
|
||
{ value: 'E', label: 'E(暂不关注)' }
|
||
],
|
||
buyingPurposeOptions: [
|
||
{ value: 'rigid', label: '刚需' },
|
||
{ value: 'investment', label: '投资' },
|
||
{ value: 'school_district',label: '学区' },
|
||
{ value: 'upgrade', label: '改善' },
|
||
{ value: 'commercial', label: '商用' },
|
||
{ value: 'other', label: '其他' }
|
||
],
|
||
paymentOptions: [
|
||
{ value: 'full', label: '全额' },
|
||
{ value: 'mortgage', label: '商业贷款' },
|
||
{ value: 'mortgage_fund', label: '商业贷款+公积金' },
|
||
{ value: 'fund', label: '公积金' }
|
||
],
|
||
bedroomOptions: [
|
||
{ value: 1, label: '1居' },
|
||
{ value: 2, label: '2居' },
|
||
{ value: 3, label: '3居' },
|
||
{ value: 4, label: '4居' },
|
||
{ value: 5, label: '5居及以上' }
|
||
],
|
||
floorOptions: [
|
||
{ value: 'no_first', label: '不要一层' },
|
||
{ value: 'low', label: '低楼层' },
|
||
{ value: 'mid', label: '中楼层' },
|
||
{ value: 'high', label: '高楼层' },
|
||
{ value: 'no_top', label: '不要顶层' }
|
||
],
|
||
decorationOptions: [
|
||
{ value: 'rough', label: '毛坯' },
|
||
{ value: 'clear', label: '清水' },
|
||
{ value: 'simple', label: '简装' },
|
||
{ value: 'mid', label: '中装' },
|
||
{ value: 'fine', label: '精装' },
|
||
{ value: 'luxury', label: '豪装' }
|
||
],
|
||
buildingAgeOptions: [
|
||
{ value: 'within_5y', label: '5年以内' },
|
||
{ value: '5_10y', label: '5-10年' },
|
||
{ value: '10_15y', label: '10-15年' },
|
||
{ value: '15_20y', label: '15-20年' },
|
||
{ value: 'over_20y', label: '20年以上' }
|
||
],
|
||
|
||
/* -------- 交互状态 -------- */
|
||
activeTab: 'contacts',
|
||
phoneRevealed: false,
|
||
revealLoading: false,
|
||
submitting: false,
|
||
redirectHint: '',
|
||
errors: {},
|
||
toast: { show: false, message: '', subText: '', type: 'success' },
|
||
|
||
/* -------- 表单数据(含占位回显值) -------- */
|
||
form: {
|
||
contacts: [],
|
||
requirementTypes: ['second_hand'],
|
||
propertyUsage: 'residential',
|
||
grade: 'C',
|
||
source: 'store_visit',
|
||
buyingPurpose: ['rigid', 'school_district'],
|
||
paymentMethod: 'mortgage',
|
||
propertiesOwned: 'none',
|
||
hasLoanRecord: 'false',
|
||
idType: 'id_card',
|
||
idNumber: '',
|
||
schools: ['实验小学'],
|
||
enrollmentDate: '2027-09',
|
||
budgetMin: 150,
|
||
budgetMax: 280,
|
||
areaMin: 80,
|
||
areaMax: 130,
|
||
bedroomCounts: [2, 3],
|
||
floorPreferences: ['mid', 'high'],
|
||
orientations: ['south'],
|
||
decorations: ['fine'],
|
||
buildingAgeRanges: ['within_5y', '5_10y'],
|
||
intentBusinessAreaIds: ['ba-001', 'ba-002'],
|
||
complexes: ['碧桂园翡翠湾', '万科金色家园'],
|
||
transportation: '需要地铁沿线,步行15分钟内',
|
||
requirementNotes: '楼层中高层为主,南向采光要求好,暂不考虑临街房源。'
|
||
},
|
||
|
||
/* -------- 初始化 -------- */
|
||
init() {
|
||
this.form.contacts = [
|
||
{
|
||
id: 'c-001',
|
||
name: '张明峰',
|
||
gender: 'male',
|
||
phoneMasked: '137****8921',
|
||
phoneCountryCode: '+86',
|
||
phone: '13712348921',
|
||
phone2CountryCode: '+86',
|
||
phone2: '',
|
||
wechat: 'zhangmf_2024',
|
||
qq: '',
|
||
remarks: '白天方便通话,晚上不接陌生来电'
|
||
},
|
||
{
|
||
id: 'c-002',
|
||
name: '李晓梅',
|
||
gender: 'female',
|
||
phoneMasked: '139****6543',
|
||
phoneCountryCode: '+86',
|
||
phone: '13956786543',
|
||
phone2CountryCode: '+86',
|
||
phone2: '',
|
||
wechat: '',
|
||
qq: '345678901',
|
||
remarks: ''
|
||
}
|
||
];
|
||
// Intersection Observer:Tab 随滚动自动高亮
|
||
this.$nextTick(() => {
|
||
const sections = ['contacts', 'basic', 'requirement'];
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const id = entry.target.id.replace('section-', '');
|
||
if (sections.includes(id)) this.activeTab = id;
|
||
}
|
||
});
|
||
}, { rootMargin: '-30% 0px -50% 0px' });
|
||
sections.forEach(id => {
|
||
const el = document.getElementById('section-' + id);
|
||
if (el) observer.observe(el);
|
||
});
|
||
});
|
||
},
|
||
|
||
/* -------- Tab 切换 -------- */
|
||
setTab(tab) {
|
||
this.activeTab = tab;
|
||
const el = document.getElementById('section-' + tab);
|
||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
},
|
||
|
||
/* -------- 字段 key 辅助 -------- */
|
||
fk(group, idx, field) {
|
||
return group + '_' + idx + '_' + field;
|
||
},
|
||
|
||
inputCls(key) {
|
||
return this.errors[key]
|
||
? 'border-danger-600 ring-danger-600/20 focus:border-danger-600 focus:ring-danger-600/20'
|
||
: 'border-neutral-300 focus:border-primary-600 focus:ring-primary-600/20';
|
||
},
|
||
|
||
groupCls(key) {
|
||
return this.errors[key]
|
||
? 'border-danger-600 ring-2 ring-danger-600/20'
|
||
: 'border-neutral-300';
|
||
},
|
||
|
||
phoneCls(key) {
|
||
return this.errors[key]
|
||
? 'border-danger-600 ring-danger-600/20 focus-within:border-danger-600 focus-within:ring-danger-600/20'
|
||
: 'border-neutral-300 focus-within:border-primary-600 focus-within:ring-primary-600/20';
|
||
},
|
||
|
||
/* -------- 联系人增删 -------- */
|
||
addContact() {
|
||
if (this.form.contacts.length >= 5) return;
|
||
this.form.contacts.push({
|
||
id: 'c-new-' + Date.now(),
|
||
name: '', gender: '',
|
||
phoneMasked: '', phoneCountryCode: '+86', phone: '',
|
||
phone2CountryCode: '+86', phone2: '',
|
||
wechat: '', qq: '', remarks: ''
|
||
});
|
||
},
|
||
removeContact(idx) {
|
||
if (idx === 0 || this.form.contacts.length <= 1) return;
|
||
this.form.contacts.splice(idx, 1);
|
||
},
|
||
|
||
/* -------- 查看号码 -------- */
|
||
revealPhone() {
|
||
if (this.revealLoading || this.phoneRevealed) return;
|
||
this.revealLoading = true;
|
||
setTimeout(() => {
|
||
this.phoneRevealed = true;
|
||
this.revealLoading = false;
|
||
this.showToast('已解码', '号码已显示,本次查看已留痕', 'success');
|
||
}, 800);
|
||
},
|
||
|
||
/* -------- 表单校验 -------- */
|
||
validateForm() {
|
||
this.errors = {};
|
||
let firstKey = '';
|
||
|
||
this.form.contacts.forEach((c, idx) => {
|
||
const nameKey = this.fk('contacts', idx, 'name');
|
||
const genderKey = this.fk('contacts', idx, 'gender');
|
||
const phoneKey = this.fk('contacts', idx, 'phone');
|
||
if (!c.name) {
|
||
this.errors[nameKey] = '姓名不能为空';
|
||
if (!firstKey) firstKey = nameKey;
|
||
}
|
||
if (!c.gender) {
|
||
this.errors[genderKey] = '请选择称呼';
|
||
if (!firstKey) firstKey = genderKey;
|
||
}
|
||
if ((this.phoneRevealed || idx > 0) && !c.phone) {
|
||
this.errors[phoneKey] = '请输入手机号';
|
||
if (!firstKey) firstKey = phoneKey;
|
||
}
|
||
});
|
||
|
||
if (!this.form.requirementTypes || this.form.requirementTypes.length === 0) {
|
||
this.errors.requirement_types = '请至少选择一种需求类型';
|
||
if (!firstKey) firstKey = 'requirement_types';
|
||
}
|
||
if (!this.form.propertyUsage) {
|
||
this.errors.property_usage = '请选择用途';
|
||
if (!firstKey) firstKey = 'property_usage';
|
||
}
|
||
if (!this.form.grade) {
|
||
this.errors.grade = '请选择等级';
|
||
if (!firstKey) firstKey = 'grade';
|
||
}
|
||
if (!this.form.source) {
|
||
this.errors.source = '请选择客户来源';
|
||
if (!firstKey) firstKey = 'source';
|
||
}
|
||
if (!this.form.budgetMin && !this.form.budgetMax) {
|
||
this.errors.budget = '请填写总价区间';
|
||
if (!firstKey) firstKey = 'budget';
|
||
}
|
||
if (!this.form.areaMin && !this.form.areaMax) {
|
||
this.errors.area = '请填写面积区间';
|
||
if (!firstKey) firstKey = 'area';
|
||
}
|
||
if (!this.form.bedroomCounts || this.form.bedroomCounts.length === 0) {
|
||
this.errors.bedroom_counts = '请至少选择一种居室要求';
|
||
if (!firstKey) firstKey = 'bedroom_counts';
|
||
}
|
||
|
||
if (firstKey) {
|
||
this.$nextTick(() => {
|
||
const el = document.querySelector('[data-field-key="' + firstKey + '"]');
|
||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
});
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
|
||
/* -------- 提交 -------- */
|
||
submitForm() {
|
||
if (this.submitting) return;
|
||
if (!this.validateForm()) return;
|
||
this.submitting = true;
|
||
this.redirectHint = '';
|
||
setTimeout(() => {
|
||
this.submitting = false;
|
||
this.showToast('保存成功', '客源信息已更新', 'success');
|
||
this.redirectHint = '模拟跳转中:即将返回客源详情页 /clients/CL20260426001/';
|
||
}, 1200);
|
||
},
|
||
|
||
/* -------- 取消 -------- */
|
||
cancelForm() {
|
||
this.redirectHint = '已取消编辑,模拟返回客源详情页 /clients/CL20260426001/';
|
||
},
|
||
|
||
/* -------- Toast -------- */
|
||
showToast(message, subText = '', type = 'success') {
|
||
this.toast = { show: true, message, subText, type };
|
||
setTimeout(() => { this.toast.show = false; }, 2500);
|
||
}
|
||
};
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|