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

1744 lines
78 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 客源详情页 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 功能范围
- 1.2 页面清单
- 1.3 用户角色与权限差异
2. [页面设计规范](#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-弹窗抽屉设计规范)
- 3.1 编辑基础信息Modal
- 3.2 改等级Modal
- 3.3 改状态Modal
- 3.4 转成交Modal
- 3.5 选择成交房源Modal 宽型)
- 3.6 写入跟进Drawer
- 3.7 新增带看(全页表单)
- 3.8 新增预约带看(全页表单)
4. [交互状态规范](#4-交互状态规范)
- 4.1 客源状态机
- 4.2 权限控制矩阵
- 4.3 HTMX 请求规范
5. [关键数据字段说明](#5-关键数据字段说明)
6. [竞品截图对应关系](#6-竞品截图对应关系)
7. [实现优先级与工期估算](#7-实现优先级与工期估算)
8. [开放问题(待决策)](#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`(不可点击) |
**[双栏主布局]**
```html
<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 URL`hx-target="#tab-content"`, `hx-swap="innerHTML"` |
| 懒加载 | 非默认 Tab 内容通过 `hx-trigger="click"` 首次点击时加载 |
```html
<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` 区域显示骨架屏:
```html
<!-- 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 结构:**
```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 空状态设计
若需求信息尚未录入(所有字段均为空),在内容区中部显示:
```html
<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 | 同上,与跟进目的共用展开/收起逻辑 |
```html
<!-- 筛选区 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`,不可删除 |
**[加载更多]**
```html
<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 空状态设计
```html
<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 状态
```html
<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 Picker`hx-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 空状态设计
```html
<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]**
```html
<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 空状态设计
```html
<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-700``bg-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选项满意/不满意/已跟进 |
**卡片网格布局:**
```html
<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 空状态设计
```html
<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]**
```html
<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-full`A 级 `bg-red-100 text-red-700`B 级 `bg-orange-100 text-orange-700`C 级 `bg-yellow-100 text-yellow-700`D/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` | `-` |
**字段行渲染:**
```html
<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>
```
**[展开全部链接]**
```html
<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]**
```html
<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]**
```html
<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]**
```html
<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 |
**[联系人条目]**
```html
<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>
```
**查看号码交互:**
```html
<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 区域详细规范
```html
<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 属性**
```html
<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 "等级已更新" + 刷新右侧状态标签行
- **失败422**Select 下方显示错误文字
- **HTMX 属性**
```html
<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 属性**
```html
<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 | 是 | 正数,单位万元 | 无 |
| 成交方 | 员工标签(可删除,可搜索添加) | 是 | 至少一人 | 当前归属人 |
**成交房源选择:**
```html
<!-- 点击触发 "选择成交房源" 宽型 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` 二次确认
```html
<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` 状态,选中后激活"确定"按钮
```html
<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 属性**
```html
<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 操作日志 | 产品 |