Files
nexus/Project/fonrey/客源详情_UI设计.md

78 KiB
Raw Blame History

客源详情页 UI 设计文档

版本v1.0 · 日期2026-04-25 依赖规范UI_SYSTEM.md v1.2 · 组件规范设计.md v1.0 PRD 来源Project/fonrey/PRD/客源管理/客源管理模块PRD.md §5.7 私客详情页 优先级P0 功能用 🔴 标注P1 用 🟡P2 用


目录

  1. 模块概述
    • 1.1 功能范围
    • 1.2 页面清单
    • 1.3 用户角色与权限差异
  2. 页面设计规范
    • 2.1 私客详情页 — 整体框架
    • 2.2 Section 左侧:需求信息 Tab
    • 2.3 Section 左侧:跟进记录 Tab
    • 2.4 Section 左侧:带看 Tab
    • 2.5 Section 左侧:客源解读 Tab
    • 2.6 Section 左侧:二手配房 Tab
    • 2.7 Section 右侧:客源信息概览面板
    • 2.8 Section 右侧:联系人面板
    • 2.9 Section 右侧:相关员工面板
  3. 弹窗/抽屉设计规范
    • 3.1 编辑基础信息Modal
    • 3.2 改等级Modal
    • 3.3 改状态Modal
    • 3.4 转成交Modal
    • 3.5 选择成交房源Modal 宽型)
    • 3.6 写入跟进Drawer
    • 3.7 新增带看(全页表单)
    • 3.8 新增预约带看(全页表单)
  4. 交互状态规范
    • 4.1 客源状态机
    • 4.2 权限控制矩阵
    • 4.3 HTMX 请求规范
  5. 关键数据字段说明
  6. 竞品截图对应关系
  7. 实现优先级与工期估算
  8. 开放问题(待决策)

1. 模块概述

1.1 功能范围

P0 功能 🔴

  • 私客详情页整体框架(左右双栏布局 + Tab 导航)
  • 需求信息查看与编辑
  • 跟进记录(全部/写入跟进/敏感信息跟进/修改跟进/其他跟进)
  • 带看记录(预约带看 + 实际带看)
  • 联系人查看与新增
  • 客源状态变更:改等级、改状态
  • 转公客 / 转成交 / 转无效
  • 右侧客源信息概览面板(基础字段展示 + 快捷操作)
  • 相关员工查看

P1 功能 🟡

  • 客源解读AI 行为分析)
  • 二手配房(录客配房 + 系统配房)
  • 收藏功能
  • 待办提醒模块
  • 编辑基础信息(完整表单)

P2 功能

  • 编辑信息来源
  • 查看操作日志
  • 名下房产修改

1.2 页面清单

页面名称 URL 模式建议 优先级 对应 PRD 章节
私客详情页(主框架) /clients/<client_id>/ P0 🔴 §5.7.1
需求信息 Tab /clients/<client_id>/requirements/ (HTMX partial) P0 🔴 §5.7.2
跟进记录 Tab /clients/<client_id>/follow-logs/ (HTMX partial) P0 🔴 §5.7.2
带看 Tab /clients/<client_id>/viewings/ (HTMX partial) P0 🔴 §5.7.2
客源解读 Tab /clients/<client_id>/insights/ (HTMX partial) P1 🟡 §5.7.2
二手配房 Tab /clients/<client_id>/property-matches/ (HTMX partial) P1 🟡 §5.7.2
右侧信息面板 /clients/<client_id>/sidebar/ (HTMX partial) P0 🔴 §5.7.3
新增带看(全页) /clients/<client_id>/viewings/create/ P0 🔴 §5.7.2
新增预约带看(全页) /clients/<client_id>/viewings/appointment/create/ P0 🔴 §5.7.2

1.3 用户角色与权限差异

功能/视图元素 经纪人(本人) 经纪人(他人客源) 店长 管理员
查看详情页 受限(需权限)
查看联系人号码明文 (有操作留痕)
写入跟进记录
编辑需求信息
改等级/改状态
转公客/转成交/转无效
编辑相关员工
查看操作日志 部分
敏感信息跟进查看 (本人)
转公客后操作

注:联系人号码查看必须留痕(自动写入 log_type='sensitive_view'client_follow_logs 记录)。


2. 页面设计规范

2.1 私客详情页 — 整体框架P0 🔴

2.1.1 页面概述

  • URL/clients/<client_id>/
  • 访问入口:客源列表点击任意行 → 跳转详情页;面包屑返回列表
  • 页面职责:集中展示私客的全量信息,提供跟进、带看、状态变更等核心操作入口
  • 竞品参考截图Project/fonrey/screenshots/客源/私客详情.png

2.1.2 布局结构

┌────────────────────────────────────────────────────────────────────────┐
│  顶部导航栏(全局,来自 base.html高度 48px                              │
├────────────────────────────────────────────────────────────────────────┤
│  面包屑导航(客源 / 客源管理 / {客源标题}                                 │
├──────────────────────────────────────┬─────────────────────────────────┤
│                                      │                                 │
│   左侧主内容区(约 68% 宽度)            │   右侧信息面板(约 32% 宽度)      │
│   ─────────────────────────────      │   ─────────────────────────     │
│   [Tab 导航栏]                        │   [客源标题 Banner橙色]      │
│   需求信息 | 跟进记录 | 带看 |           │   [状态标签行]                 │
│   客源解读 | 智能配房                   │   [基础字段列表]                │
│                                      │   [展开全部 链接]               │
│   [Tab 内容区HTMX 动态加载)]         │   [快捷操作按钮组]              │
│                                      │   [状态变更操作网格]            │
│                                      │   [待办提醒区块]               │
│                                      │   [联系人区块]                 │
│                                      │   [相关员工区块]               │
│                                      │                                 │
└──────────────────────────────────────┴─────────────────────────────────┘

2.1.3 区域详细规范

[面包屑导航区]

属性 说明
组件 原生 HTML <nav> + Heroicons chevron-right 16px
内容 客源 / 客源管理 / {client.title}
样式 flex items-center gap-1 text-sm text-neutral-500 px-6 py-3
最后一项 text-neutral-700 font-medium(不可点击)

[双栏主布局]

<div class="flex gap-6 px-6 pb-6 items-start">
  <!-- 左侧主内容区 -->
  <div class="flex-1 min-w-0 space-y-0">
    <!-- Tab 导航 + Tab 内容 -->
  </div>
  <!-- 右侧固定面板 -->
  <div class="w-80 flex-shrink-0 space-y-3 sticky top-4">
    <!-- 客源信息概览 + 联系人 + 相关员工 -->
  </div>
</div>

右侧面板使用 sticky top-4 实现滚动时固定,最大高度 max-h-[calc(100vh-5rem)] overflow-y-auto

[Tab 导航栏]

属性 说明
组件 §10 Tab Navigation
Tab 列表 需求信息 / 跟进记录 / 带看 / 客源解读 / 智能配房
默认激活 需求信息
激活样式 text-primary-600 font-medium border-b-2 border-primary-600 -mb-px
未激活样式 text-neutral-500 hover:text-neutral-700
Alpine 管理 x-data="{ activeTab: 'requirements' }"
HTMX 行为 点击 Tab 时 hx-get 对应 partial URLhx-target="#tab-content", hx-swap="innerHTML"
懒加载 非默认 Tab 内容通过 hx-trigger="click" 首次点击时加载
<div x-data="{ activeTab: 'requirements' }" class="bg-white rounded-lg border border-neutral-200">
  <!-- Tab 导航 -->
  <div class="border-b border-neutral-200 px-4" role="tablist">
    <nav class="flex gap-1 -mb-px">
      <button
        role="tab"
        :aria-selected="activeTab === 'requirements'"
        @click="activeTab = 'requirements'"
        :class="activeTab === 'requirements'
          ? 'h-10 px-4 text-sm text-primary-600 font-medium border-b-2 border-primary-600 -mb-px'
          : 'h-10 px-4 text-sm text-neutral-500 hover:text-neutral-700'"
        hx-get="/clients/{{ client.id }}/requirements/"
        hx-target="#tab-content"
        hx-swap="innerHTML"
        hx-trigger="click[activeTab !== 'requirements']"
      >需求信息</button>
      <!-- 其余 Tab 类似 -->
    </nav>
  </div>
  <!-- Tab 内容区 -->
  <div id="tab-content" class="p-4" role="tabpanel">
    <!-- 初始渲染需求信息内容 -->
  </div>
</div>

2.1.4 使用的特殊组件

组件名 来源 用途 自定义说明
Tab Navigation §10 Tab Navigation 左侧五个功能 Tab 切换 不做懒加载 intersect,改为 click 首次触发

2.1.5 空状态设计

不适用(详情页框架本身无空状态,各 Tab 内容各自定义)

2.1.6 Loading 状态

Tab 切换时,#tab-content 区域显示骨架屏:

<!-- HTMX indicator放在 Tab 内容容器内) -->
<div id="tab-content-skeleton" class="animate-pulse space-y-3 p-4 htmx-indicator">
  <div class="h-4 bg-neutral-200 rounded w-3/4"></div>
  <div class="h-4 bg-neutral-200 rounded w-1/2"></div>
  <div class="h-4 bg-neutral-200 rounded w-2/3"></div>
</div>

2.2 Section 左侧:需求信息 TabP0 🔴

2.2.1 页面概述

  • URL/clients/<client_id>/requirements/HTMX partial
  • 访问入口:详情页默认激活 Tab
  • 页面职责:展示客户购房/租房需求的全量字段,支持内联编辑
  • 竞品参考截图Project/fonrey/screenshots/客源/需求信息.png

2.2.2 布局结构

┌──────────────────────────────────────────────────────────┐
│  [标题区] 需求信息                        [编辑] 链接        │
├──────────────────────────────────────────────────────────┤
│  [字段网格] 3列布局字段名灰色/ 字段值(深色)          │
│  总价 | 面积 | 居室                                        │
│  装修 | 朝向 | 楼层                                        │
│  楼龄 | 意向商圈 | 意向小区                                  │
│  交通情况 | 备注(跨列)                                     │
└──────────────────────────────────────────────────────────┘

2.2.3 区域详细规范

[需求信息字段网格]

字段名 数据字段 展示类型 特殊渲染
总价 requirement.price_min / price_max 文本范围 {min}-{max}万元,空时显示 -
面积 requirement.area_min / area_max 文本范围 {min}㎡-{max}㎡
居室 requirement.rooms 文本 2居
装修 requirement.decoration 文本 空时 -
朝向 requirement.orientation 文本 多选逗号分隔
楼层 requirement.floor_preference 文本 多选,如 中楼层、低楼层
楼龄 requirement.building_age 文本 空时 -
意向商圈 requirement.preferred_districts 多标签 逗号分隔文本,空时 -
意向小区 requirement.preferred_communities 多标签 逗号分隔,空时 -
交通情况 requirement.transport_preference 文本 空时 -
备注 requirement.notes 多行文本 跨越全行 col-span-3,空时 -

字段渲染 HTML 结构:

<div class="grid grid-cols-3 gap-x-6 gap-y-4">
  <div class="space-y-1">
    <dt class="text-xs text-neutral-500">总价</dt>
    <dd class="text-sm text-neutral-800">{{ req.price_min }}-{{ req.price_max }}万元</dd>
  </div>
  <!-- ... -->
</div>

[编辑按钮]

属性 说明
样式 text-sm text-info-600 hover:text-blue-700 font-medium
触发 点击打开编辑需求信息 Drawer§3.1
权限 仅归属人/管理员/店长可见

2.2.4 使用的特殊组件

组件名 来源 用途 自定义说明
Inline Edit Mode §15 字段网格切换编辑态 本模块改为 Drawer 编辑,不用内联编辑模式

2.2.5 空状态设计

若需求信息尚未录入(所有字段均为空),在内容区中部显示:

<div class="flex flex-col items-center justify-center py-12 text-center">
  <svg class="w-10 h-10 text-neutral-300 mb-3"><!-- Heroicons document-text --></svg>
  <p class="text-sm text-neutral-500">暂未填写需求信息</p>
  <button class="mt-3 text-sm text-primary-600 hover:underline">立即编辑</button>
</div>

2.2.6 Loading 状态

由父页面 Tab 切换骨架屏覆盖(见 §2.1.6),内容区自身不额外处理。


2.3 Section 左侧:跟进记录 TabP0 🔴

2.3.1 页面概述

  • URL/clients/<client_id>/follow-logs/HTMX partial
  • 访问入口:点击"跟进记录" Tab
  • 页面职责:按时间线展示所有跟进记录,支持筛选、写入新跟进
  • 竞品参考截图Project/fonrey/screenshots/客源/跟进记录-全部.png跟进记录-写入跟进.png

2.3.2 布局结构

┌──────────────────────────────────────────────────────────┐
│  [子 Tab 行] 全部 | 写入跟进 | 敏感信息跟进 | 修改跟进 | 其他跟进 │
├──────────────────────────────────────────────────────────┤
│  [筛选区(写入跟进 Tab 展开更多筛选)]                         │
│  日期范围选择器            [有录音] [有图片] 复选框            │
│  跟进目的:多选复选框(展开/收起)                             │
│  跟进标签:多选复选框(展开/收起)                             │
├──────────────────────────────────────────────────────────┤
│  [时间线内容区]                                            │
│  ● 2026-04-19 ──────────────────────────────────         │
│    [跟进记录条目 1]                                        │
│    [跟进记录条目 2]                                        │
│  ● 2026-04-18 ──────────────────────────────────         │
│    [跟进记录条目 ...]                                      │
│  [加载更多 按钮]                                           │
└──────────────────────────────────────────────────────────┘

2.3.3 区域详细规范

[子 Tab 导航栏]

属性 说明
组件 §10 Tab Navigation嵌套风格改为 pill 式)
Tab 列表 全部 / 写入跟进 / 敏感信息跟进 / 修改跟进 / 其他跟进
映射 log_type all / written / sensitive_view / modified / other
样式 bg-neutral-100 rounded-full px-3 py-1 text-xs;激活 bg-primary-600 text-white
Alpine x-data="{ logTab: 'all' }"
HTMX 切换时 hx-get="/clients/{id}/follow-logs/?type={logTab}" hx-target="#log-list" hx-swap="innerHTML"

[筛选区]

字段 组件类型 说明
日期范围 §9 Date Range Picker 开始时间 → 结束时间hx-trigger="change"
有录音 / 有图片 Checkbox hx-trigger="change" 触发重筛
跟进目的 多选 Checkbox 网格 仅在"写入跟进"Tab 下展开显示;默认折叠,"展开 " 链接切换;竞品中约 25 个选项
跟进标签 多选 Checkbox 同上,与跟进目的共用展开/收起逻辑
<!-- 筛选区 Alpine 管理展开/收起 -->
<div x-data="{ filterOpen: false }">
  <div class="flex items-center gap-4 py-2 border-b border-neutral-100">
    <span class="text-xs text-neutral-500">筛选:</span>
    <label class="flex items-center gap-1 text-xs text-neutral-600 cursor-pointer">
      <input type="checkbox" class="w-3 h-3 rounded accent-primary-600"
             hx-get="/clients/{{ id }}/follow-logs/" hx-trigger="change"
             hx-include="[name='filter']" hx-target="#log-list" name="filter" value="has_audio">
      有录音
    </label>
    <label class="flex items-center gap-1 text-xs text-neutral-600 cursor-pointer">
      <input type="checkbox" class="w-3 h-3 rounded accent-primary-600"
             hx-get="/clients/{{ id }}/follow-logs/" hx-trigger="change"
             hx-include="[name='filter']" hx-target="#log-list" name="filter" value="has_image">
      有图片
    </label>
  </div>
  <!-- 跟进目的(仅写入跟进 Tab 显示) -->
  <template x-if="logTab === 'written'">
    <div class="py-2 border-b border-neutral-100">
      <div class="flex items-center gap-2 flex-wrap">
        <span class="text-xs text-neutral-500 flex-shrink-0">跟进目的:</span>
        <!-- 复选框网格 -->
        <div x-show="filterOpen" class="flex flex-wrap gap-2">
          <!-- 25 个目的选项 -->
        </div>
        <button @click="filterOpen = !filterOpen"
                class="text-xs text-info-600 hover:underline ml-auto">
          <span x-text="filterOpen ? '收起 ∧' : '展开 '"></span>
        </button>
      </div>
    </div>
  </template>
</div>

[时间线内容区]

采用 §10.3 内嵌 Timeline 子组件规范:

元素 规格
外容器 relative pl-6 space-y-1
竖线 absolute left-2.5 top-0 bottom-0 w-px bg-neutral-200
日期分组标题 flex items-center gap-2 text-xs font-semibold text-neutral-500 uppercase py-2;圆点 w-3 h-3 rounded-full border-2 border-primary-600 bg-white absolute -left-[14px]
记录条目卡片 ml-2 bg-white border border-neutral-100 rounded-md p-3 space-y-1

跟进记录条目卡片规范:

┌────────────────────────────────────────────────────────┐
│  [类型标签 Badge] 【电话】    [时间戳] 11:25  [⋯ 更多]  │
│  [内容文本] 433弄5楼65.85平想置换...                    │
│  [底部元信息] 都市港湾店一组 雷威 · 2026-04-19          │
│  [已开放] [隐藏] 操作链接(灰色小字)                   │
└────────────────────────────────────────────────────────┘
元素 规格
类型标签 text-xs font-medium px-2 py-0.5 rounded-full bg-blue-100 text-blue-700
时间戳 text-xs text-neutral-400 tabular-nums
内容文本 text-sm text-neutral-700 leading-relaxed
元信息 text-xs text-neutral-400
操作链接 text-xs text-info-600 hover:underline" 已开放" / "隐藏"
更多菜单 Heroicons ellipsis-horizontal 16px点击展开 Dropdown修改/删除选项)
敏感信息标记 log_type === 'sensitive_view':条目背景 bg-amber-50 border-amber-200,不可删除

[加载更多]

<div id="log-load-more" class="text-center py-3">
  <button class="text-sm text-primary-600 hover:underline"
          hx-get="/clients/{{ id }}/follow-logs/?page=2"
          hx-target="#log-list"
          hx-swap="beforeend"
          hx-indicator="#log-load-spinner">
    查看全部跟进
  </button>
  <span id="log-load-spinner" class="htmx-indicator ml-2 inline-block w-4 h-4 border-2 border-primary-600 border-t-transparent rounded-full animate-spin"></span>
</div>

2.3.4 使用的特殊组件

组件名 来源 用途 自定义说明
Tab Navigation §10 跟进类型子 Tab 改为 pill 风格,而非下划线风格
Timeline 子组件 §10.3 时间线记录展示 增加日期分组标题,圆点用 border-primary-600
Date Range Picker §9 跟进记录日期筛选 标准实现,hx-trigger="change" 自动触发筛选

2.3.5 空状态设计

<div class="flex flex-col items-center justify-center py-16 text-center">
  <svg class="w-10 h-10 text-neutral-300 mb-3"><!-- Heroicons chat-bubble-left-ellipsis --></svg>
  <p class="text-sm text-neutral-500">暂无跟进</p>
</div>

2.3.6 Loading 状态

<div class="animate-pulse space-y-3 p-4 htmx-indicator">
  <div class="h-3 bg-neutral-200 rounded w-24"></div><!-- 日期标题 -->
  <div class="ml-2 space-y-2 border border-neutral-100 rounded-md p-3">
    <div class="h-3 bg-neutral-200 rounded w-16"></div>
    <div class="h-4 bg-neutral-200 rounded w-full"></div>
    <div class="h-3 bg-neutral-200 rounded w-1/2"></div>
  </div>
</div>

2.4 Section 左侧:带看 TabP0 🔴

2.4.1 页面概述

  • URL/clients/<client_id>/viewings/HTMX partial
  • 访问入口:点击"带看" Tab
  • 页面职责:展示该客源的预约带看和实际带看记录,提供新增入口
  • 竞品参考截图Project/fonrey/screenshots/客源/带看.png

2.4.2 布局结构

┌──────────────────────────────────────────────────────────┐
│  [子 Tab] 预约 | 带看active          [日期范围选择器]   │
├──────────────────────────────────────────────────────────┤
│  [筛选复选框] □ 归属人带看  □ 其他人带看                   │
├──────────────────────────────────────────────────────────┤
│  [时间线带看记录列表]                                      │
│  ● 2026-04-17 20:30                                      │
│    带看情况: 客户继续维护                                   │
│    房源: 金沙丽晶苑一期-001-1201  [一看] 带看: 雷威          │
│    [详情 ]                                              │
│  [加载更多]                                              │
└──────────────────────────────────────────────────────────┘

2.4.3 区域详细规范

[子 Tab 导航]

属性 说明
Tab 列表 预约 / 带看
Alpine x-data="{ viewingTab: 'viewing' }"
HTMX hx-get="/clients/{id}/viewings/?type={viewingTab}" hx-target="#viewing-list"

[筛选行]

字段 说明
归属人带看 hx-trigger="change" 筛选重载
其他人带看 同上
日期范围 §9 Date Range Pickerhx-trigger="change"

[带看记录条目]

┌─────────────────────────────────────────────────────────┐
│  ● 2026-04-17 20:30                                     │
│  带看情况: 客户继续维护                                    │
│  [房源链接] 金沙丽晶苑一期-001-1201                        │
│  [带看次数 Badge: 一看 橙色] 带看: 雷威                    │
│  [详情  链接]                                           │
└─────────────────────────────────────────────────────────┘
元素 规格
带看次数 Badge text-xs font-medium px-2 py-0.5 rounded-full bg-primary-50 text-primary-700 border border-primary-200
房源链接 text-sm text-info-600 hover:underline
带看情况 text-sm text-neutral-700
带看人 text-xs text-neutral-500
详情链接 text-xs text-info-600 hover:underline ml-auto

2.4.4 使用的特殊组件

组件名 来源 用途 自定义说明
Tab Navigation §10 预约/带看子 Tab pill 风格
Timeline 子组件 §10.3 带看记录时间线 圆点时间戳展示带看时刻
Date Range Picker §9 带看日期筛选 标准实现

2.4.5 空状态设计

<div class="flex flex-col items-center justify-center py-16 text-center">
  <svg class="w-10 h-10 text-neutral-300 mb-3"><!-- Heroicons home --></svg>
  <p class="text-sm text-neutral-500">暂无带看记录</p>
  <a href="/clients/{{ id }}/viewings/create/"
     class="mt-3 text-sm text-primary-600 hover:underline">新增带看</a>
</div>

2.4.6 Loading 状态

与 §2.3.6 相同的骨架屏方案。


2.5 Section 左侧:客源解读 TabP1 🟡

2.5.1 页面概述

  • URL/clients/<client_id>/insights/HTMX partial
  • 访问入口:点击"客源解读" Tab
  • 页面职责:展示 AI 分析的客户找房行为数据:活跃时间、购房偏好、区域偏好
  • 竞品参考截图Project/fonrey/screenshots/客源/客源解读.png

2.5.2 布局结构

┌──────────────────────────────────────────────────────────┐
│  [说明文字] + 使用指南链接          [数据更新时间戳]          │
├──────────────────────────────────────────────────────────┤
│  [活跃行为卡片]       [活跃时间卡片: 工作日 | 周末]          │
├──────────────────────────────────────────────────────────┤
│  购房偏好                [时段筛选: 近7日 | 近30日 | 近90日] │
│  [偏好摘要网格: 价格/户型/面积/商圈/小区]                    │
│  [类型占比行]                                             │
│  [偏好子 Tab: 二手偏好 | 新房偏好]                          │
│  [三个甜甜圈图: 价格偏好 / 户型偏好 / 面积偏好]              │
│  [区域-商圈偏好] [小区偏好]                                │
└──────────────────────────────────────────────────────────┘

2.5.3 区域详细规范

[活跃行为卡片]

属性 说明
样式 bg-white border border-neutral-200 rounded-lg p-4
内容 标题"活跃行为"+ 数值(如"7 天内")用 text-2xl font-bold text-neutral-800
副标题 text-sm text-neutral-500 描述说明

[购房偏好摘要网格]

字段 规格
价格 text-sm font-semibold text-neutral-800 展示范围
户型 同上
面积 同上
商圈 / 小区 空时显示 -

[甜甜圈图]

MVP 阶段可用简单的百分比 text-4xl font-bold text-primary-600 替代 Chart.js 图表,配合 text-sm text-neutral-500 标签文字。P1 阶段再引入图表库。

[时段筛选 Pill]

<div x-data="{ period: '7d' }" class="flex gap-1">
  <template x-for="p in [{val:'7d', label:'近7日'},{val:'30d', label:'近30日'},{val:'90d', label:'近90日'}]">
    <button @click="period = p.val"
            :class="period === p.val ? 'bg-primary-600 text-white' : 'bg-neutral-100 text-neutral-600'"
            class="text-xs px-3 py-1 rounded-full"
            hx-get="/clients/{{ id }}/insights/" :hx-vals="JSON.stringify({period: p.val})"
            hx-target="#insight-content" hx-swap="innerHTML">
      <span x-text="p.label"></span>
    </button>
  </template>
</div>

2.5.4 使用的特殊组件

组件名 来源 用途 自定义说明
Tab Navigation §10 二手偏好/新房偏好子 Tab pill 风格

2.5.5 空状态设计

<div class="text-center py-12">
  <svg class="w-10 h-10 text-neutral-300 mx-auto mb-3"><!-- Heroicons chart-bar --></svg>
  <p class="text-sm text-neutral-500">暂无找房行为数据</p>
  <p class="text-xs text-neutral-400 mt-1">数据由系统每日凌晨更新</p>
</div>

2.5.6 Loading 状态

骨架屏三个卡片占位块,animate-pulse 处理。


2.6 Section 左侧:二手配房 TabP1 🟡

2.6.1 页面概述

  • URL/clients/<client_id>/property-matches/HTMX partial
  • 访问入口:点击"智能配房" Tab
  • 页面职责:展示系统根据客户需求匹配的二手房源,按分组展示推荐卡片
  • 竞品参考截图Project/fonrey/screenshots/客源/二手配房.png

2.6.2 布局结构

┌──────────────────────────────────────────────────────────┐
│  二手配房          [更新时间]        [批量分享 按钮]       │
│  [子 Tab] 录客配房active | 系统配房                      │
├──────────────────────────────────────────────────────────┤
│  [分组标题: 优质户型]                                       │
│  [房源卡片] [房源卡片] [房源卡片]                           │
│  [分组标题: 降价]                                          │
│  [房源卡片] [房源卡片]                                     │
│  [分组标题: 热门]                                          │
│  [房源卡片] ...                                           │
└──────────────────────────────────────────────────────────┘

2.6.3 区域详细规范

[房源卡片]

┌─────────────────────────────────────────────┐
│  [缩略图 80×60]   [房源名称(蓝色链接)]        │
│                   [规格] 2/2/1 · 101.17㎡    │
│                   [区域] 嘉定 丰庄            │
│                   [特征标签组](橙/蓝 pills  │
│  [价格] 620万 [跌价信息] 已跌20万              │
│  [单价] 61283元/㎡                           │
│  [分享房源] [反馈 ]action 链接)           │
└─────────────────────────────────────────────┘
元素 规格
缩略图 w-20 h-15 rounded-md object-cover flex-shrink-0
房源名称 text-sm font-medium text-info-600 hover:underline
规格文字 text-xs text-neutral-500
特征标签 text-xs px-1.5 py-0.5 rounded bg-orange-50 text-orange-700bg-blue-50 text-blue-700
价格 text-base font-bold text-neutral-900
跌价标注 text-xs text-neutral-400
分组标题 text-sm font-semibold text-neutral-700 py-2 border-b border-neutral-200 mb-3
批量分享 btn-secondary text-xs 灰色描边按钮
反馈 Dropdown选项满意/不满意/已跟进

卡片网格布局:

<div class="grid grid-cols-1 gap-3" id="match-list">
  <!-- 每个分组 -->
  <div>
    <h3 class="text-sm font-semibold text-neutral-700 py-2 border-b border-neutral-200 mb-3">优质户型</h3>
    <div class="space-y-3">
      <!-- 房源卡片 -->
    </div>
  </div>
</div>

2.6.4 使用的特殊组件

组件名 来源 用途
Tab Navigation §10 录客配房/系统配房子 Tab

2.6.5 空状态设计

<div class="text-center py-12">
  <svg class="w-10 h-10 text-neutral-300 mx-auto mb-3"><!-- Heroicons building-office-2 --></svg>
  <p class="text-sm text-neutral-500">暂无匹配房源</p>
</div>

2.6.6 Loading 状态

卡片骨架屏,每张卡片用 animate-pulse 占位块。


2.7 Section 右侧客源信息概览面板P0/P1

2.7.1 页面概述

  • URL/clients/<client_id>/sidebar/HTMX partial初始 SSR
  • 访问入口:详情页右侧固定面板(持续可见)
  • 页面职责:集中展示客源核心标识信息 + 快捷操作入口
  • 竞品参考截图Project/fonrey/screenshots/客源/客源信息概览.png

2.7.2 布局结构

┌──────────────────────────────────────────────┐
│  [橙色 Banner]                               │
│  求购  叶阿姨买一房(上门)  女士               │
├──────────────────────────────────────────────┤
│  [状态标签行] 私客 · 一看 · C(一般)            │
├──────────────────────────────────────────────┤
│  [基础字段列表(只读)]                         │
│  最近跟进 / 客户编号 / 委托日期 ...            │
│  [展开全部  链接]                            │
├──────────────────────────────────────────────┤
│  [三个主操作按钮]P0                        │
│  [打电话] [写跟进] [报备/常看]                 │
├──────────────────────────────────────────────┤
│  [状态变更操作网格]P0                      │
│  ☆ 收藏 · 不置顶                             │
│  改等级 · 改状态                              │
│  转公客 · 转成交                              │
│  转无效 · 编辑客源                            │
└──────────────────────────────────────────────┘

2.7.3 区域详细规范

[橙色 Banner]

<div class="bg-primary-600 rounded-t-lg px-4 py-3">
  <div class="flex items-start gap-2">
    <span class="text-xs font-medium text-white bg-white/20 px-2 py-0.5 rounded-full">求购</span>
    <div class="flex-1 min-w-0">
      <h2 class="text-sm font-semibold text-white leading-snug truncate">
        {{ client.title }}
      </h2>
      <p class="text-xs text-white/80 mt-0.5">{{ client.primary_contact.name }} {{ client.primary_contact.gender_display }}</p>
    </div>
  </div>
</div>

注:竞品用橙色 #f5774a;本系统采用 Teal primary-600 (#0F766E) 替代,保持品牌一致性。

[状态标签行]

标签 样式
私客client_type text-xs px-2 py-0.5 rounded-full bg-neutral-100 text-neutral-600
带看次数(如"一看" text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-700
等级C/一般) text-xs px-2 py-0.5 rounded-fullA 级 bg-red-100 text-red-700B 级 bg-orange-100 text-orange-700C 级 bg-yellow-100 text-yellow-700D/E bg-neutral-100 text-neutral-500

[基础字段列表]

字段名 数据来源 空值展示
最近跟进 client.last_follow_at -
客户编号 client.client_no
委托日期 client.created_at date
需求类型 client.requirement_type_display
房源用途 client.property_usage_display
客户来源 client.source_display
购房目的 client.purchase_purpose -
付款方式 client.payment_method -
名下房产 client.owned_properties 未填写
贷款记录 client.loan_record -
证件类型 client.id_type -
证件号码 client.id_number(脱敏) -
意向学校 client.preferred_school -
入学时间 client.school_start_year -

字段行渲染:

<dl class="space-y-1.5 px-4 py-3">
  <div class="flex items-start justify-between gap-2">
    <dt class="text-xs text-neutral-400 flex-shrink-0 w-16">最近跟进</dt>
    <dd class="text-xs text-neutral-700 text-right">{{ client.last_follow_at|date:"Y-m-d"|default:"-" }}</dd>
  </div>
  <!-- ... -->
</dl>

[展开全部链接]

<button x-data="{ expanded: false }" @click="expanded = !expanded"
        class="w-full text-center text-xs text-info-600 hover:underline py-2 border-t border-neutral-100">
  <span x-text="expanded ? '收起 ∧' : '展开全部 '"></span>
</button>
<!-- 隐藏字段区x-show="expanded" -->

[三个主操作按钮P0]

<div class="grid grid-cols-3 gap-2 px-4 py-3 border-t border-neutral-100">
  <button class="flex flex-col items-center gap-1 py-2 bg-primary-600 hover:bg-primary-700
                 text-white text-xs font-medium rounded-md transition-colors">
    <svg class="w-4 h-4"><!-- Heroicons phone --></svg>
    打电话
  </button>
  <button hx-get="/clients/{{ id }}/follow-logs/create/"
          hx-target="#drawer-container" hx-swap="innerHTML"
          @click="drawerOpen = true"
          class="flex flex-col items-center gap-1 py-2 bg-primary-600 hover:bg-primary-700
                 text-white text-xs font-medium rounded-md transition-colors">
    <svg class="w-4 h-4"><!-- Heroicons pencil-square --></svg>
    写跟进
  </button>
  <button class="flex flex-col items-center gap-1 py-2 bg-primary-600 hover:bg-primary-700
                 text-white text-xs font-medium rounded-md transition-colors">
    <svg class="w-4 h-4"><!-- Heroicons flag --></svg>
    报备/常看
  </button>
</div>

[状态变更操作网格P0]

<div class="grid grid-cols-2 gap-1 px-4 py-3 border-t border-neutral-100">
  <!-- 收藏 -->
  <button class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons star --></svg>
    收藏
  </button>
  <!-- 不置顶 -->
  <button class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons arrow-down --></svg>
    不置顶
  </button>
  <!-- 改等级(触发 Modal -->
  <button hx-get="/clients/{{ id }}/change-grade/"
          hx-target="#modal-container" hx-swap="innerHTML"
          @click="modalOpen = true"
          class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons adjustments-horizontal --></svg>
    改等级
  </button>
  <!-- 改状态 -->
  <button hx-get="/clients/{{ id }}/change-status/"
          hx-target="#modal-container" hx-swap="innerHTML"
          @click="modalOpen = true"
          class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons arrow-path --></svg>
    改状态
  </button>
  <!-- 转公客 -->
  <button class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons users --></svg>
    转公客
  </button>
  <!-- 转成交 -->
  <button hx-get="/clients/{{ id }}/convert-deal/"
          hx-target="#modal-container" hx-swap="innerHTML"
          @click="modalOpen = true"
          class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons check-badge --></svg>
    转成交
  </button>
  <!-- 转无效 -->
  <button class="flex items-center gap-2 px-3 py-2 text-xs text-danger-600
                 hover:bg-red-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-danger-500"><!-- Heroicons x-circle --></svg>
    转无效
  </button>
  <!-- 编辑客源 -->
  <button hx-get="/clients/{{ id }}/edit-basic/"
          hx-target="#modal-container" hx-swap="innerHTML"
          @click="modalOpen = true"
          class="flex items-center gap-2 px-3 py-2 text-xs text-neutral-600
                 hover:bg-neutral-50 rounded-md transition-colors">
    <svg class="w-4 h-4 text-neutral-400"><!-- Heroicons pencil --></svg>
    编辑客源
  </button>
</div>

[待办提醒区块P1]

<div class="px-4 py-3 border-t border-neutral-100">
  <div class="flex items-center justify-between mb-1">
    <span class="text-xs font-medium text-neutral-700">待办提醒 ({{ todo_count }})</span>
    <button class="text-xs text-info-600 hover:underline">写办</button>
  </div>
  <p class="text-xs text-neutral-400">系统会在设置的时间点发送跟进通知</p>
</div>

2.7.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 改等级/改状态/转成交/编辑客源 弹窗容器

2.7.5 空状态设计

不适用(面板始终显示)

2.7.6 Loading 状态

右侧面板初始 SSR 渲染HTMX 更新时对应区域显示 opacity-50 pointer-events-none 禁用态。


2.8 Section 右侧联系人面板P0 🔴

2.8.1 页面概述

  • 竞品参考截图Project/fonrey/screenshots/客源/联系人.png

2.8.2 布局结构

┌─────────────────────────────────────────────┐
│  联系人    [查看号码]  [新增联系人]  操作链接   │
├─────────────────────────────────────────────┤
│  [联系人条目]                                │
│  姓名 · 称呼                                 │
│  电话1: +86 137****8888  [默认拨打] [接通N次] │
│  微信: -  QQ: -  备注: -                    │
└─────────────────────────────────────────────┘

2.8.3 区域详细规范

[面板 Header]

元素 规格
标题 text-sm font-semibold text-neutral-800
查看号码 text-xs text-info-600 hover:underline;点击触发 HTMX POST 到 /clients/{id}/reveal-phone/,响应返回明文号码,同时后端写入 sensitive_view 日志
新增联系人 text-xs text-info-600 hover:underline;触发 Drawer

[联系人条目]

<div class="space-y-2">
  <div class="flex items-start gap-2">
    <div class="flex-1 min-w-0">
      <p class="text-sm font-medium text-neutral-800">
        {{ contact.name }} <span class="text-neutral-400 text-xs">{{ contact.gender_display }}</span>
      </p>
      <!-- 手机号(脱敏) -->
      <p class="text-xs text-neutral-600 mt-1">
        电话1:
        <span id="phone-display-{{ contact.id }}" class="tabular-nums">
          {{ contact.phone_masked }}
        </span>
        <span class="ml-2 text-neutral-400">默认拨打</span>
        <span class="ml-1 text-neutral-400">接通{{ contact.call_success_count }}次</span>
        <span class="ml-1 text-neutral-400">拨打{{ contact.call_total_count }}次</span>
      </p>
      <p class="text-xs text-neutral-400 mt-0.5">微信: {{ contact.wechat|default:"-" }}</p>
    </div>
    <button class="text-primary-600 hover:text-primary-700 flex-shrink-0 p-1">
      <svg class="w-4 h-4"><!-- Heroicons phone --></svg>
    </button>
  </div>
</div>

查看号码交互:

<button hx-post="/clients/{{ id }}/contacts/{{ contact.id }}/reveal-phone/"
        hx-target="#phone-display-{{ contact.id }}"
        hx-swap="innerHTML"
        hx-confirm="查看号码将会留下操作记录,确认继续?"
        class="text-xs text-info-600 hover:underline">
  查看号码
</button>

2.8.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 新增联系人表单

2.8.5 空状态设计

联系人列表为空时显示 text-xs text-neutral-400 提示 "暂无联系人"。


2.9 Section 右侧相关员工面板P0 🔴

2.9.1 页面概述

  • 竞品参考截图Project/fonrey/screenshots/客源/相关员工.png

2.9.2 布局结构

┌─────────────────────────────────────────────┐
│  相关员工                           [编辑]   │
├─────────────────────────────────────────────┤
│  【首录人】                        ☎ 电话图标 │
│  都市港湾店一组 雷威                          │
│  参与时间: 2026-04-17 19:21                 │
│  【归属人】                        ☎ 电话图标 │
│  都市港湾店一组 雷威                          │
│  参与时间: 2026-04-17 19:21                 │
└─────────────────────────────────────────────┘

2.9.3 区域详细规范

<div class="bg-white rounded-lg border border-neutral-200 p-4">
  <div class="flex items-center justify-between mb-3">
    <h3 class="text-sm font-semibold text-neutral-800">相关员工</h3>
    {% if can_edit_staff %}
    <button class="text-xs text-info-600 hover:underline">编辑</button>
    {% endif %}
  </div>
  <div class="space-y-3">
    {% for staff in related_staff %}
    <div class="flex items-start justify-between">
      <div>
        <p class="text-xs font-medium text-neutral-700">【{{ staff.role_display }}】</p>
        <p class="text-sm text-neutral-800">{{ staff.department }} {{ staff.name }}</p>
        <p class="text-xs text-neutral-400">参与时间: {{ staff.joined_at|date:"Y-m-d H:i" }}</p>
      </div>
      <button class="text-primary-600 hover:text-primary-700 p-1 flex-shrink-0">
        <svg class="w-4 h-4"><!-- Heroicons phone --></svg>
      </button>
    </div>
    {% endfor %}
  </div>
</div>
元素 权限控制
编辑按钮 仅管理员/店长可见({% if can_edit_staff %}
电话图标 拨打员工内部电话,无需权限控制

3. 弹窗/抽屉设计规范

3.1 编辑基础信息P1 🟡

3.1.1 触发方式

  • 触发位置:右侧客源信息概览面板 → "编辑客源" 按钮
  • 组件类型Modal Dialog内容字段多但不足以需要 Drawer 宽度)
  • 尺寸max-w-2xl(宽型)+ 最大高度 max-h-[80vh] 内部滚动
  • 竞品截图Project/fonrey/screenshots/客源/编辑基础信息.png

3.1.2 表单字段规范

字段名 组件类型 必填 校验规则 默认值/预填值
需求类型 Checkbox二手/新房) 至少选一 预填当前值
用途 Select 必选 预填当前值
来源 Select 必选 预填当前值
购房目的 多选 Checkbox 预填当前值
付款方式 Select 预填
名下房产 Select 预填
贷款记录 Radio有/无) 预填
证件类型 Select 预填
证件号码 Text Input 身份证 18 位格式校验 预填
意向学校 动态 Text Input可多行 预填
入学时间 Select年份 预填

3.1.3 提交行为

  • 提交方式hx-patch="/clients/{{ id }}/edit-basic/"
  • 成功响应:关闭 Modal + Toast "保存成功" + hx-get 刷新右侧面板
  • 失败响应422:字段级 <p class="text-xs text-danger-600 mt-1"> 错误提示
  • HTMX 属性
<form hx-patch="/clients/{{ id }}/edit-basic/"
      hx-target="#modal-container"
      hx-swap="innerHTML"
      hx-on::after-request="if(event.detail.successful){ modalOpen = false; htmx.trigger('#sidebar', 'refresh') }">

3.1.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 弹窗容器
Multi-select Tag Input §17 购房目的多选

3.2 改等级P0 🔴

3.2.1 触发方式

  • 触发位置:右侧面板 → "改等级" 按钮
  • 组件类型Modal Dialog内容极简
  • 尺寸max-w-sm
  • 竞品截图Project/fonrey/screenshots/客源/改等级.png

3.2.2 表单字段规范

字段名 组件类型 必填 校验规则 默认值
原等级 只读文本 client.grade_display
新等级 Select 必选;不能与原等级相同

新等级选项A_urgentA+紧急)/ A / B / C默认/ D / E

3.2.3 提交行为

  • 提交方式hx-post="/clients/{{ id }}/change-grade/"
  • 成功:关闭 Modal + Toast "等级已更新" + 刷新右侧状态标签行
  • 失败422Select 下方显示错误文字
  • HTMX 属性
<form hx-post="/clients/{{ id }}/change-grade/"
      hx-target="#status-badge-row"
      hx-swap="outerHTML"
      hx-on::after-request="if(event.detail.successful){ modalOpen = false }">

3.2.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 弹窗容器

3.3 改状态P0 🔴

3.3.1 触发方式

  • 触发位置:右侧面板 → "改状态" 按钮
  • 组件类型Modal Dialog
  • 尺寸max-w-md
  • 竞品截图Project/fonrey/screenshots/客源/改状态.png

3.3.2 表单字段规范

字段名 组件类型 必填 校验规则 默认值
原状态 只读文本 client.status_display(如"求购"
新状态 Select 必选;受状态机约束(见 §4.1
等级 Select 当前等级预填
更改理由 Textarea 非空

新状态可选项(根据当前状态动态过滤,后端控制)

  • 当前 buying/renting → 可选:suspended(暂缓)、public(转公)、bought/rented_done(成交)、invalid(无效)

3.3.3 提交行为

  • 提交方式hx-post="/clients/{{ id }}/change-status/"
  • 成功:关闭 Modal + Toast "状态已更新" + 刷新整个右侧面板
  • 失败422:字段级错误
  • HTMX 属性
<form hx-post="/clients/{{ id }}/change-status/"
      hx-target="#sidebar-panel"
      hx-swap="outerHTML"
      hx-on::after-request="if(event.detail.successful){ modalOpen = false }">

3.3.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 弹窗容器

3.4 转成交P0 🔴

3.4.1 触发方式

  • 触发位置:右侧面板 → "转成交" 按钮
  • 组件类型Modal Dialog
  • 尺寸max-w-lg
  • 竞品截图Project/fonrey/screenshots/客源/转成交.png

3.4.2 表单字段规范

字段名 组件类型 必填 校验规则 默认值
状态 Radio我购/我租) 必选 我购
房源类型 Radio二手/新房) 必选 二手
成交房源 触发选择房源 Modal 的链接 必选
成交日期 Date Picker 不能早于委托日期 今日
成交价格 Number Input 正数,单位万元
成交方 员工标签(可删除,可搜索添加) 至少一人 当前归属人

成交房源选择:

<!-- 点击触发 "选择成交房源" 宽型 Modal§3.5 -->
<button type="button"
        hx-get="/properties/select-modal/"
        hx-target="#property-select-modal-container"
        hx-swap="innerHTML"
        @click="propertyModalOpen = true"
        class="text-sm text-info-600 hover:underline">
  + 选择成交房源
</button>
<!-- 已选后展示 -->
<div id="selected-property-display" class="hidden text-sm text-neutral-800">
  <!-- 由 §3.5 Modal 选中后注入 -->
</div>
<input type="hidden" name="property_id" id="selected-property-id">

3.4.3 提交行为

  • 提交方式hx-post="/clients/{{ id }}/convert-deal/"
  • 成功:关闭 Modal + Toast "已成功转为成交客" + 页面跳转至成交客详情
  • 失败422:字段级错误
  • 特殊:成功后客源状态不可逆(bought/rented_done),需在提交前显示 hx-confirm 二次确认
<form hx-post="/clients/{{ id }}/convert-deal/"
      hx-confirm="确认将此客户标记为成交?此操作不可撤销。"
      hx-on::after-request="if(event.detail.successful){ window.location.href='/clients/bought/{{ id }}/' }">

3.4.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 弹窗容器
Date Range Picker §9 成交日期选择(单日模式)

3.5 选择成交房源P0 🔴

3.5.1 触发方式

  • 触发位置:转成交 Modal → "选择成交房源" 链接
  • 组件类型Modal Dialog宽型含搜索表格
  • 尺寸max-w-5xl(全宽搜索表)
  • 竞品截图Project/fonrey/screenshots/客源/选择成交房源.png

3.5.2 表单字段规范

[搜索筛选区]

字段 组件类型 说明
关键词 Text Input 房源编号/楼盘地址/业主姓名/电话
楼栋 / 单元 / 房号 三个联动 Text Input 精确定位
区域 Select 城区筛选
状态 Select 房源状态
相关方 Select + 员工搜索 归属人筛选

[结果表格]

列名 宽度 说明
选择Radio 48px 单选
房源名称 auto 含楼栋单元房号
交易类型 80px 买卖/租赁
状态 80px Badge 展示
用途 60px 住宅/商业
城区商圈 100px 文本
房型 80px X/X/X
楼层 80px X/X
面积(㎡) 80px tabular-nums

分页共N条 | 1 2 3 … §2 Pagination 组件)

3.5.3 提交行为

  • 提交方式:点击"确定"将选中房源 ID 写入父 Modal 的隐藏字段
  • HTMX 属性:搜索按钮 hx-get 刷新结果表
  • Alpine 管理selectedPropertyId 状态,选中后激活"确定"按钮
<div x-data="{ selectedPropertyId: null, selectedPropertyName: '' }">
  <!-- 表格内 Radio 按钮绑定 @change="selectedPropertyId = $el.value; selectedPropertyName = ..." -->
  <!-- 底部 Footer -->
  <div class="flex items-center justify-between">
    <span class="text-sm text-neutral-500">
      已选 (<span x-text="selectedPropertyId ? 1 : 0"></span>/1)
    </span>
    <button :disabled="!selectedPropertyId"
            @click="
              document.getElementById('selected-property-id').value = selectedPropertyId;
              document.getElementById('selected-property-display').textContent = selectedPropertyName;
              propertyModalOpen = false;
            "
            class="btn-primary disabled:opacity-40 disabled:cursor-not-allowed">
      确定
    </button>
  </div>
</div>

3.5.4 使用的特殊组件

组件名 来源 用途
Modal Dialog §7 宽型容器 max-w-5xl
Data Table §1 房源选择结果表
Pagination §2 表格分页

3.6 写入跟进P0 🔴

3.6.1 触发方式

  • 触发位置:右侧面板 → "写跟进" 按钮;或跟进记录 Tab 内"写入跟进"按钮
  • 组件类型Drawer右侧抽屉字段较多但无需全页
  • 尺寸w-[480px]
  • 竞品截图Project/fonrey/screenshots/客源/跟进记录-写入跟进.png

3.6.2 表单字段规范

字段名 组件类型 必填 校验规则 默认值
跟进方式 Select / Radio Pills 必选 电话
跟进目的 多选 Checkbox
跟进内容 Textarea ≥ 6 字
跟进标签 §17 Multi-select Tag Input
跟进时间 DateTime Picker 不能超过当前时间 当前时间
附件 文件上传 最多 20MB格式 bmp/jpg/png/gif
是否开放 Toggle 开放(true

:跟进方式选项(含图标):电话 / 上门 / 短信 / 微信 / 其他

3.6.3 提交行为

  • 提交方式hx-post="/clients/{{ id }}/follow-logs/create/"
  • 成功:关闭 Drawer + Toast "跟进记录已保存" + hx-trigger 刷新跟进记录 Tab 列表
  • 失败422:字段级错误
  • HTMX 属性
<form hx-post="/clients/{{ id }}/follow-logs/create/"
      hx-encoding="multipart/form-data"
      hx-target="#log-list"
      hx-swap="afterbegin"
      hx-on::after-request="if(event.detail.successful){ drawerOpen = false; showToast('跟进记录已保存') }">

3.6.4 使用的特殊组件

组件名 来源 用途
Drawer §16 抽屉容器
Multi-select Tag Input §17 跟进标签选择
Date Range Picker §9 跟进时间(单时间模式)

3.7 新增带看P0 🔴

3.7.1 触发方式

  • 触发位置:右侧面板 → "报备/常看" 按钮;或带看 Tab → 空状态"新增带看"
  • 组件类型:全页表单(字段多、含文件上传、含房源关联)
  • URL/clients/<client_id>/viewings/create/
  • 竞品截图Project/fonrey/screenshots/客源/新增带看.png

3.7.2 布局结构

┌──────────────────────────────────────────────────────────┐
│  面包屑: 客源 / 客源管理 / 新增带看                          │
│  页面标题: 新增带看                                        │
├──────────────────────────────────────────────────────────┤
│  [带看信息卡片]                                           │
│  业务类型(只读) | 带看客户(只读+查看详情链接)             │
│  经纪人(只读)                                           │
│  带看时间(日期+时间段)*                                   │
│  陪看人最多5位+ 合作带看人最多5位                    │
│  带看情况Textarea*                                    │
│  附件上传                                                 │
├──────────────────────────────────────────────────────────┤
│  [带看房源卡片]                                           │
│  添加房源至少1套最多10套*                             │
│  每套房源选择客户意向程度                                   │
├──────────────────────────────────────────────────────────┤
│  [操作按钮] 提交primary  保存secondary  取消        │
└──────────────────────────────────────────────────────────┘

3.7.3 表单字段规范

字段名 组件类型 必填 校验规则 默认值
业务类型 只读文本 二手
带看客户 只读文本 + 链接 当前客源
经纪人 只读文本 当前登录人
带看时间 Date Picker + Time Range 开始时间 < 结束时间
陪看人 员工多选搜索 ≤ 5 人
合作带看人 员工多选搜索 ≤ 5 人
带看情况 Textarea ≥ 6 字
附件 文件上传 每张 ≤ 20MBbmp/jpg/png/gif
带看房源 动态房源选择列表 1-10 套
每套意向程度 Select 是(每套) 必选

3.7.4 提交行为

  • 提交方式hx-post="/clients/{{ id }}/viewings/create/" hx-encoding="multipart/form-data"
  • 成功:跳转至带看详情页 + Toast "带看记录已提交"
  • 失败422:字段级错误,保留已填内容
  • "保存"按钮hx-post="...?draft=true",保存草稿,不跳转

3.7.5 使用的特殊组件

组件名 来源 用途
Date Range Picker §9 带看时间段选择
Dynamic Form Table §18 带看房源动态增删列表
Photo Gallery Manager §12 附件上传管理

3.8 新增预约带看P0 🔴

3.8.1 触发方式

  • 触发位置:带看 Tab → 预约子 Tab → 空状态 or "新增预约" 按钮
  • 组件类型:全页表单
  • URL/clients/<client_id>/viewings/appointment/create/
  • 竞品截图Project/fonrey/screenshots/客源/新增预约带看.png

3.8.2 表单字段规范

字段名 组件类型 必填 校验规则 默认值
业务类型 只读文本 二手
预约客户 只读文本 + 链接 当前客源
预约人 只读文本 当前登录人
预约时间(日期) Date Picker 不早于今日
预约时间(时刻) Time Picker
陪看人 员工多选 ≤ 5 人
合作带看人 员工多选 ≤ 5 人
预约房源 动态房源选择列表 1-10 套

3.8.3 提交行为

  • 提交方式hx-post="/clients/{{ id }}/viewings/appointment/create/"
  • 成功:跳转带看 Tab预约子 Tab+ Toast "预约带看已创建"
  • 失败422:字段级错误

3.8.4 使用的特殊组件

组件名 来源 用途
Date Range Picker §9 预约日期+时刻
Dynamic Form Table §18 预约房源动态列表

4. 交互状态规范

4.1 全局状态机

客源状态流转(clients.status 字段):

                  ┌──────────────────────────────┐
                  │          buying              │
                  │    (求购,可逆态)             │
                  └──────────────────────────────┘
                      │              │
               ┌──────┘              └──────┐
               ▼                           ▼
        suspended暂缓            public转公
        (可逆 → 回到 buying         (不可逆)
               │
               └──────────────────────────────────┐
                                                  ▼
                                     bought成交不可逆
                                     invalid无效不可逆
                  ┌──────────────────────────────┐
                  │          renting             │
                  │    (求租,可逆态)             │
                  └──────────────────────────────┘
                      │              │
               ┌──────┘              └──────┐
               ▼                           ▼
        suspended暂缓            public转公
               │
               └──────────────────────────────────┐
                                                  ▼
                                   rented_done成交不可逆
                                   invalid无效不可逆

UI 控制规则:

  • 右侧面板"转公客"/"转成交"/"转无效"按钮在状态已为不可逆态时 disabled + opacity-40 cursor-not-allowed
  • "改状态" Modal 中新状态 Select 选项由后端根据当前状态动态过滤

4.2 权限控制矩阵

操作 经纪人(本人) 经纪人(他人) 店长 管理员
查看需求信息 受限
编辑需求信息
写入跟进
查看跟进记录 (敏感不可见)
查看联系人明文号码 (留痕) (留痕) (留痕)
新增联系人
改等级
改状态
转公客
转成交
转无效
新增带看
编辑相关员工
查看操作日志 部分
删除跟进记录 (非敏感) (非敏感) (非敏感)

4.3 HTMX 请求规范

操作 hx-trigger hx-method + URL hx-target hx-swap Loading 行为
Tab 切换(需求信息) click GET /clients/{id}/requirements/ #tab-content innerHTML htmx-indicator 骨架屏
Tab 切换(跟进记录) click GET /clients/{id}/follow-logs/ #tab-content innerHTML 骨架屏
Tab 切换(带看) click GET /clients/{id}/viewings/ #tab-content innerHTML 骨架屏
Tab 切换(客源解读) click GET /clients/{id}/insights/ #tab-content innerHTML 骨架屏
Tab 切换(智能配房) click GET /clients/{id}/property-matches/ #tab-content innerHTML 骨架屏
跟进子 Tab 切换 click GET /clients/{id}/follow-logs/?type=written #log-list innerHTML 列表 spinner
跟进记录日期筛选 change GET /clients/{id}/follow-logs/ #log-list innerHTML 列表 spinner
跟进记录加载更多 click GET /clients/{id}/follow-logs/?page=N #log-list beforeend 按钮 spinner
带看子 Tab 切换 click GET /clients/{id}/viewings/?type=appointment #viewing-list innerHTML 列表 spinner
查看联系人号码 click POST /clients/{id}/contacts/{cid}/reveal-phone/ #phone-display-{cid} innerHTML 行内 spinner
打开改等级 Modal click GET /clients/{id}/change-grade/ #modal-container innerHTML 无(预加载)
提交改等级 submit POST /clients/{id}/change-grade/ #status-badge-row outerHTML 按钮 loading
打开改状态 Modal click GET /clients/{id}/change-status/ #modal-container innerHTML
提交改状态 submit POST /clients/{id}/change-status/ #sidebar-panel outerHTML 按钮 loading
打开转成交 Modal click GET /clients/{id}/convert-deal/ #modal-container innerHTML
提交转成交 submit POST /clients/{id}/convert-deal/ body 按钮 loading
选择成交房源搜索 click GET /properties/select-modal/?q=... #property-table-body innerHTML 表格 spinner
打开写跟进 Drawer click GET /clients/{id}/follow-logs/create/ #drawer-container innerHTML
提交写跟进 submit POST /clients/{id}/follow-logs/create/ #log-list afterbegin 按钮 loading
编辑基础信息提交 submit PATCH /clients/{id}/edit-basic/ #sidebar-panel outerHTML 按钮 loading
客源解读时段切换 click GET /clients/{id}/insights/?period=7d #insight-content innerHTML 内容区 spinner

5. 关键数据字段说明

字段名(英文) 显示名 数据类型 说明
client.id 客源 ID UUID 路由参数
client.client_no 客户编号 string 格式 {日期}{类型}{随机}
client.title 需求标题 string 如"姚叔叔置换电梯两房(上门)"
client.status 状态 enum buying/renting/suspended/public/bought/rented_done/invalid
client.grade 等级 enum A_urgent/A/B/C/D/E
client.client_type 客源类型 enum private私客/public公客/deal成交客
client.source 客户来源 string 如"线下·门店接待"
client.requirement_type 需求类型 enum second_hand/new/rent
client.property_usage 房源用途 enum residential/commercial/...
client.purchase_purpose 购房目的 multi-enum 刚需/投资/学区/改善
client.payment_method 付款方式 enum full/loan/...
client.owned_properties 名下房产 string 自由文本
client.loan_record 贷款记录 boolean 有/无
client.id_type 证件类型 enum id_card/passport/...
client.id_number 证件号码 string加密 AES 加密,展示时脱敏
client.preferred_school 意向学校 string list 可多条
client.school_start_year 入学时间 year
client.last_follow_at 最近跟进时间 datetime
client.created_at 委托日期 datetime
client.activity_level 活跃度 int Celery 每日计算
client_contacts.name 联系人姓名 string
client_contacts.phone_hash 手机号 Hash string SHA-256用于重复检测
client_contacts.phone_encrypted 手机号密文 string AES 加密,展示时解密脱敏
client_contacts.gender 性别 enum male/female
client_contacts.wechat 微信号 string
client_requirements.price_min/max 总价范围 decimal 万元
client_requirements.area_min/max 面积范围 decimal
client_requirements.rooms 居室 string
client_requirements.floor_preference 楼层偏好 multi-enum
client_requirements.preferred_districts 意向商圈 string list
client_follow_logs.log_type 跟进类型 enum written/modified/sensitive_view/other/system
client_follow_logs.follow_method 跟进方式 enum phone/door/sms/wechat/other
client_follow_logs.content 跟进内容 text
client_follow_logs.is_open 是否开放 boolean
client_viewings.viewing_time 带看时间 datetime
client_viewings.situation 带看情况 text
client_viewings.viewing_count 带看次数 label string 一看/二看/...
client_property_matches.match_type 配房类型 enum recorded/system
client_property_matches.group_label 分组标签 string 优质户型/降价/热门/新上
related_staff.role 员工角色 enum first_recorder首录人/owner归属人
related_staff.joined_at 参与时间 datetime

6. 竞品截图对应关系

截图路径 对应功能 对应文档章节 采纳的设计要点
私客详情.png 详情页整体框架 §2.1 左右双栏布局;右侧 Banner 使用品牌主色(竞品橙色改为 TealTab 导航在左侧内容区顶部
需求信息.png 需求信息 Tab 内容 §2.2 3 列字段网格;右上角蓝色"编辑"链接;空字段显示 -
跟进记录-全部.png 跟进记录时间线 §2.3 日期分组橙色圆点;条目卡片带类型 Badge"已开放"/"隐藏"小字操作链接
跟进记录-写入跟进.png 跟进子 Tab + 扩展筛选 §2.3 跟进目的多选展开/收起pill 式子 Tab 导航
跟进记录-敏感信息跟进.png 敏感信息跟进空状态 §2.3.5 空状态文案"暂无跟进"
跟进记录-修改跟进.png 修改跟进空状态 §2.3.5 同上
跟进记录-其他跟进.png 其他跟进(系统日志) §2.3 【新增私客】等系统类型条目,无操作按钮
带看.png 带看 Tab 内容 §2.4 预约/带看子 Tab带看次数橙色 Badge"详情 " 链接
新增带看.png 新增带看全页表单 §3.7 分两个卡片区域(带看信息 + 带看房源);三个操作按钮(提交/保存/取消)
新增预约带看.png 新增预约带看全页表单 §3.8 日期+时刻分两个选择器;不含"保存"按钮(预约只有提交/取消)
客源解读.png 客源解读 Tab §2.5 时段筛选 Pill活跃行为卡片三个甜甜圈图MVP 用百分比文字代替)
二手配房.png 二手配房 Tab §2.6 分组标题 + 房源卡片列表;录客/系统配房子 Tab"批量分享"次要按钮
客源信息概览.png 右侧客源信息面板 §2.7 字段列表 + 三主按钮 + 2列操作网格"展开全部"收起切换
编辑基础信息.png 编辑基础信息 Modal §3.1 宽型 Modal多字段含联动证件类型 → 证件号);意向学校可动态添加
改等级.png 改等级 Modal §3.2 极简 2 字段;max-w-sm
改状态.png 改状态 Modal §3.3 4 字段含 Textarea状态选项后端动态控制
转成交.png 转成交 Modal §3.4 Radio 选购/租、二手/新房;成交方员工标签组件
选择成交房源.png 选择成交房源宽型 Modal §3.5 搜索 + 表格 + 分页Radio 单选;"已选(0/1)"状态控制确定按钮
联系人.png 联系人面板 §2.8 号码脱敏;"查看号码"留痕确认;接通/拨打次数展示
相关员工.png 相关员工面板 §2.9 首录人/归属人标签;右侧电话图标;"编辑"仅管理员可见

7. 实现优先级与工期估算

页面/功能 优先级 特殊组件复杂度 工期估算(前端)
详情页整体框架(双栏 + Tab P0 🔴 Tab Navigation 1 天
需求信息 Tab只读 + 编辑链接) P0 🔴 0.5 天
右侧信息概览面板(字段 + 操作网格) P0 🔴 1 天
跟进记录 Tab时间线 + 子 Tab + 筛选) P0 🔴 Timeline + Date Range Picker 2 天
写入跟进 Drawer P0 🔴 Drawer + Multi-select Tag 1.5 天
带看 Tab时间线 P0 🔴 1 天
新增带看(全页表单) P0 🔴 Dynamic Form Table + 文件上传 2 天
新增预约带看(全页表单) P0 🔴 Date Picker 1 天
改等级 Modal P0 🔴 0.5 天
改状态 Modal P0 🔴 0.5 天
转成交 Modal P0 🔴 1 天
选择成交房源 Modal搜索表格 P0 🔴 Data Table + Pagination 1.5 天
联系人面板(含查看号码留痕) P0 🔴 0.5 天
相关员工面板 P0 🔴 0.5 天
编辑基础信息 Modal完整表单 P1 🟡 Multi-select 1.5 天
客源解读 Tab数据图表 P1 🟡 图表组件 2 天
二手配房 Tab房源卡片网格 P1 🟡 1.5 天
待办提醒区块 P1 🟡 0.5 天
收藏功能 P1 🟡 0.5 天
查看操作日志 P2 0.5 天
合计 P0 ~14.5 天
合计 P1 ~6 天
合计 P2 ~0.5 天

8. 开放问题(待决策)

# 问题 影响范围 待确认方
1 竞品 Banner 颜色为橙色 #f5774a,本系统使用 Teal primary-600。确认是否在客源详情页 Banner 引入橙色辅助色,还是统一用 Teal §2.7 右侧面板 Banner 产品/设计
2 跟进目的选项约 25 个,竞品截图可见,是否与 PRD 中枚举完全一致?需后端确认字段枚举值。 §2.3 跟进筛选 后端
3 "报备/常看" 按钮的业务逻辑:是打开带看表单还是单独的"报备"功能?竞品未见明确说明。 §2.7 快捷操作 产品
4 联系人"查看号码"是否需要每次查看都弹出确认对话框(hx-confirm),还是首次查看确认即可(本 session 内缓存权限)? §2.8 产品/法务
5 带看房源"意向程度"的选项枚举值是什么?竞品未完整显示。 §3.7 新增带看 后端
6 "转公客"操作是否需要理由 Textarea竞品未显示此 Modal直接 POST 还是需要二次确认弹窗? §2.7 状态变更 产品
7 客源解读 Tab 数据是否 MVP 就需要真实 AI 接口,还是 P1 阶段可先用静态假数据 + 占位图? §2.5 产品/后端
8 右侧面板"展开全部"默认折叠后显示几个字段?竞品显示约 10 个,全部展开约 14 个,需确认默认展示数量。 §2.7 基础字段列表 产品
9 二手配房"批量分享"的分享目标是什么渠道(微信/短信/链接复制)?需确认 P1 实现范围。 §2.6 产品
10 client_status_logs 是否需要在详情页"查看操作日志"入口展示?竞品有此链接但 PRD 未详细说明展示方式(独立页 or Drawer P2 操作日志 产品