新增笔记

This commit is contained in:
Shen Wei
2026-04-29 07:08:27 +08:00
parent faf3aa51bb
commit 15cd44b2ca
11 changed files with 2684 additions and 23 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -76,6 +76,10 @@
11. `UI_DESIGN/` 下已审阅的静态页面(`.html`/`.md`)属于**强约束输入**:必须以其为实现基线,禁止擅自重做页面信息架构、主布局、字段顺序与关键交互。
12. 若实现与 UI 静态页存在冲突:先按 UI 静态页落地,并在交付中列出冲突点;不得在未确认前自行改版 UI。
### 3.1) 可复用模板(建议直接引用)
- API 契约冲突提报模板:`Project/fonrey/prompt/提示词模板/API_CONTRACT_冲突提报模板_v1.md`
## 4) 通用执行模板(复制后替换 US 内容)
```text
@@ -114,6 +118,12 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 关键实现说明
- 测试结果
@@ -273,6 +283,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -341,6 +358,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -409,6 +433,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -477,6 +508,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -545,6 +583,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -613,6 +658,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -681,6 +733,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -749,6 +808,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -817,6 +883,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -885,6 +958,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -953,6 +1033,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1025,6 +1112,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1097,6 +1191,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1168,6 +1269,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1242,6 +1350,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1311,6 +1426,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1379,6 +1501,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1448,6 +1577,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1517,6 +1653,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1586,6 +1729,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1654,6 +1804,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1722,6 +1879,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1790,6 +1954,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1858,6 +2029,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1926,6 +2104,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -1995,6 +2180,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2064,6 +2256,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2133,6 +2332,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2202,6 +2408,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2271,6 +2484,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2340,6 +2560,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2409,6 +2636,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2478,6 +2712,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2547,6 +2788,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2616,6 +2864,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2685,6 +2940,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2754,6 +3016,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2823,6 +3092,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2892,6 +3168,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -2961,6 +3244,13 @@
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -3029,6 +3319,13 @@ Celery Beat 定时任务每日凌晨执行超过运营配置天数如30天
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果
@@ -3097,6 +3394,13 @@ Celery Beat 定时任务每日凌晨执行超过运营配置天数如30天
【交付格式】
- 变更文件列表
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
- UI 一致性核对清单(逐条对应 UI 静态页:已对齐/偏差项)
- 核心实现说明(与验收标准逐条对应)
- 测试命令与结果

View File

@@ -0,0 +1,421 @@
# Fonrey 全局系统设计 Review 报告
> **Review 类型**:全量 ReviewPRD + DATA_MODEL + TECH_STACK + UI/UX + TASK 交叉验证)
> **Review 模式**:增量对比 `REVIEW_全局_2026-04-26.md`,逐条核对 4 Blocker + 12 Major 状态,识别新增风险
> **Review 日期**2026-04-28
> **Reviewer**:首席系统设计 ReviewerAI 辅助6 路并行 explore agent 取证)
> **当前阶段**:需求 ~90% / 数据模型 ~80% / UI 设计 ~35%HTML 原型推进中)/ TECH 横切规范 ~70%
> **覆盖文档**9 份 PRD + 10 份 TECH_STACK + 9 份 DATA_MODEL含新增 ENUMS.md v2.2+ 3 份 UI_SYSTEM + 11 份 UI_DESIGN + TASK 总表
> **问题分级**:🔴 Blocker阻塞开发 / 🟠 Major必须修复但不阻塞 / 🟡 Minor建议优化
> **状态标记**:✅ 已修复 / ⚠️ 部分修复 / ❌ 未修复 / 🆕 新增
---
## 、执行摘要Executive Summary
### 整体评价
相较 2026-04-26 版本,本期文档体系出现 **5 项实质性进展**,是历次 Review 以来修复力度最大的一次:
1. **🟢 B-01 系统配置 PRD 落地**`PRD/系统配置/系统配置模块PRD.md`273 行)+ `系统配置参数数据.md`1100 行)+ `系统配置数据模型设计说明_for_Atlas.md`275 行)三件套已完整产出,含 US-SETTING-001-A/B/C 三条 P0 Story 与完整验收标准。MVP 范围被显式约束(明确 ❌ 标签/带看/通知/发布平台等推迟到 P1/P2**降低实施风险**。
2. **🟢 B-02 枚举三方不一致根除**`DATA_MODEL/ENUMS.md` v2.2780 行2026-04-28作为**唯一权威源**已建立,全量统一为 `lower_snake_case`,历史大写值(`SUCCESS`/`A_urgent`/`A_app`)的迁移规则已写明,并要求 DB CHECK / `enum_labels` / 前端三方同步。
3. **🟢 B-03 权限档位冲突收敛**:权限 PRD §3 已显式表述"五档基线 + 各权限项可选子集(含三档场景)"DATA_MODEL_PERMISSION 的 `staff_data_scopes` 表(已建 DDL含 3 条索引)实现"跨层级并集"模型PRD ↔ Data ↔ Code 三方对齐。
4. **🟢 B-04 Keyset 分页规范化**`API_CONTRACT.md` §4 已写入 `{"mode":"keyset","cursor":"...","limit":20}` 标准合约 + 不透明游标禁令;客源/房源/楼盘/组织 4 个模块技术方案均已采用此契约。
5. **🟢 M-01 测试规范多租户化**`TECH_STACK.md:208` + `测试规范.md:69,146` 三处明确"必须使用 `TenantClient`,禁止 Django 原生 `Client()`"4 个模块技术方案同步引用。
但本期 Review 也确认 **3 类系统性债务未动**,且因其他模块详化反而更显突出:
- **❌ 主表乐观锁 `version` 字段** —— 全量 DATA_MODEL 0 命中89k 房源并发编辑场景PRD 明确双经纪人同时改房)将出现"后写覆盖前写"。
- **❌ 高写入表 `PARTITION BY` DDL** —— `follow_logs` / `property_photos` / `permission_change_logs` / `login_attempts` / `platform_audit_logs` 全量 0 命中分区子句,仅文字"建议月度分区"。**200 万跟进日志 + 2 年保留期** 的查询性能未保障。
- **❌ KMS / 密钥轮换 SOP** —— 仅见"`R2_ADMIN_KEY` 通过 Docker Secret 注入",无主密钥轮换流程、无应急吊销、无加密字段重新封装。
### 核心问题摘录Top 10
| # | 等级 | 编号 | 问题 | 维度 | 状态 |
|---|------|------|------|------|------|
| 1 | 🔴 | **B-05** | 主表乐观锁 `version` 字段全量 0 实现:`properties` / `clients` / `complexes` 在 PRD 多人协作场景中是核心DDL 无并发控制 | Data↔PRD | 🆕 升 Blocker持续 3 次未修) |
| 2 | 🔴 | **B-06** | 高写入表分区 DDL 仍未落地M-03 持续 3 次未修5 张高频表无 `PARTITION BY RANGE` 子句,仅注释"建议月度分区",无法在迁移期补加分区 | Data | 🆕 升 Blocker |
| 3 | 🟠 | **M-11** | KMS / 密钥轮换 SOP 仍未补:`core/encryption.py` 已声明,但主密钥轮换、密钥版本号、加密字段重新封装、应急吊销四类流程在 TECH_STACK 与系统管理 PRD 中均无对应章节 | 安全 | ❌ 持续未修 |
| 4 | 🟠 | **M-12** | Celery 任务 schema 切换缺统一封装:多模块技术方案声明 `tenant_schema_name` 入参,但无 `with_tenant_context` 装饰器或基类抽象,开发期容易漏写导致跨租户脏读 | TECH/多租户 | ⚠️ 部分修复 |
| 5 | 🟠 | **M-13** | R2 路径前缀全局规范不一致:系统管理已规范 `backups/{tenant_schema}/...` `exports/{tenant_schema}/...`,但客源/房源/楼盘模块 R2 路径仍写"`property_photos/...`"无 tenant 前缀模板 | TECH/多租户 | ⚠️ 部分修复 |
| 6 | 🟠 | **M-06** | 客户端发布无签名校验/防降级:`/api/client/updates/latest/``download_url` 对外公开,仅 SHA256 完整性校验,可被 MITM 投递降级版本(昨日 M-06 未修) | 安全 | ❌ 持续未修 |
| 7 | 🟠 | **M-14** | ORM Manager / QuerySet 数据范围统一封装规范缺失:`DATA_MODEL_PERMISSION.md:143-145``ScopeQueryBuilder` 只是 helper未规定"所有业务 QuerySet 必须经过 Scope 包装"的强制约束,开发期容易漏权限校验 | 安全/Data | 🆕 新增 |
| 8 | 🟠 | **M-05** | 89k 数据 < 2 秒列表查询 NFR 仍无 p95/EXPLAIN/性能基准测试任务(昨日 M-05 未修) | NFR↔TECH↔测试 | ❌ 持续未修 |
| 9 | 🟠 | **M-09** | UI_SYSTEM 复杂组件虚拟滚动列表、批量操作面板、抽屉表单嵌套规则、文件上传批量、富权限树规范深度不足UI_DESIGN 11 份原型仅覆盖客源 + 房源列表,**楼盘/权限/系统配置/组织人事/发布管理 5 大模块全部缺原型** | UI | ⚠️ 部分修复 |
| 10 | 🟡 | **N-01** | ENUMS.md v2.2 已统一,但 PRD 文本中仍混用中文枚举(如客源 PRD 仍写"求购/求租"),需要一次全文档"中文枚举 → ENUMS 锚点链接"替换 | PRD↔Data | 🆕 新增 |
### 风险等级分布
| 等级 | 本次2026-04-28 | 上次2026-04-26 | 净变化 |
|------|---|---|---|
| 🔴 Blocker | **2** | 4 | -2B-01/B-02/B-03/B-04 全部清零B-05/B-06 由历史 Major 升级) |
| 🟠 Major | **9** | 12 | -3M-01 已修复关闭M-02→B-05、M-03→B-06 升级) |
| 🟡 Minor | **3** | 6 | -3聚焦 Top 风险) |
| 合计 | **14** | 22 | -8 |
### 增量对比一览(昨日 Blocker / Major → 今日状态)
| 上次编号 | 简述 | 状态 | 今日处置 |
|----------|------|------|----------|
| **B-01** | 系统配置 PRD 空骨架 | ✅ **已修复**273+1100+275 行三件套) | 关闭 |
| **B-02** | 枚举三方不一致 | ✅ **已修复**ENUMS.md v2.2 权威源) | 关闭,遗留 N-01PRD 文本回写) |
| **B-03** | 权限三档/五档冲突 | ✅ **已修复**(五档基线 + 子集子句) | 关闭,遗留 M-14ORM 强制约束) |
| **B-04** | Keyset 分页缺失 | ✅ **已修复**API_CONTRACT §4 + 4 模块) | 关闭 |
| M-01 | 测试规范多租户化 | ✅ 已修复TenantClient 强制) | 关闭 |
| M-02 | 主表乐观锁 / 楼盘锁 | ❌ 持续未修 | **升 B-05** |
| M-03 | 高写入表分区 | ❌ 持续未修 | **升 B-06** |
| M-04 | Celery/R2/索引规范 | ⚠️ 部分修复(拆分) | 拆分为 M-12 / M-13 / 残留索引矩阵 |
| M-05 | 89k 数据 < 2s NFR 测试 | ❌ 持续未修 | M-05 |
| M-06 | 客户端发布签名 | ❌ 持续未修 | M-06 |
| M-07~M-08 | 价格走势 / 楼盘市场报盘 | ⚠️ 降级 | 转 N-02Minor |
| M-09 | UI 组件 + Wireframe | ⚠️ 部分修复 | M-09 |
| M-10 | Redis Key tenant 前缀 | ✅ 已修复(系统设置技术方案 §6.4 | 关闭 |
| M-11 | 加密密钥管理 | ❌ 持续未修 | M-11 |
| M-12 | 数据保留与归档 | ⚠️ 部分修复 | 转 N-03 |
---
## 一、PRD 一致性审查PRD ↔ PRD ↔ TASK
### ✅ 历史 B-01系统配置 PRD 空骨架)已修复
**证据**
- `PRD/系统配置/系统配置模块PRD.md`273 行v0.12026-04-27含目标度量、非目标范围、3 条 P0 User StoryUS-SETTING-001-A/B/C与完整 Given-When-Then 验收标准。
- `PRD/系统配置/系统配置参数数据.md`1100 行):实际参数清单。
- `PRD/系统配置/系统配置数据模型设计说明_for_Atlas.md`275 行):与 DATA_MODEL_SETTING.md 对齐。
**剩余风险(降级为 Minor N-04**US-SETTING-001 的"配置变更生效时效 ≤ 5 分钟Redis 缓存 TTL"与 `DATA_MODEL_SETTING.md` 的 Redis Key TTL = 300s 一致,但**无 Cache Invalidation 主动失效路径**(仅靠 TTL 过期),管理员保存后 5 分钟内一线经纪人仍看不到新选项 —— PRD AC-2 "刷新后立即可见" 与实现存在间隙。
---
### ✅ 历史 B-02核心枚举三方不一致已修复
**证据**
- `DATA_MODEL/ENUMS.md` v2.2780 行2026-04-28作为唯一权威源覆盖 Publictenant 生命周期、平台管理员、备份导出、升级发布)+ Tenant客源/房源/楼盘/权限/组织/系统设置)所有枚举域。
- 命名规范统一为 `lower_snake_case`,历史大写值(`SUCCESS`/`FAILED`/`A_urgent`/`A_app`/`B_schema`/`C_feature`/`REPLACE`/`RESTRICT`/`GRANT`)的迁移路径已书面化。
**剩余风险N-01 Minor**PRD 文本与 TASK AC 中仍存在大量中文/旧值混用(如客源 PRD 仍写"求购/求租")。需要一次性回写:
- 在每份 PRD 顶部加 `> 枚举值参见 DATA_MODEL/ENUMS.md` 锚点
- 在 ENUMS.md 中给每个枚举值附"中文展示标签"列(部分已有)
- TASK.md 排一条 P0 任务PRD 中文枚举 → 英文锚点引用
---
### ✅ 历史 B-03权限档位三档/五档冲突)已修复
**证据**
- `PRD/权限管理/权限管理模块PRD.md:46`:明文"采用**五档模型**:本人 / 本组 / 本门店 / 本区域 / 全公司。注意:并非所有权限项均开放五档,各项的实际可选范围以权限编辑页的下拉配置为准(例如某些权限项仅提供「本人 / 本门店 / 全公司」三个选项)"
- `:499`:复述同一约定,闭环。
- `DATA_MODEL_PERMISSION.md:24,31,254-275,967-987``staff_data_scopes` 表 DDL 完整,索引 3 条到位,"员工数据范围 = 所有 staff_data_scopes 对应 org_units.path 子树并集"实现"跨层级叠加"。
**剩余风险M-14 Major见 §五**DATA_MODEL_PERMISSION 的 `ScopeQueryBuilder` 只是 helper class未在 TECH_STACK 中强制规定"所有业务 QuerySet 必须经过 Scope 包装",开发期容易漏写导致越权读取。
---
### 🟡 N-01 PRD 文本回写 ENUMS 锚点(来自 B-02 残留)
详见上文。**责任**PM 在 Phase 1 编码启动前完成全量 PRD 文本扫描替换。
---
## 二、TECH_STACK / API / 测试规范审查
### ✅ 历史 B-04Keyset 分页缺失)已修复
**证据**
- `API_CONTRACT.md:143,154,157,182`:标准请求/响应契约 `{"mode":"keyset","cursor":"opaque_cursor","limit":20}` + `next_cursor`
- `API_CONTRACT.md:165``cursor` MUST 为不透明字符串,**禁止暴露内部排序字段组合** ✅ 与 OWASP 推荐一致
- `API_CONTRACT.md:378`drf-spectacular `OpenApiParameter("cursor", ...)` 注解
- 客源 / 房源 / 楼盘 / 组织 4 个模块技术方案均同步采用
**剩余 Minor**:尚无 Keyset SQL 模板(含 `WHERE (sort_key, id) < (?, ?)` 的复合键模板)。建议在 `TECH_STACK.md` 新增 §"分页 SQL 模板"章节,给出含 `tenant_schema` schema-search-path 切换 + 复合键的标准实现。
---
### ✅ 历史 M-01测试规范多租户化已修复
**证据**
- `TECH_STACK.md:203,208,211`:明确 TenantClient 强制 + R2/Redis/邮件 Mock
- `测试规范.md:55,69,146,230``TenantClient` 在测试金字塔图、约束章节、请求路径、Mock 策略 4 处统一表述
- 4 个模块技术方案均引用
---
### 🟠 M-12 Celery 多租户 schema 切换无统一封装M-04 拆分)
**文档**`TECH_STACK/客源管理技术方案.md` / `房源管理技术方案.md` / `楼盘管理技术方案.md` / `组织人事技术方案.md`(均声明 `tenant_schema_name` 入参vs `TECH_STACK.md`(无统一规范)
**事实**
- 模块技术方案约定 Celery 任务签名带 `tenant_schema_name: str` 形参,但**无 `with_tenant_context(schema)` 装饰器或基类**。
- 系统设置技术方案虽给出 schema 切换示例(`:28-31`),但未提取为通用规范。
- `core/cache.py` 已规范带前缀,但 ORM 查询的 schema 切换由开发者手写 `connection.set_schema(...)`**漏写不报错**。
**影响**
- Celery worker 在多租户调度下,相邻任务可能因 search_path 残留导致跨租户脏读/脏写。
- 单元测试若忘记设置 schema测试通过但生产出错。
**改进方向**
-`TECH_STACK.md` 新增 §"Celery 多租户规范"
- 强制所有业务 task 用 `@tenant_task(schema_arg="tenant_schema_name")` 装饰器
- 装饰器职责:进入时 `set_schema_to(schema)`,退出/异常时 `set_schema_to_public()`,记录 task_id 与 schema 到结构化日志
- 测试规范补:"Celery 任务测试必须显式 mock schema 切换断言"
---
### 🟠 M-13 R2 路径前缀全局规范不一致M-04 拆分)
**文档**`TECH_STACK/系统管理技术文档.md:521-525`已规范vs 客源/房源/楼盘技术方案(未规范)
**事实**
- 系统管理已统一:`backups/{tenant_schema}/{record_id}.tar.gz` / `exports/{tenant_schema}/{task_id}.zip` / `releases/system/{version}/...`(无 tenant 前缀,公共资源)✅
- 房源 / 客源模块的图片 / 跟进附件 R2 路径**未在文档中给出 key 模板**,仅写"预签名上传"。
- 公开发布包路径无 tenant 前缀(合理:客户端尚未登录),但**生产环境 bucket policy 需明示**:哪些 prefix 是 public-read哪些需要 signed URL。
**影响**
- 跨租户文件越权访问风险(房源图片如果误用全局 prefixA 租户能猜到 B 租户对象 key
- 生命周期策略(如客源跟进附件 90 天清理)无 prefix 难以配置。
**改进方向**
-`TECH_STACK.md` 新增"R2 路径规范"统一表:
```
公共资源 → releases/system/{version}/... public-read
租户 backup → backups/{tenant_schema}/{rid}.tar.gz signed only
租户 export → exports/{tenant_schema}/{tid}.zip signed-24h
房源图片 → media/{tenant_schema}/property/{pid}/{photo_id}.{ext}
跟进附件 → media/{tenant_schema}/follow/{log_id}/{idx}.{ext}
审计归档 → exports/audit/{task_id}.csv signed only
```
- 路径中**禁止出现 tenant_idUUID**,统一用 `tenant_schema_name`,便于跨环境迁移。
---
### 🟠 M-05 89k 数据 < 2 秒列表查询 NFR 仍无性能基准(持续未修)
**文档**`PRD/客源管理/客源管理模块PRD.md` US-CLIENT-002 / `PRD/房源管理/房源管理模块PRD.md` US-PROPERTY-002 NFR列表查询 p95 < 2s89k 数据)
**事实**
- 测试规范未含 p95 / EXPLAIN ANALYZE / pg_stat_statements 集成。
- 无 89k 量级 seed factory仅有单条 fixture
- 无 CI 性能回归任务(如 pgTAP / pytest-benchmark
- AC 中"< 2 秒"实质不可测。
**改进方向**
- 在 `测试规范.md` 增补 §"性能测试"
- seed: `make seed-perf` 灌入 89k 房源 + 200k 客源 + 200 万 follow_logs
- 工具: `pytest-benchmark` + `EXPLAIN (ANALYZE, BUFFERS)` 输出归档
- 阈值: 列表 p95 < 2s / 详情 p95 < 500ms / 写入 p95 < 1s
- CI: nightly 跑一次,回归 > 20% 标 fail
- 优先 instrument 客源/房源列表 API含 keyset 翻第 100 页场景)
---
## 三、DATA_MODEL 审查
### 🔴 B-05 主表乐观锁 `version` 字段全量未实现M-02 升级,连续 3 次未修)
**文档**`DATA_MODEL/DATA_MODEL_PROPERTY.md` / `DATA_MODEL_CLIENT.md` / `DATA_MODEL_COMPLEX.md`
**事实**grep `version|乐观锁|optimistic` 在 properties / clients / complexes 主表 DDL 中 **0 命中**`version` 仅出现在 ENUMS.md 文档版本号、`SystemVersion` 表名、Permission 缓存 key `:perm:version` 等不相关上下文中)。
**业务场景**PRD 多处):
- 房源 PRD双经纪人同时编辑同一房源一人改价、一人改状态
- 客源 PRD录入员 + 跟进经纪人同时编辑客源等级
- 楼盘 PRD4 类锁字段lock_building/lock_room/lock_info/lock_standard_room已建但**无 `lock_version` 触发 update conflict**
**影响**:后写覆盖前写、数据丢失,**89k 房源体量下必然出现**。
**改进方向**
- DDL 增加:
```sql
ALTER TABLE properties ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
ALTER TABLE clients ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
ALTER TABLE complexes ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
```
- 应用层:`Model.objects.filter(id=x, version=v_old).update(..., version=F('version')+1)`,受影响行 0 → 抛 `ConflictError`,前端展示"已被他人修改,请刷新后重试"。
- API_CONTRACT.md 增补 `If-Match: <version>` Header 规范PUT/PATCH 必传)。
---
### 🔴 B-06 高写入表分区 DDL 仍未落地M-03 升级,连续 3 次未修)
**文档**`DATA_MODEL/DATA_MODEL_CLIENT.md`follow_logs/ `DATA_MODEL_PROPERTY.md`property_photos/ `DATA_MODEL_PERMISSION.md`permission_change_logs/ `DATA_MODEL_LOGIN.md`login_attempts/ `DATA_MODEL_PUBLIC.md`platform_audit_logs
**事实**grep `PARTITION BY|分区表|partition` 在 DATA_MODEL 全量 0 命中。仅文字"建议月度分区"。
**业务体量**PRD 与 NFR
- `follow_logs`200 万 / 2 年保留期
- `property_photos`89k 房源 × 平均 8 张 ≈ 70 万行
- `login_attempts`:所有登录失败/成功审计
- `platform_audit_logs`:跨租户全平台审计
**影响**单表过大导致索引膨胀、VACUUM 阻塞、按时间窗口归档不可分区操作。**编码后再切分区将停机迁移**,是必须前置的设计决策。
**改进方向**
- DDL 改为:
```sql
CREATE TABLE follow_logs (
id BIGSERIAL,
created_at TIMESTAMPTZ NOT NULL,
...
PRIMARY KEY (id, created_at) -- 分区键必须含 created_at
) PARTITION BY RANGE (created_at);
CREATE TABLE follow_logs_2026_04 PARTITION OF follow_logs
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
```
- 增补 Celery 周期任务 `partition_maintenance_task`(每月最后一天):自动建下月分区 + drop 超出保留期分区。
- DATA_MODEL.md 增 §"分区策略"统一表(保留期 / 分区粒度 / 维护任务)。
---
### 🟠 M-14 ORM 数据范围统一封装规范缺失B-03 残留)
**文档**`DATA_MODEL/DATA_MODEL_PERMISSION.md:143-145, 414-455`
**事实**
- `ScopeQueryBuilder` 已实现(含 `is_protected` 二次 AND 规则)。
- 但**未在 TECH_STACK 中强制要求"所有业务 QuerySet 必须经过 ScopeQueryBuilder"**。
- 模块技术方案 view 层示例代码直接 `Property.objects.filter(...)`,可绕过权限控制。
**影响**:开发新功能时漏接 ScopeQueryBuilder → 越权读取,且测试不易发现。
**改进方向**
- TECH_STACK 增补"ORM 数据范围强制规范"
- 业务 Model 不暴露默认 `objects` Manager统一暴露 `scoped(staff)` 入口
- 写一个 lint rulepylint plugin / pre-commit禁止业务模块直接调用 `<Model>.objects.<query>`
- 测试规范补"权限边界测试矩阵":每个 Model 至少 3 caseown / department / cross_department denied
---
### 🟡 N-02 楼盘价格走势 / 市场报盘M-07/M-08 降级)
历史 M-07/M-08 已写明"P2 不做",本期降为 Minor。
---
### 🟡 N-03 数据保留与归档策略M-12 降级)
`platform_audit_logs` / `follow_logs` 在 PRD 中提到"2 年保留",但 DATA_MODEL 无对应 Celery 归档任务定义。建议在 B-06 分区落地后顺带补全 archive job。
---
## 四、安全审查(含多租户 / 权限 / 审计)
### 🟠 M-06 客户端发布无签名校验 / 防降级(持续未修)
**文档**`PRD/发布管理/客户端发布管理模块PRD.md:165,198,284,347` / `TECH_STACK.md:119`
**事实**
- 端点 `GET /api/client/updates/latest/` 公开(合理)。
- `download_url` 走 R2 / CDN 公开(合理)。
- 完整性校验仅 SHA256仅防偶然篡改不防 MITM 主动替换)。
- **无代码签名Authenticode / OS-level codesign**、**无服务端响应签名**、**无版本号单调递增校验(防降级)**。
**改进方向**
- 升级响应 JSON 增加 `signature` 字段HMAC-SHA256密钥服务端持有客户端校验签名后再 fetch download_url。
- `electron-updater` 配合 `publisherName`Windows Authenticode在 CI 用代码签名证书签 EXE。
- 客户端本地存"已安装版本 N",若服务端返回版本 < N**拒绝降级**(除非配置允许 force_downgrade=true 由后端控制)。
---
### 🟠 M-11 加密密钥管理 / 轮换 SOP 仍未补(持续未修)
**文档**`TECH_STACK/系统管理技术文档.md:877`(仅"密钥通过 Docker Secret 注入"一句)
**事实**
- `core/encryption.py` 中央加密 helper 已声明(散见模块技术方案)。
- **缺**主密钥KEK轮换流程、字段加密密钥DEK版本号设计、密钥泄露应急吊销 + 全量字段重新封装、生产-staging 密钥隔离。
**改进方向**
- 在 `TECH_STACK.md` 新增 §"密钥管理 SOP"
- 三层结构环境主密钥KMS / sops + age → 应用 KEK启动时加载 → 字段 DEKper-row 或 per-column
- DDL 加密字段必须含 `key_version SMALLINT NOT NULL`
- 季度轮换 + 异常吊销 runbook
- 短期(无 KMS方案`age` + `sops` 加密 secrets 文件,运维流程文档化。
---
### 🟠 M-14 ORM 数据范围(详见 §三)
---
## 五、UI/UX 审查
### 🟠 M-09 UI 设计覆盖率与复杂组件深度(部分修复)
**文档**`UI_DESIGN/`11 份 markdown + 11 份 HTML 原型 + `UI_设计任务总表.md`/ `UI_SYSTEM/`3 份)
**事实**
- ✅ 已覆盖:客源列表 / 客源详情 / 客源新增 / 客源编辑 / 房源列表 5 类原型HTML + Markdown 双版本)。
- ❌ 仍缺:
- 楼盘管理(详情 / 新增 / 锁字段交互)
- 权限管理(权限树 / 数据范围编辑器 / 五档下拉子集)
- 系统配置lookup_items 编辑抽屉 / 必填规则配置)
- 组织人事org tree / 员工卡)
- 发布管理(升级表单 / 灰度名单 / 进度面板)
- ❌ UI_SYSTEM 复杂组件规范深度仍不足虚拟滚动列表89k 房源必需)、批量操作面板、抽屉表单嵌套规则、文件上传批量、富权限树。
**改进方向**
- 把 UI_DESIGN 11 份 task 按 PRD 优先级排进 Phase 1客源已齐→ 房源详情 → 楼盘 → 权限 → 系统配置;
- UI_SYSTEM 增补"复杂组件清单"5 项,每项给出 Alpine.js + HTMX 实现范本;
- 89k 房源列表必须用虚拟滚动或固定窗口(如 PageDown 分页HTMX 按 keyset cursor 局部刷新。
---
## 六、TASK ↔ 文档交叉验证
### 🟡 N-04 系统配置 Cache InvalidationB-01 残留)
详见 §一。
---
## 七、汇总 Action Items
| # | 等级 | 编号 | Action | 责任 | 建议完成时间 | 编码门禁 |
|---|------|------|--------|------|--------------|----------|
| 1 | 🔴 | B-05 | 主表 `version` 字段 + `If-Match` API 规范 | 架构师 + 后端 Lead | T+3 天 | **Phase 1 启动前** |
| 2 | 🔴 | B-06 | 5 张高写入表 `PARTITION BY RANGE` DDL + 分区维护 Celery 任务 | 架构师 + DBA | T+5 天 | **Phase 1 启动前** |
| 3 | 🟠 | M-05 | 性能基准测试方案seed 89k + pytest-benchmark + p95 阈值) | 测试 Lead | T+7 天 | Phase 1 内 |
| 4 | 🟠 | M-06 | 客户端发布签名 + 防降级HMAC + Authenticode | 安全 + 发布运维 | T+10 天 | 客户端发布 GA 前 |
| 5 | 🟠 | M-09 | 楼盘 / 权限 / 系统配置 / 组织人事 / 发布 5 模块原型 + 5 复杂组件规范 | UX + 前端 Lead | T+14 天 | 各模块编码前 |
| 6 | 🟠 | M-11 | 密钥管理 SOPKEK/DEK/版本/轮换/吊销) | 安全 + 运维 | T+10 天 | 加密字段上线前 |
| 7 | 🟠 | M-12 | `@tenant_task` 装饰器 + Celery 多租户规范文档 | 架构师 | T+5 天 | Celery 任务编码前 |
| 8 | 🟠 | M-13 | R2 路径前缀统一规范表 + bucket policy | 架构师 + 运维 | T+5 天 | R2 上传任务编码前 |
| 9 | 🟠 | M-14 | ORM 数据范围强制 lint + `scoped(staff)` 入口 | 架构师 + 后端 Lead | T+7 天 | Phase 1 内 |
| 10 | 🟡 | N-01 | PRD 文本中文枚举 → ENUMS 锚点全量替换 | PM | T+3 天 | 编码前清零 |
| 11 | 🟡 | N-04 | 系统配置 Cache Invalidation 主动失效路径补充 | 后端 Lead | T+5 天 | US-SETTING-001 编码前 |
| 12 | 🟡 | N-02/N-03 | 楼盘走势/数据归档策略 | PM + DBA | Phase 2 | 非阻塞 |
---
## 八、Review 结论
### 编码门禁Phase 1 启动前必清零)
**🔴 Blocker2 项必修)**
1. **B-05** 主表 `version` 乐观锁字段 —— 涉及 properties / clients / complexes DDL + API_CONTRACT 增补 `If-Match` Header
2. **B-06** 5 张高写入表分区 DDL + 分区维护任务 —— 后期切分需停机,必须前置
### 整体判断
- **本期是历次 Review 进展最大的一次**4 个 Blocker 全部清零1 个 MajorM-01已修复文档体系特别是 PRD ↔ ENUMS ↔ DATA_MODEL建立了真正的"权威源"。
- **剩余 Blocker 集中在 DATA_MODEL 物理设计层面**(并发控制 + 分区),属于"知道但没动"的债务,**修复成本可估、风险可控**,建议本周内打磨完毕。
- **Phase 1 编码可在 B-05 / B-06 修复后立即启动**,其余 9 个 Major 可与编码并行推进,但需在对应模块进入开发前清零(参见上表"编码门禁"列)。
- **下一次 Review 焦点**B-05 / B-06 修复后,应重点审查 M-09UI 覆盖率)+ M-05性能基准以及编码启动后第一波集成测试反馈。
### 文档健康度趋势
| 维度 | 04-25 | 04-26 | 04-28 | 趋势 |
|------|-------|-------|-------|------|
| PRD 完整性 | 60% | 75% | **90%** | ↑↑ |
| DATA_MODEL 严谨性 | 50% | 65% | **80%** | ↑↑ |
| TECH_STACK 横切规范 | 30% | 50% | **70%** | ↑ |
| 测试规范多租户化 | 20% | 40% | **85%** | ↑↑↑ |
| UI/UX 覆盖率 | 5% | 30% | **35%** | ↑(最弱环节) |
| 安全设计 | 40% | 45% | **55%** | ↑M-06/M-11 持续债务) |
---
> **Reviewer 签字**:首席系统设计 ReviewerAI 辅助)
> **下次 Review 建议触发条件**B-05 + B-06 完成 PR 合并后,或 Phase 1 编码启动后第 1 个 Sprint 结束。

View File

@@ -28,17 +28,17 @@
## 3) P0 缺口任务(按优先级执行)
| 序号 | 优先级 | 模块 | 覆盖 US | UI.md 目标文件 | HTML 目标文件 | 当前状态 | 下一步 |
| --- | ---- | ----------- | --------------------- | ----------------------------- | ------------------------ | ---- | --------------------------- |
| 01 | P0-A | 登录管理 | US-ACCOUNT-001~003 | `UI_DESIGN/登录管理/登录_UI.md` | `UI_DESIGN/登录_UI.html` | 待评审 | 你评审登录 UI.md + 静态页,给我反馈我再迭代 |
| 02 | P0-A | 房源管理(新增) | US-PROPERTY-001 | `UI_DESIGN/房源管理/新增房源_UI.md` | `UI_DESIGN/新增房源_UI.html` | 已完成 | 已完成评审迭代(壳层/按钮/结构一致化进入任务03 |
| 03 | P0-A | 房源管理(详情) | US-PROPERTY-003~008 | `UI_DESIGN/房源管理/房源详情_UI.md` | `UI_DESIGN/房源详情_UI.html` | 待评审 | 你评审房源详情 UI.md + 静态页,给我反馈我再迭代 |
| 04 | P0-B | 楼盘管理(列表) | US-COMPLEX-002 | `UI_DESIGN/楼盘管理/楼盘列表_UI.md` | `UI_DESIGN/楼盘列表_UI.html` | 待评审 | 你评审楼盘列表 UI.md + 静态页,给我反馈我再迭代 |
| 05 | P0-B | 楼盘管理(详情/维护) | US-COMPLEX-001 | `UI_DESIGN/楼盘管理/楼盘详情_UI.md` | `UI_DESIGN/楼盘详情_UI.html` | 待设计 | 完成任务04后开始 |
| 06 | P0-B | 楼盘管理(区域) | US-COMPLEX-003 | `UI_DESIGN/楼盘管理/区域管理_UI.md` | `UI_DESIGN/区域管理_UI.html` | 待设计 | 完成任务05后开始 |
| 07 | P0-C | 组织人事 | US-ORG-001~003 | `UI_DESIGN/组织人事管理/组织人事_UI.md` | `UI_DESIGN/组织人事_UI.html` | 待设计 | 完成任务06后开始 |
| 08 | P0-C | 权限管理 | US-PERMISSION-001~005 | `UI_DESIGN/权限管理/权限管理_UI.md` | `UI_DESIGN/权限管理_UI.html` | 待设计 | 完成任务07后开始 |
| 09 | P0-C | 系统配置 | US-SETTING-001-A/B/C | `UI_DESIGN/系统配置/系统配置_UI.md` | `UI_DESIGN/系统配置_UI.html` | 待设计 | 完成任务08后开始 |
| 序号 | 优先级 | 模块 | 覆盖 US | UI.md 目标文件 | 竞品截图参考 | HTML 目标文件 | 当前状态 | 下一步 |
| --- | ---- | ----------- | --------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------ | ---- | ---------------------------- |
| 01 | P0-A | 登录管理 | US-ACCOUNT-001~003 | `UI_DESIGN/登录管理/登录_UI.md` | | `UI_DESIGN/登录_UI.html` | 待评审 | 你评审登录 UI.md + 静态页,给我反馈我再迭代 |
| 02 | P0-A | 房源管理(新增) | US-PROPERTY-001 | `UI_DESIGN/房源管理/新增房源_UI.md` | | `UI_DESIGN/新增房源_UI.html` | 已完成 | 已完成评审迭代(壳层/按钮/结构一致化进入任务03 |
| 03 | P0-A | 房源管理(详情) | US-PROPERTY-003~008 | `UI_DESIGN/房源管理/房源详情_UI.md` | | `UI_DESIGN/房源详情_UI.html` | 待评审 | 你评审房源详情 UI.md + 静态页,给我反馈我再迭代 |
| 04 | P0-B | 楼盘管理(列表) | US-COMPLEX-002 | `UI_DESIGN/楼盘管理/楼盘列表_UI.md` | | `UI_DESIGN/楼盘列表_UI.html` | 待评审 | 你评审楼盘列表 UI.md + 静态页,给我反馈我再迭代 |
| 05 | P0-B | 楼盘管理(详情/维护) | US-COMPLEX-001 | `UI_DESIGN/楼盘管理/楼盘详情_UI.md` | `Project/fonrey/screenshots/楼盘管理/楼盘管理.png`<br>`Project/fonrey/screenshots/楼盘管理/楼栋管理.png`<br> | `UI_DESIGN/楼盘详情_UI.html` | 待评审 | 进入任务06楼盘管理-区域管理) |
| 06 | P0-B | 楼盘管理(区域) | US-COMPLEX-003 | `UI_DESIGN/楼盘管理/区域管理_UI.md` | `Project/fonrey/screenshots/楼盘管理/区域管理.png` | `UI_DESIGN/区域管理_UI.html` | 待设计 | 完成任务05后开始 |
| 07 | P0-C | 组织人事 | US-ORG-001~003 | `UI_DESIGN/组织人事管理/组织人事_UI.md` | `Project/fonrey/screenshots/组织人事/组织结构/公司组织结构.png` | `UI_DESIGN/组织人事_UI.html` | 待设计 | 完成任务06后开始 |
| 08 | P0-C | 权限管理 | US-PERMISSION-001~005 | `UI_DESIGN/权限管理/权限管理_UI.md`<br>`Project/fonrey/PRD/权限管理/房源-二手租赁.md`<br>`Project/fonrey/PRD/权限管理/客源.md` | `Project/fonrey/screenshots/权限管理/权限-客源-客源.png`<br>``<br>`Project/fonrey/screenshots/权限管理/权限-房源-二手租赁.jpg`<br>`` | `UI_DESIGN/权限管理_UI.html` | 待设计 | 完成任务07后开始 |
| 09 | P0-C | 系统配置 | US-SETTING-001-A/B/C | `UI_DESIGN/系统配置/系统配置_UI.md` | | `UI_DESIGN/系统配置_UI.html` | 待设计 | 完成任务08后开始 |
---

View File

@@ -0,0 +1,181 @@
# 区域管理 UI 设计文档
> **任务编号**06P0-B
> **覆盖范围**`US-COMPLEX-003`(管理员维护区域管理:城区/商圈)
> **输出文件**`UI_DESIGN/区域管理_UI.html`
> **设计基线**`UI_SYSTEM/UI_SYSTEM.md`列表页模板、Top Bar + Sidebar 壳层、表格/分页规范)
> **竞品截图参考**
> - `Project/fonrey/screenshots/楼盘管理/区域管理.png`
> - `Project/fonrey/screenshots/楼盘管理/商圈管理.png`
> - `Project/fonrey/screenshots/楼盘管理/编辑商圈.png`
> - `Project/fonrey/screenshots/楼盘管理/查看关联.png`
---
## 1. 目标与范围
### 1.1 页面目标
区域管理页用于运营/数据管理员统一维护“城区-商圈”两级地理字典,并支撑楼盘、房源、客源模块的区域筛选与统计。
核心目标:
1. 在同一业务页面支持“城区管理 / 商圈管理”双子模块切换
2. 支持关键词检索、区域过滤、有无坐标过滤
3. 支持批量操作(合并城区、合并商圈、转移商圈)
4. 支持新增/修改商圈弹窗维护
5. 支持查看商圈关联关系(标准区域映射)
### 1.2 本任务边界
- ✅ 包含:壳层、模块 Tab、子 Tab、筛选区、批量区、表格、分页、弹窗校验、关联关系弹窗
- ✅ 包含:竞品截图中的关键文案与字段结构(城区/商圈/关联关系)
- ⛔ 不包含:真实后端 CRUD、真实关联跳转、真实地图 API、真实权限鉴权
---
## 2. 信息架构
### 2.1 壳层结构(统一后台模板)
- **Top Bar固定 56px**:品牌 + 一级导航 + 用户区
- **Sidebar固定 240px**:房源管理二级导航
- **Main Content`ml-60 pt-[72px]`**
1. 面包屑 + 页面标题
2. 模块 Tab楼盘 / 区域管理 / 学校管理 / 应用标准数据)
3. 区域管理子 Tab城区管理 / 商圈管理)
4. 筛选区 + 批量区 + 列表区 + 分页区
### 2.2 区域管理子模块
#### A. 城区管理
- 筛选:城区名称关键词 + 有无坐标(不限/有坐标/无坐标)
- 列表字段:城区名称 / 商圈数量 / 楼盘数量 / 坐标 / 操作
- 操作:修改、设置坐标、合并城区、新增城区
#### B. 商圈管理
- 筛选:商圈名称关键词 + 区域过滤(标签)+ 有无坐标
- 列表字段:城区名称 / 商圈名称(含“标准”标签)/ 楼盘数量 / 坐标 / 操作
- 操作:修改、查看关联关系、设置坐标、合并商圈、转移商圈、新增商圈
### 2.3 关联关系视图(弹窗承载)
- 触发:商圈列表行内「查看关联关系」
- 筛选:标准区域(下拉)、本地区域(下拉)
- 列表字段:标准城市 / 标准城区 / 标准商圈 / 关联本地商圈 / 本地商圈所属城区 / 操作(变更)
- 支持批量修改按钮(勾选后启用)
---
## 3. 关键交互设计
### 3.1 子 Tab 切换
- 点击「城区管理 / 商圈管理」切换内容区
- 切换后保留当前模块壳层和分页区风格一致
### 3.2 筛选与重置
- 点击「查询」执行前端过滤
- 点击「重置」清空关键词与标签筛选,恢复默认列表
- 坐标筛选支持:不限 / 有坐标 / 无坐标
### 3.3 批量操作启用规则
- 未勾选时:批量按钮禁用态
- 勾选 1 条及以上:批量按钮启用
- 提示区实时显示:`已选本页 N 条`
### 3.4 新增/编辑弹窗
#### 城区弹窗
- 标题:新增城区 / 修改城区
- 字段:
- 城区名称(必填)
- 城区简称(选填)
- 校验:城区名称为空时提示“请输入城区名称”
#### 商圈弹窗
- 标题:新增商圈 / 修改商圈
- 字段:
- 所属城区(必填,下拉)
- 商圈名称(必填,文本)
- 校验:
- 未选城区:`请选择所属城区`
- 未填商圈名称:`请输入商圈名称`
### 3.5 设置坐标弹窗
- 支持城区/商圈两种来源
- 字段:纬度、经度
- 校验:任一为空时提示“请完整填写经纬度”
### 3.6 查看关联关系弹窗
- 保持“查看关联情况”核心信息结构
- 支持顶部筛选 + 列表 + 批量修改占位
- 点击「变更」仅触发原型提示(无真实提交)
---
## 4. 状态矩阵
| 状态 | 触发 | 页面反馈 |
|---|---|---|
| 默认态 | 首次进入页面 | 默认显示“城区管理”,加载城区列表 |
| 子模块切换 | 点击子 Tab | 切换到对应列表和筛选区 |
| 查询态 | 输入关键词并查询 | 列表按条件过滤 |
| 空结果态 | 条件过严 | 表格显示“暂无匹配数据” |
| 勾选态 | 勾选行/全选 | 批量按钮启用,显示已选条数 |
| 弹窗校验失败 | 必填缺失提交 | 字段下方红字错误,弹窗保持打开 |
| 保存成功(原型) | 校验通过提交 | 关闭弹窗 + Toast 成功提示 |
| 主题策略 | 统一后台视觉 | 页面内不包含 Light/Dark/System 切换控件 |
---
## 5. 字段与数据模型映射DATA_MODEL_COMPLEX
| UI 字段 | 数据模型字段 |
|---|---|
| 城区名称 | `districts.name` |
| 城区简称 | `districts.short_name` |
| 所属城市 | `districts.city` |
| 商圈名称 | `business_areas.name` |
| 所属城区 | `business_areas.district_id -> districts.id` |
| 商圈坐标 | `business_areas.latitude` / `business_areas.longitude` |
| 城区坐标(原型占位) | `districts` 扩展坐标字段PRD层业务占位 |
| 商圈数量 | `COUNT(business_areas.id)`(按城区聚合) |
| 楼盘数量 | `COUNT(complexes.id)`(按城区/商圈聚合) |
| 关联本地商圈 | `business_areas.name`(映射结果) |
| 标准城市/城区/商圈 | 标准区域字典(独立映射域,原型以 mock 展示) |
---
## 6. 可访问性与规范
- 表格列头使用 `<th scope="col">`
- 纯图标按钮补充 `aria-label`
- 弹窗支持 `Esc` 关闭
- 错误提示采用“字段下方红字文本”,不只依赖颜色
- 焦点样式统一 `focus-visible:ring-2 focus-visible:ring-primary-600/40`
- 页面保持浅色后台主题,不引入主题切换控件
---
## 7. 验收清单
- [x] 壳层结构Top Bar + Sidebar + Main
- [x] 模块 Tab 与子 Tab 结构完整
- [x] 城区管理筛选/列表/批量/分页完整
- [x] 商圈管理筛选/列表/批量/分页完整
- [x] 商圈编辑弹窗字段与校验符合 PRD Story 10
- [x] 关联关系视图字段与交互符合 PRD Story 11
- [x] 页面内不包含 Light/Dark/System 主题切换控件
- [ ] 控制台 0 报错(待本地预览验证)
---
## 8. 后续衔接
- 本页评审通过后进入任务07组织人事`US-ORG-001~003`
- 区域字典(城区/商圈)将作为房源/客源筛选条件来源,需保持命名与状态一致

View File

@@ -0,0 +1,176 @@
# 楼盘详情 UI 设计文档
> **任务编号**05P0-B
> **覆盖范围**`US-COMPLEX-001`(管理员录入与维护楼盘基础信息)
> **关联需求参考**`PRD/房源管理/楼盘管理模块PRD.md` Story 2~8楼盘详情分区信息架构
> **输出文件**`UI_DESIGN/楼盘详情_UI.html`
> **设计基线**`UI_SYSTEM/UI_SYSTEM.md`Top Bar + Sidebar 壳层、详情页模板、表单与校验规范)
---
## 1. 目标与范围
### 1.1 页面目标
楼盘详情页用于运营/数据管理员在单个楼盘维度完成“查看 + 维护”闭环:
1. 查看楼盘基础档案(基本信息、对口学校、其他信息)
2. 管理楼栋与结构(楼层/房号)
3. 查看与维护楼盘图片、附件
4. 查看价格走势与周边配套信息
### 1.2 本任务边界
本任务交付“楼盘详情静态高保真原型”,重点完成信息架构与关键交互演示。
- ✅ 包含:壳层、详情头部、分区 Tab、编辑抽屉、必填校验、批量操作占位、分页占位
- ✅ 包含:底部固定操作区(编辑态)且按钮右对齐
- ⛔ 不包含:真实接口联动、地图 API、图表库渲染、上传存储、权限后端判定
---
## 2. 信息架构
### 2.1 壳层结构(统一后台模板)
- **Top Bar固定 56px**:品牌 + 一级导航 + 用户区
- **Sidebar固定 240px**:房源管理二级导航
- **Main Content`ml-60 pt-[72px]`**
1. 面包屑 + 楼盘标题 + 锁定标签 + 顶部动作按钮
2. 楼盘详情分区 Tab8 个)
3. Tab 内容区域
### 2.2 楼盘详情分区 Tab页面级
1. 楼盘信息(默认)
2. 楼栋管理
3. 结构管理
4. 楼盘照片
5. 楼盘附件
6. 周边配套
7. 楼盘价格走势
8. 销控盘
### 2.3 楼盘信息分区结构(默认 Tab
- **基本信息**:城区商圈、地址、建筑类型、楼栋结构、别名、竣工年限、坐标等
- **对口学校**:学校名称/类型/性质/等级表格
- **其他信息**:面积、容积率、绿化率、物业信息、车位信息、供水供电、备注
- **右侧辅助面板**:维护完成度、锁定状态、相关员工、快捷操作
---
## 3. 关键交互设计
### 3.1 页面级交互
1. 点击分区 Tab 切换对应内容(单页切换)
2. 顶部「解锁楼盘」按钮触发提示(原型态)
3. 顶部「编辑楼盘信息」打开右侧编辑抽屉
### 3.2 编辑抽屉(楼盘信息维护)
- 抽屉分为:基本信息 / 学校信息 / 其他信息 三段
- 必填字段校验:
- 物业类型(必填)
- 楼栋结构(必填)
- 小区名称、小区地址为只读灰底展示
- 底部固定操作区:
- `取消`(次按钮)
- `保存`(主按钮)
- 底部操作区始终固定,按钮右对齐
### 3.3 楼栋管理交互
- 关键词搜索单元名
- 行勾选 + 表头全选
- 批量按钮(批量设置/合并/移动)随选中状态启用
- 每行操作:编辑 / 结构管理
### 3.4 结构管理交互
- 左侧单元列表(可切换当前单元)
- 右侧楼层-房号矩阵
- 房号勾选后启用“批量编辑房号/合并房号”
- 提示区显示“已选 N 条”与异常结构入口占位
### 3.5 其他分区交互(原型演示)
- 楼盘照片:分类切换(楼盘图片/户型图/楼盘VR+ 户型过滤占位
- 楼盘附件:上传入口占位 + 附件列表
- 周边配套:分类切换(交通/教育/医疗/购物/生活/娱乐)+ 地图占位
- 价格走势:司内/市场子 Tab 切换 + 图表占位 + 明细表
---
## 4. 状态矩阵
| 状态 | 触发 | 页面反馈 |
|---|---|---|
| 默认态 | 首次进入页面 | 默认激活“楼盘信息”Tab展示详情概览 |
| Tab 切换 | 点击分区 Tab | 内容区域切换,对应 Tab 高亮 |
| 打开编辑 | 点击“编辑楼盘信息” | 右侧抽屉滑入,加载当前字段值 |
| 校验失败 | 抽屉内点击保存且必填缺失 | 字段下方显示错误文本 + 保持抽屉打开 |
| 保存成功(原型) | 抽屉内点击保存且校验通过 | 抽屉关闭 + Toast“楼盘信息已保存原型” |
| 楼栋批量态 | 勾选楼栋行 | 批量按钮启用,显示已选条数 |
| 结构批量态 | 勾选房号 | 矩阵批量按钮启用,显示已选条数 |
| 空数据态 | 学校/附件/成交明细为空 | 使用“暂无数据”占位与引导文案 |
| 主题策略 | 全局后台视觉 | 页面内不提供 Light/Dark/System 切换控件 |
---
## 5. 字段与数据模型映射DATA_MODEL_COMPLEX
| UI 字段 | 数据模型字段 |
|---|---|
| 楼盘名称(只读) | `complexes.name` |
| 城区商圈 | `complexes.district_id` + `complex_business_areas` |
| 小区地址(只读) | `complexes.address` |
| 概要地址 | `complexes.address_summary` |
| 物业类型(多选) | `complexes.property_usage_types` |
| 楼栋结构 | `complexes.building_structure` |
| 建筑类型 | `complexes.building_type` |
| 土地使用年限 | `complexes.land_use_years` |
| 竣工年限 | `complexes.built_years` |
| 权属类别 | `complexes.ownership_category` |
| 单元总数/总户数 | `complexes.total_units` / `complexes.total_households` |
| 经纬度坐标 | `complexes.latitude` / `complexes.longitude` |
| 对口学校 | `complex_schools` + `schools` |
| 楼栋列表 | `buildings` |
| 楼层房号矩阵 | `room_units` |
| 图片资源 | `complex_photos` |
| 附件 | `complex_attachments` |
| 价格走势 | `complex_price_trends` |
| 锁定标签 | `lock_building` / `lock_room` / `lock_info` / `lock_standard_room` |
---
## 6. 可访问性与规范
- 表格列头使用语义化 `<th scope="col">`
- 抽屉与弹层支持 `Esc` 关闭
- 纯图标按钮均带 `aria-label`
- 表单错误提示为“字段下方红字 + 文本说明”,不只靠颜色
- 所有可交互控件支持 `focus-visible:ring-2`
- 页面采用统一浅色后台主题,不包含主题切换控件
---
## 7. 验收清单
- [x] 壳层结构Top Bar + Sidebar + Main
- [x] 楼盘详情头部信息与锁定标签完整
- [x] 8 个分区 Tab楼盘信息/楼栋/结构/照片/附件/配套/价格/销控盘)完整
- [x] 楼盘信息包含“基本信息/对口学校/其他信息”三段
- [x] 编辑抽屉具备必填校验(物业类型、楼栋结构)
- [x] 编辑态底部按钮固定且右对齐
- [x] 楼栋管理与结构管理具备勾选 + 批量操作演示
- [x] 页面内不包含主题切换控件
- [ ] 控制台 0 报错(待本地预览验证)
---
## 8. 后续衔接
- 本页评审通过后继续任务06区域管理`US-COMPLEX-003`
- 区域管理将与本页“城区商圈”字段形成前后联动(字典数据来源一致)

View File

@@ -0,0 +1,874 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1280" />
<title>Fonrey 楼盘详情 · 静态原型</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#F0FDFA',
100: '#CCFBF1',
200: '#99F6E4',
500: '#14B8A6',
600: '#0F766E',
700: '#115E59',
800: '#134E4A'
},
neutral: {
50: '#F8FAFC',
100: '#F1F5F9',
200: '#E2E8F0',
300: '#CBD5E1',
400: '#94A3B8',
500: '#64748B',
600: '#475569',
700: '#334155',
800: '#1E293B',
900: '#0F172A'
},
success: { 50: '#F0FDF4', 600: '#16A34A' },
warning: { 50: '#FFFBEB', 600: '#D97706' },
danger: { 50: '#FEF2F2', 600: '#DC2626' },
info: { 50: '#EFF6FF', 600: '#2563EB' }
},
boxShadow: {
xs: '0 1px 2px rgba(15,23,42,0.04)'
}
}
}
}
</script>
<style>
:root {
--bg-page: #F8FAFC;
--bg-card: #FFFFFF;
--bg-subtle: #F1F5F9;
--text-primary: #0F172A;
--text-secondary: #64748B;
--border: #E2E8F0;
}
html { scroll-behavior: smooth; }
body {
background: var(--bg-page);
color: var(--text-primary);
font-family: Inter, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
[x-cloak] { display: none !important; }
.tabular-nums { font-variant-numeric: tabular-nums; }
.bg-surface { background: var(--bg-card); }
.bg-subtle { background: var(--bg-subtle); }
.border-surface { border-color: var(--border); }
.text-surface { color: var(--text-primary); }
.text-muted { color: var(--text-secondary); }
.tab-btn {
color: #64748B;
border-bottom: 2px solid transparent;
}
.tab-btn.active {
color: #0F766E;
border-bottom-color: #0F766E;
font-weight: 600;
}
.badge-lock {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
line-height: 1;
padding: 4px 8px;
border-radius: 999px;
border: 1px solid #FECACA;
color: #B91C1C;
background: #FEF2F2;
}
.subtab-pill {
border: 1px solid #E2E8F0;
background: #FFFFFF;
color: #64748B;
}
.subtab-pill.active {
border-color: #0F766E;
color: #0F766E;
background: #F0FDFA;
font-weight: 600;
}
.left-item {
border: 1px solid #E2E8F0;
background: #FFFFFF;
color: #334155;
}
.left-item.active {
border-color: #0F766E;
background: #F0FDFA;
color: #0F766E;
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: #CBD5E1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #94A3B8; }
</style>
</head>
<body class="text-sm antialiased" x-data="complexDetailPage()" x-init="init()">
<!-- Top Bar -->
<header class="fixed top-0 left-0 right-0 h-14 z-20 bg-primary-800 flex items-center justify-between">
<div class="flex items-center gap-2 px-4 w-60 shrink-0">
<div class="w-7 h-7 rounded-md bg-primary-500 flex items-center justify-center text-white text-sm font-semibold">F</div>
<span class="text-base font-semibold text-white">Fonrey</span>
</div>
<nav class="flex items-center gap-1 flex-1 px-2" aria-label="主导航">
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">工作台</a>
<a class="px-3 py-1.5 text-sm rounded-md bg-primary-600 text-white font-medium">房源</a>
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">客源</a>
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">营销</a>
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">交易</a>
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">数据</a>
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">人事</a>
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">系统</a>
</nav>
<div class="flex items-center gap-1 px-4 shrink-0">
<button class="p-1.5 text-primary-200 hover:bg-primary-700 hover:text-white rounded-md" aria-label="消息">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022 23.848 23.848 0 0 0 5.454 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"/></svg>
</button>
<div class="flex items-center gap-2 pl-3 ml-1 border-l border-primary-700">
<div class="w-8 h-8 rounded-full bg-primary-600 text-white flex items-center justify-center text-sm font-semibold"></div>
<span class="text-sm font-medium text-primary-100">魏深</span>
</div>
</div>
</header>
<!-- Sidebar -->
<aside class="fixed left-0 top-14 h-[calc(100vh-56px)] w-60 z-20 border-r border-surface bg-surface overflow-y-auto">
<nav class="p-3 space-y-0.5">
<div class="px-2 pt-2 pb-1 text-xs font-medium text-muted uppercase tracking-wide">房源管理</div>
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md bg-primary-50 text-primary-700 font-medium">楼盘管理</a>
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">房源列表</a>
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">新增房源</a>
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">成交房源</a>
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">已删房源</a>
</nav>
</aside>
<!-- Main -->
<main class="ml-60 pt-[72px] min-h-screen px-6 py-5">
<div class="mx-auto max-w-[1680px] space-y-4">
<!-- Header Card -->
<section class="bg-surface border border-surface rounded-lg p-4 space-y-3">
<nav class="flex items-center gap-1 text-xs text-muted" aria-label="面包屑">
<a href="#" class="hover:text-neutral-700">房源</a>
<span>/</span>
<a href="#" class="hover:text-neutral-700">小区</a>
<span>/</span>
<a href="#" class="hover:text-neutral-700">楼盘管理系统-楼盘管理</a>
<span>/</span>
<span class="text-surface">楼盘详情</span>
</nav>
<div class="flex items-start justify-between gap-4">
<div class="space-y-2">
<h1 class="text-xl font-semibold text-surface">阳光威尼斯四期(别墅)</h1>
<p class="text-xs text-muted">恒盛提香湾 / 璟聚人生 / 祁连山南路2727弄 / 金鼎路1600弄</p>
<div class="flex items-center gap-2 flex-wrap">
<span class="badge-lock">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-3 0h15a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 19.5V12a1.5 1.5 0 0 1 1.5-1.5Z"/></svg>
楼栋锁
</span>
<span class="badge-lock"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-3 0h15a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 19.5V12a1.5 1.5 0 0 1 1.5-1.5Z"/></svg>房号</span>
<span class="badge-lock"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-3 0h15a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 19.5V12a1.5 1.5 0 0 1 1.5-1.5Z"/></svg>信息</span>
<span class="badge-lock"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-3 0h15a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 19.5V12a1.5 1.5 0 0 1 1.5-1.5Z"/></svg>标准房号</span>
</div>
</div>
<div class="flex items-center gap-2">
<button class="px-3 py-1.5 rounded-md border border-neutral-300 text-neutral-700 hover:bg-neutral-50" @click="notify('已触发楼盘解锁流程(原型)')">解锁楼盘</button>
<button class="px-3 py-1.5 rounded-md bg-primary-600 text-white hover:bg-primary-700" @click="openEditDrawer()">编辑楼盘信息</button>
</div>
</div>
</section>
<!-- Tabs -->
<section class="bg-surface border border-surface rounded-lg px-4">
<nav class="flex items-center gap-6 overflow-x-auto" aria-label="楼盘详情分区导航">
<template x-for="item in tabs" :key="item.key">
<button class="tab-btn py-3 whitespace-nowrap" :class="{ 'active': activeTab === item.key }" @click="activeTab = item.key" x-text="item.label"></button>
</template>
</nav>
</section>
<!-- 楼盘信息 -->
<section x-show="activeTab === 'info'" x-cloak class="grid grid-cols-12 gap-6 items-start">
<div class="col-span-8 space-y-4">
<article class="bg-surface border border-surface rounded-lg p-4">
<div class="flex items-center justify-between mb-3">
<h2 class="text-base font-semibold">基本信息</h2>
<p class="text-xs text-info-600">楼盘地址有误?点此【纠错】</p>
</div>
<dl class="grid grid-cols-2 gap-x-8 gap-y-3 text-sm">
<div><dt class="text-xs text-muted">城区商圈</dt><dd class="mt-0.5">普陀-真光</dd></div>
<div><dt class="text-xs text-muted">小区地址</dt><dd class="mt-0.5">上海普陀金鼎路1600弄,祁连山南路2727弄</dd></div>
<div><dt class="text-xs text-muted">楼栋结构</dt><dd class="mt-0.5">单元-房号</dd></div>
<div><dt class="text-xs text-muted">概要地址</dt><dd class="mt-0.5">金鼎路1600弄,祁连山南路2727弄</dd></div>
<div><dt class="text-xs text-muted">小区别名</dt><dd class="mt-0.5">恒盛提香湾,璟聚人生,阳光建华城</dd></div>
<div><dt class="text-xs text-muted">建筑类型</dt><dd class="mt-0.5">板塔结合</dd></div>
<div><dt class="text-xs text-muted">物业类型</dt><dd class="mt-0.5">别墅</dd></div>
<div><dt class="text-xs text-muted">土地使用年限</dt><dd class="mt-0.5">70年</dd></div>
<div><dt class="text-xs text-muted">竣工年限</dt><dd class="mt-0.5">2009</dd></div>
<div><dt class="text-xs text-muted">权属类别</dt><dd class="mt-0.5">商品房住宅</dd></div>
<div><dt class="text-xs text-muted">单元总数</dt><dd class="mt-0.5">96栋</dd></div>
<div><dt class="text-xs text-muted">总户数</dt><dd class="mt-0.5">96户</dd></div>
<div class="col-span-2"><dt class="text-xs text-muted">小区坐标</dt><dd class="mt-0.5 tabular-nums">31.264564,121.376238</dd></div>
</dl>
</article>
<article class="bg-surface border border-surface rounded-lg p-4">
<div class="flex items-center justify-between mb-3">
<h2 class="text-base font-semibold">对口学校</h2>
<button class="px-3 py-1.5 rounded-md border border-neutral-300 hover:bg-neutral-50" @click="notify('打开学校关联弹窗(原型)')">+ 添加学校</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="bg-subtle border-y border-surface">
<tr>
<th scope="col" class="px-3 py-2 text-left">学校名称</th>
<th scope="col" class="px-3 py-2 text-left">学校类型</th>
<th scope="col" class="px-3 py-2 text-left">学校性质</th>
<th scope="col" class="px-3 py-2 text-left">学校等级</th>
<th scope="col" class="px-3 py-2 text-left">操作</th>
</tr>
</thead>
<tbody>
<template x-if="schools.length === 0">
<tr><td colspan="5" class="px-3 py-8 text-center text-muted">暂无数据</td></tr>
</template>
<template x-for="row in schools" :key="row.name">
<tr class="border-b border-surface">
<td class="px-3 py-2" x-text="row.name"></td>
<td class="px-3 py-2" x-text="row.type"></td>
<td class="px-3 py-2" x-text="row.nature"></td>
<td class="px-3 py-2" x-text="row.level"></td>
<td class="px-3 py-2"><button class="text-danger-600 hover:underline" @click="notify('删除学校关联(原型)')">删除</button></td>
</tr>
</template>
</tbody>
</table>
</div>
</article>
<article class="bg-surface border border-surface rounded-lg p-4">
<h2 class="text-base font-semibold mb-3">其他信息</h2>
<dl class="grid grid-cols-2 gap-x-8 gap-y-3 text-sm">
<div><dt class="text-xs text-muted">小区总建筑面积</dt><dd class="mt-0.5 tabular-nums">80000㎡</dd></div>
<div><dt class="text-xs text-muted">小区占地面积</dt><dd class="mt-0.5 tabular-nums">50000㎡</dd></div>
<div><dt class="text-xs text-muted">容积率</dt><dd class="mt-0.5 tabular-nums">1.60</dd></div>
<div><dt class="text-xs text-muted">绿化率</dt><dd class="mt-0.5 tabular-nums">45%</dd></div>
<div><dt class="text-xs text-muted">物业公司</dt><dd class="mt-0.5">阳光物业有限公司</dd></div>
<div><dt class="text-xs text-muted">物业费</dt><dd class="mt-0.5 tabular-nums">1.45 元/㎡/月</dd></div>
<div><dt class="text-xs text-muted">物业电话</dt><dd class="mt-0.5">-</dd></div>
<div><dt class="text-xs text-muted">开发商</dt><dd class="mt-0.5">上海意景房产开发有限公司</dd></div>
<div><dt class="text-xs text-muted">车位数(总)</dt><dd class="mt-0.5 tabular-nums">800 个</dd></div>
<div><dt class="text-xs text-muted">车位数(地下)</dt><dd class="mt-0.5">-</dd></div>
<div><dt class="text-xs text-muted">停车位配比</dt><dd class="mt-0.5">100:63</dd></div>
<div><dt class="text-xs text-muted">供水 / 供电</dt><dd class="mt-0.5">民水 / 民电</dd></div>
<div><dt class="text-xs text-muted">统一供暖</dt><dd class="mt-0.5"></dd></div>
<div><dt class="text-xs text-muted">有无燃气</dt><dd class="mt-0.5"></dd></div>
<div class="col-span-2"><dt class="text-xs text-muted">备注</dt><dd class="mt-0.5">本楼盘别墅区产品为主,房号结构基本标准化。</dd></div>
</dl>
</article>
</div>
<aside class="col-span-4 space-y-4 sticky top-[88px]">
<article class="bg-surface border border-surface rounded-lg p-4">
<h3 class="text-sm font-semibold mb-3">维护完成度</h3>
<div class="w-full h-2 rounded-full bg-neutral-200 overflow-hidden">
<div class="h-full bg-primary-600" style="width: 72%"></div>
</div>
<p class="text-xs text-muted mt-2">当前维护度 72%,建议补全学校和附件信息。</p>
</article>
<article class="bg-surface border border-surface rounded-lg p-4">
<h3 class="text-sm font-semibold mb-3">相关员工</h3>
<ul class="space-y-2 text-sm">
<li class="flex items-center justify-between"><span class="text-muted">首录人</span><span>李晨(普陀真光)</span></li>
<li class="flex items-center justify-between"><span class="text-muted">归属人</span><span>杜利强(运营)</span></li>
<li class="flex items-center justify-between"><span class="text-muted">最近更新</span><span class="tabular-nums">2026-04-28 15:20</span></li>
</ul>
</article>
<article class="bg-surface border border-surface rounded-lg p-4 space-y-2">
<button class="w-full px-3 py-2 rounded-md bg-primary-600 text-white hover:bg-primary-700" @click="openEditDrawer()">编辑楼盘信息</button>
<button class="w-full px-3 py-2 rounded-md border border-surface hover:bg-neutral-50" @click="notify('打开楼盘附件上传(原型)')">上传附件</button>
<button class="w-full px-3 py-2 rounded-md border border-surface hover:bg-neutral-50" @click="notify('查看楼盘操作日志(原型)')">查看操作日志</button>
</article>
</aside>
</section>
<!-- 楼栋管理 -->
<section x-show="activeTab === 'buildings'" x-cloak class="bg-surface border border-surface rounded-lg p-4 space-y-3">
<div class="flex items-center justify-between gap-3">
<div class="flex items-center gap-2">
<input x-model.trim="buildingKeyword" type="text" placeholder="单元" class="w-56 px-3 py-2 rounded-md border border-surface bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" />
<button class="px-3 py-2 rounded-md bg-primary-600 text-white hover:bg-primary-700" @click="applyBuildingFilter()">搜索</button>
</div>
<p class="text-xs text-muted">本楼盘还有 <span class="text-danger-600 font-medium">0</span> 个非标准结构,<button class="text-primary-600 hover:underline" @click="notify('查看非标明细(原型)')">查看明细</button></p>
</div>
<div class="flex items-center gap-2 flex-wrap">
<button class="px-3 py-1.5 rounded-md border border-surface" :disabled="selectedBuildingCount===0" :class="selectedBuildingCount===0 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-neutral-50'">批量设置单元信息</button>
<button class="px-3 py-1.5 rounded-md border border-surface" :disabled="selectedBuildingCount===0" :class="selectedBuildingCount===0 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-neutral-50'">合并单元</button>
<button class="px-3 py-1.5 rounded-md border border-surface" :disabled="selectedBuildingCount===0" :class="selectedBuildingCount===0 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-neutral-50'">移动单元</button>
<span class="text-xs text-muted" x-text="selectedBuildingCount > 0 ? '已选 ' + selectedBuildingCount + ' 条' : '未选中单元'"></span>
</div>
<div class="overflow-x-auto border border-surface rounded-lg">
<table class="min-w-full text-sm">
<thead class="bg-subtle border-b border-surface">
<tr>
<th scope="col" class="px-3 py-2 w-10"><input type="checkbox" aria-label="全选单元" class="rounded border-surface" :checked="allBuildingsOnPage" @click.prevent="toggleBuildingSelectPage(!allBuildingsOnPage)" /></th>
<th scope="col" class="px-3 py-2 text-left">单元名</th>
<th scope="col" class="px-3 py-2 text-left">物业类型</th>
<th scope="col" class="px-3 py-2 text-left">竣工年限</th>
<th scope="col" class="px-3 py-2 text-left">总层数</th>
<th scope="col" class="px-3 py-2 text-left">土地使用年限</th>
<th scope="col" class="px-3 py-2 text-left">电梯</th>
<th scope="col" class="px-3 py-2 text-left">关联学校</th>
<th scope="col" class="px-3 py-2 text-left">操作</th>
</tr>
</thead>
<tbody>
<template x-for="row in filteredBuildings" :key="row.id">
<tr class="border-b border-surface hover:bg-neutral-50/70">
<td class="px-3 py-2">
<input type="checkbox" class="rounded border-surface" :checked="selectedBuildingIds.includes(row.id)" @change="toggleBuildingSelect(row.id, $event.target.checked)" />
</td>
<td class="px-3 py-2">
<button class="text-primary-600 hover:underline" @click="notify('进入单元详情(原型)')" x-text="row.name"></button>
<span class="ml-2 inline-flex px-1.5 py-0.5 rounded text-xs bg-info-50 text-info-600 border border-info-600/20">标准</span>
</td>
<td class="px-3 py-2" x-text="row.usage"></td>
<td class="px-3 py-2 tabular-nums" x-text="row.year"></td>
<td class="px-3 py-2" x-text="row.floors"></td>
<td class="px-3 py-2" x-text="row.landYears"></td>
<td class="px-3 py-2" x-text="row.elevator"></td>
<td class="px-3 py-2" x-text="row.school"></td>
<td class="px-3 py-2">
<button class="text-primary-600 hover:underline mr-3" @click="notify('编辑楼栋(原型)')">编辑</button>
<button class="text-primary-600 hover:underline" @click="switchToStructure(row.name)">结构管理</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="flex items-center justify-between text-xs text-muted px-1">
<span><span x-text="filteredBuildings.length"></span></span>
<span>每页 20 条(原型占位)</span>
</div>
</section>
<!-- 结构管理 -->
<section x-show="activeTab === 'structure'" x-cloak class="bg-surface border border-surface rounded-lg p-4">
<div class="grid grid-cols-12 gap-4 min-h-[520px]">
<aside class="col-span-4 border border-surface rounded-lg p-3 flex flex-col">
<div class="flex items-center gap-2 mb-3">
<select class="px-2.5 py-2 rounded-md border border-surface bg-white text-sm">
<option>单元</option>
</select>
<button class="px-3 py-2 rounded-md border border-surface hover:bg-neutral-50 text-sm">设置单元信息</button>
</div>
<div class="space-y-1.5 overflow-y-auto pr-1">
<template x-for="item in units" :key="item.id">
<button class="left-item w-full text-left px-3 py-2 rounded-md text-sm" :class="{ 'active': currentUnitId === item.id }" @click="currentUnitId = item.id">
<span x-text="item.name"></span>
<span class="ml-2 inline-flex px-1.5 py-0.5 rounded text-xs bg-info-50 text-info-600 border border-info-600/20">标准</span>
</button>
</template>
</div>
</aside>
<div class="col-span-8 border border-surface rounded-lg p-3 flex flex-col">
<div class="flex items-center justify-between gap-3 mb-3">
<h3 class="text-sm font-semibold" x-text="currentUnitName"></h3>
<p class="text-xs text-muted">本楼盘还有 0 个房号无法关联结构,<button class="text-primary-600 hover:underline" @click="notify('查看无法关联明细(原型)')">查看明细</button></p>
</div>
<div class="flex items-center gap-2 mb-3">
<button class="px-3 py-1.5 rounded-md border border-surface" :disabled="selectedRoomCount===0" :class="selectedRoomCount===0 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-neutral-50'">批量编辑房号</button>
<button class="px-3 py-1.5 rounded-md border border-surface" :disabled="selectedRoomCount===0" :class="selectedRoomCount===0 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-neutral-50'">合并房号</button>
<span class="text-xs text-muted" x-text="'已选 ' + selectedRoomCount + ' 条'">已选 0 条</span>
<span class="text-xs text-muted">找不到房号?<button class="text-primary-600 hover:underline" @click="notify('申请新增房号(原型)')">点此申请新增</button></span>
</div>
<div class="overflow-auto border border-surface rounded-lg flex-1">
<table class="min-w-full text-sm">
<thead class="bg-subtle border-b border-surface">
<tr>
<th scope="col" class="px-3 py-2 w-10"><input type="checkbox" class="rounded border-surface" :checked="allRoomsSelected" @click.prevent="toggleAllRooms(!allRoomsSelected)" /></th>
<th scope="col" class="px-3 py-2 text-left">楼层名(实际层)</th>
<th scope="col" class="px-3 py-2 text-left">房号</th>
</tr>
</thead>
<tbody>
<template x-for="room in currentRooms" :key="room.id">
<tr class="border-b border-surface hover:bg-neutral-50/70">
<td class="px-3 py-2"><input type="checkbox" class="rounded border-surface" :checked="selectedRoomIds.includes(room.id)" @change="toggleRoom(room.id, $event.target.checked)" /></td>
<td class="px-3 py-2" x-text="room.floorName"></td>
<td class="px-3 py-2">
<span x-text="room.roomNo"></span>
<span class="ml-2 inline-flex px-1.5 py-0.5 rounded text-xs bg-info-50 text-info-600 border border-info-600/20">标准</span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- 楼盘照片 -->
<section x-show="activeTab === 'photos'" x-cloak class="bg-surface border border-surface rounded-lg p-4 space-y-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<button class="subtab-pill px-3 py-1.5 rounded-md" :class="{ 'active': photoType === 'complex' }" @click="photoType='complex'">楼盘图片12</button>
<button class="subtab-pill px-3 py-1.5 rounded-md" :class="{ 'active': photoType === 'layout' }" @click="photoType='layout'">户型图8</button>
<button class="subtab-pill px-3 py-1.5 rounded-md" :class="{ 'active': photoType === 'vr' }" @click="photoType='vr'">楼盘VR2</button>
</div>
<button class="px-3 py-2 rounded-md bg-primary-600 text-white hover:bg-primary-700" @click="notify('上传图片(原型)')">上传图片</button>
</div>
<div x-show="photoType === 'layout'" class="flex items-center gap-2">
<span class="text-xs text-muted">户型:</span>
<button class="subtab-pill px-2 py-1 rounded-full text-xs active">全部</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">1室</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">2室</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">3室</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">4室+</button>
</div>
<div class="grid grid-cols-6 gap-3">
<template x-for="card in 12" :key="card">
<div class="border border-surface rounded-lg overflow-hidden bg-white">
<div class="aspect-[4/3] bg-neutral-200 flex items-center justify-center text-neutral-500 text-xs">图片占位</div>
<div class="px-2 py-1.5 text-xs text-neutral-600">1室2厅1卫 · 朝南</div>
</div>
</template>
</div>
</section>
<!-- 楼盘附件 -->
<section x-show="activeTab === 'attachments'" x-cloak class="bg-surface border border-surface rounded-lg p-4 space-y-3">
<div class="flex items-center justify-between">
<h2 class="text-base font-semibold">楼盘附件</h2>
<button class="px-3 py-2 rounded-md bg-primary-600 text-white hover:bg-primary-700" @click="notify('上传附件(原型)')">上传附件</button>
</div>
<div class="overflow-x-auto border border-surface rounded-lg">
<table class="min-w-full text-sm">
<thead class="bg-subtle border-b border-surface">
<tr>
<th scope="col" class="px-3 py-2 text-left">附件名称</th>
<th scope="col" class="px-3 py-2 text-left">类型</th>
<th scope="col" class="px-3 py-2 text-left">大小</th>
<th scope="col" class="px-3 py-2 text-left">上传人</th>
<th scope="col" class="px-3 py-2 text-left">上传时间</th>
<th scope="col" class="px-3 py-2 text-left">操作</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-surface"><td class="px-3 py-2">小区总平图.pdf</td><td class="px-3 py-2">PDF</td><td class="px-3 py-2">1.2MB</td><td class="px-3 py-2">杜利强</td><td class="px-3 py-2 tabular-nums">2026-04-25 11:20</td><td class="px-3 py-2"><button class="text-primary-600 hover:underline">下载</button></td></tr>
<tr><td class="px-3 py-2">物业合同扫描件.zip</td><td class="px-3 py-2">ZIP</td><td class="px-3 py-2">3.8MB</td><td class="px-3 py-2">杜利强</td><td class="px-3 py-2 tabular-nums">2026-04-26 09:30</td><td class="px-3 py-2"><button class="text-primary-600 hover:underline">下载</button></td></tr>
</tbody>
</table>
</div>
</section>
<!-- 周边配套 -->
<section x-show="activeTab === 'facility'" x-cloak class="bg-surface border border-surface rounded-lg p-4">
<div class="grid grid-cols-12 gap-4">
<div class="col-span-8 border border-surface rounded-lg overflow-hidden">
<div class="h-[420px] bg-neutral-200 flex items-center justify-center text-neutral-500">地图区域占位(楼盘点位 + 周边 POI</div>
</div>
<aside class="col-span-4 border border-surface rounded-lg p-3 space-y-3">
<div class="flex items-center gap-1 flex-wrap">
<button class="subtab-pill px-2 py-1 rounded-full text-xs active">交通</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">教育</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">医疗</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">购物</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">生活</button>
<button class="subtab-pill px-2 py-1 rounded-full text-xs">娱乐</button>
</div>
<ul class="space-y-2 text-sm">
<li class="border border-surface rounded-md p-2"><p class="font-medium">地铁13号线 · 金运路站</p><p class="text-xs text-muted mt-0.5">距楼盘 1227 米</p></li>
<li class="border border-surface rounded-md p-2"><p class="font-medium">公交站 · 祁连山南路金鼎路</p><p class="text-xs text-muted mt-0.5">距楼盘 356 米</p></li>
<li class="border border-surface rounded-md p-2"><p class="font-medium">公交站 · 金鼎路定边路</p><p class="text-xs text-muted mt-0.5">距楼盘 412 米</p></li>
</ul>
</aside>
</div>
</section>
<!-- 价格走势 -->
<section x-show="activeTab === 'price'" x-cloak class="bg-surface border border-surface rounded-lg p-4 space-y-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<button class="subtab-pill px-3 py-1.5 rounded-md" :class="{ 'active': priceView === 'internal' }" @click="priceView='internal'">司内数据</button>
<button class="subtab-pill px-3 py-1.5 rounded-md" :class="{ 'active': priceView === 'market' }" @click="priceView='market'">市场数据</button>
</div>
<p class="text-xs text-muted">以下数据按照 T+1 更新,市场与网签数据仅供参考</p>
</div>
<div class="grid grid-cols-4 gap-3">
<div class="border border-surface rounded-lg p-3"><p class="text-xs text-muted">本周挂牌均价</p><p class="text-xl font-semibold tabular-nums mt-1">57,800 元/㎡</p></div>
<div class="border border-surface rounded-lg p-3"><p class="text-xs text-muted">近一年成交均价</p><p class="text-xl font-semibold tabular-nums mt-1">55,200 元/㎡</p></div>
<div class="border border-surface rounded-lg p-3"><p class="text-xs text-muted">挂牌套数</p><p class="text-xl font-semibold tabular-nums mt-1">38 套</p></div>
<div class="border border-surface rounded-lg p-3"><p class="text-xs text-muted">近一年成交套数</p><p class="text-xl font-semibold tabular-nums mt-1">26 套</p></div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="border border-surface rounded-lg p-3">
<div class="flex items-center justify-between mb-2"><h3 class="font-medium">挂牌均价趋势</h3><div class="flex gap-1"><button class="subtab-pill px-2 py-0.5 rounded text-xs active">按周</button><button class="subtab-pill px-2 py-0.5 rounded text-xs">按月</button></div></div>
<div class="h-56 bg-neutral-100 rounded-md flex items-center justify-center text-neutral-500">折线图占位</div>
</div>
<div class="border border-surface rounded-lg p-3">
<h3 class="font-medium mb-2">成交均价趋势</h3>
<div class="h-56 bg-neutral-100 rounded-md flex items-center justify-center text-neutral-500">折线图占位</div>
</div>
</div>
<div class="border border-surface rounded-lg overflow-hidden">
<div class="px-3 py-2 bg-subtle border-b border-surface text-sm font-medium">本小区成交数据明细(近一年)</div>
<div class="overflow-x-auto">
<table class="min-w-full text-xs">
<thead class="bg-subtle/60 border-b border-surface">
<tr>
<th scope="col" class="px-3 py-2 text-left">房源编号</th>
<th scope="col" class="px-3 py-2 text-left">挂牌价格(万)</th>
<th scope="col" class="px-3 py-2 text-left">成交价格(万)</th>
<th scope="col" class="px-3 py-2 text-left">成交单价(元/㎡)</th>
<th scope="col" class="px-3 py-2 text-left">成交日期</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-surface"><td class="px-3 py-2">01DS016848</td><td class="px-3 py-2 tabular-nums">678</td><td class="px-3 py-2 tabular-nums">650</td><td class="px-3 py-2 tabular-nums">59215</td><td class="px-3 py-2 tabular-nums">2026-03-18</td></tr>
<tr><td class="px-3 py-2">01DS015902</td><td class="px-3 py-2 tabular-nums">620</td><td class="px-3 py-2 tabular-nums">598</td><td class="px-3 py-2 tabular-nums">56612</td><td class="px-3 py-2 tabular-nums">2026-02-03</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- 销控盘 -->
<section x-show="activeTab === 'sales'" x-cloak class="bg-surface border border-surface rounded-lg p-8 text-center">
<h2 class="text-lg font-semibold text-surface">销控盘(后续版本)</h2>
<p class="text-sm text-muted mt-2">本模块用于新房/一手楼盘销控,当前在 P0 阶段保留占位。</p>
</section>
</div>
</main>
<!-- Edit Drawer -->
<div x-cloak x-show="editDrawerOpen" class="fixed inset-0 z-40">
<div class="absolute inset-0 bg-neutral-900/30" @click="closeEditDrawer()" aria-hidden="true"></div>
<aside class="absolute right-0 top-0 h-full w-[560px] bg-white border-l border-surface shadow-xl flex flex-col">
<header class="px-4 py-3 border-b border-surface flex items-center justify-between">
<div>
<h3 class="text-base font-semibold">编辑楼盘信息</h3>
<p class="text-xs text-muted mt-0.5">部分信息修改会影响房源,请确认后保存</p>
</div>
<button class="p-1.5 rounded-md hover:bg-neutral-100" aria-label="关闭编辑抽屉" @click="closeEditDrawer()">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</header>
<div class="flex-1 overflow-y-auto p-4 space-y-5">
<section class="space-y-3">
<h4 class="text-sm font-semibold">基本信息</h4>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">小区名称 <span class="text-danger-600">*</span></label>
<input type="text" class="w-full px-3 py-2 rounded-md border border-surface bg-neutral-100 text-neutral-500" x-model="editForm.name" readonly />
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">小区地址 <span class="text-danger-600">*</span></label>
<input type="text" class="w-full px-3 py-2 rounded-md border border-surface bg-neutral-100 text-neutral-500" x-model="editForm.address" readonly />
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">物业类型 <span class="text-danger-600">*</span></label>
<select x-model="editForm.propertyUsage" class="w-full px-3 py-2 rounded-md border bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" :class="editErrors.propertyUsage ? 'border-danger-600' : 'border-surface'">
<option value="">请选择物业类型</option>
<option value="residential">住宅</option>
<option value="villa">别墅</option>
<option value="commercial_residential">商住</option>
<option value="commercial">商业</option>
<option value="office">写字楼</option>
</select>
<p class="text-xs text-danger-600 mt-1" x-show="editErrors.propertyUsage" x-text="editErrors.propertyUsage"></p>
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">楼栋结构 <span class="text-danger-600">*</span></label>
<select x-model="editForm.buildingStructure" class="w-full px-3 py-2 rounded-md border bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" :class="editErrors.buildingStructure ? 'border-danger-600' : 'border-surface'">
<option value="">请选择楼栋结构</option>
<option value="unit_room">单元-房号</option>
<option value="other">其他</option>
</select>
<p class="text-xs text-danger-600 mt-1" x-show="editErrors.buildingStructure" x-text="editErrors.buildingStructure"></p>
</div>
</section>
<section class="space-y-2">
<h4 class="text-sm font-semibold">学校信息</h4>
<p class="text-xs text-warning-600">删除学校,所有房源下关联该学校将会被删除(原型说明)</p>
<div class="flex items-center justify-between border border-surface rounded-md px-3 py-2">
<span>真光小学(公立 · 重点)</span>
<button class="text-danger-600 hover:underline" @click="notify('删除学校(原型)')">删除</button>
</div>
<button class="px-3 py-1.5 rounded-md border border-surface hover:bg-neutral-50" @click="notify('新增学校(原型)')">+ 添加</button>
</section>
<section class="space-y-3 pb-24">
<h4 class="text-sm font-semibold">其他信息</h4>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">容积率</label>
<input type="number" step="0.01" x-model="editForm.plotRatio" class="w-full px-3 py-2 rounded-md border border-surface bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" />
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">绿化率(%</label>
<input type="number" step="0.01" x-model="editForm.greenRate" class="w-full px-3 py-2 rounded-md border border-surface bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" />
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">物业费(元/㎡/月)</label>
<input type="number" step="0.01" x-model="editForm.propertyFee" class="w-full px-3 py-2 rounded-md border border-surface bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" />
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">停车位配比</label>
<input type="text" x-model="editForm.parkingRatio" class="w-full px-3 py-2 rounded-md border border-surface bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40" />
</div>
</div>
<div>
<label class="block text-xs font-medium text-neutral-700 mb-1">备注</label>
<textarea rows="3" x-model="editForm.remarks" class="w-full px-3 py-2 rounded-md border border-surface bg-white focus:outline-none focus:ring-2 focus:ring-primary-600/40"></textarea>
</div>
</section>
</div>
<!-- Fixed Footer: right aligned -->
<footer class="absolute bottom-0 left-0 right-0 border-t border-surface bg-white px-4 py-3">
<div class="flex items-center justify-end gap-3">
<button class="inline-flex items-center px-5 py-2 text-sm font-medium rounded-md border border-neutral-300 text-neutral-700 hover:bg-neutral-50" @click="closeEditDrawer()">取消</button>
<button class="inline-flex items-center gap-1.5 px-6 py-2 text-sm font-medium rounded-md bg-primary-600 text-white hover:bg-primary-700 active:bg-primary-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-600/40 disabled:opacity-70 disabled:cursor-wait" :disabled="editSubmitting" @click="saveEdit()">
<svg x-show="editSubmitting" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z"></path></svg>
<span x-text="editSubmitting ? '保存中...' : '保存'"></span>
</button>
</div>
</footer>
</aside>
</div>
<!-- Toast -->
<div x-cloak x-show="toast.show" class="fixed bottom-6 right-6 z-50" x-transition>
<div class="bg-surface border border-surface shadow-lg rounded-lg px-4 py-3 min-w-[260px]">
<p class="text-sm font-medium" x-text="toast.title"></p>
<p class="text-xs text-muted mt-1" x-text="toast.message"></p>
</div>
</div>
<script>
function complexDetailPage() {
return {
activeTab: 'info',
tabs: [
{ key: 'info', label: '楼盘信息' },
{ key: 'buildings', label: '楼栋管理' },
{ key: 'structure', label: '结构管理' },
{ key: 'photos', label: '楼盘照片' },
{ key: 'attachments', label: '楼盘附件' },
{ key: 'facility', label: '周边配套' },
{ key: 'price', label: '楼盘价格走势' },
{ key: 'sales', label: '销控盘' }
],
schools: [
{ name: '真光小学', type: '小学', nature: '公立', level: '重点' },
{ name: '真光中学', type: '中学', nature: '公立', level: '区重点' }
],
buildingKeyword: '',
buildings: [
{ id: 'b1', name: '祁连山南路2727弄101单元', usage: '别墅', year: 2009, floors: '1层', landYears: '70年', elevator: '无', school: '-' },
{ id: 'b2', name: '祁连山南路2727弄57单元', usage: '别墅', year: 2009, floors: '1层', landYears: '70年', elevator: '无', school: '-' },
{ id: 'b3', name: '祁连山南路2727弄59单元', usage: '别墅', year: 2009, floors: '1层', landYears: '70年', elevator: '无', school: '-' },
{ id: 'b4', name: '祁连山南路2727弄61单元', usage: '别墅', year: 2009, floors: '1层', landYears: '70年', elevator: '无', school: '-' },
{ id: 'b5', name: '祁连山南路2727弄63单元', usage: '别墅', year: 2009, floors: '1层', landYears: '70年', elevator: '无', school: '-' }
],
filteredBuildings: [],
selectedBuildingIds: [],
units: [
{ id: 'u1', name: '祁连山南路2727弄101单元' },
{ id: 'u2', name: '祁连山南路2727弄57单元' },
{ id: 'u3', name: '祁连山南路2727弄59单元' },
{ id: 'u4', name: '祁连山南路2727弄61单元' }
],
currentUnitId: 'u2',
roomMap: {
u1: [
{ id: 'r1', floorName: '11层', roomNo: '101' },
{ id: 'r2', floorName: '11层', roomNo: '102' }
],
u2: [
{ id: 'r3', floorName: '11层', roomNo: '57-1' },
{ id: 'r4', floorName: '11层', roomNo: '57-2' }
],
u3: [
{ id: 'r5', floorName: '11层', roomNo: '59-1' }
],
u4: [
{ id: 'r6', floorName: '11层', roomNo: '61-1' }
]
},
selectedRoomIds: [],
photoType: 'complex',
priceView: 'internal',
editDrawerOpen: false,
editSubmitting: false,
editErrors: {},
editForm: {
name: '阳光威尼斯四期(别墅)',
address: '上海普陀金鼎路1600弄,祁连山南路2727弄',
propertyUsage: 'villa',
buildingStructure: 'unit_room',
plotRatio: '1.60',
greenRate: '45',
propertyFee: '1.45',
parkingRatio: '100:63',
remarks: '别墅区结构完整,建议补全学校关系。'
},
toast: {
show: false,
title: '',
message: ''
},
init() {
this.filteredBuildings = [...this.buildings];
},
get selectedBuildingCount() {
return this.selectedBuildingIds.length;
},
get allBuildingsOnPage() {
return this.filteredBuildings.length > 0 && this.filteredBuildings.every(row => this.selectedBuildingIds.includes(row.id));
},
toggleBuildingSelect(id, checked) {
if (checked) {
if (!this.selectedBuildingIds.includes(id)) this.selectedBuildingIds.push(id);
} else {
this.selectedBuildingIds = this.selectedBuildingIds.filter(x => x !== id);
}
},
toggleBuildingSelectPage(checked) {
const ids = this.filteredBuildings.map(r => r.id);
if (checked) {
this.selectedBuildingIds = Array.from(new Set([...this.selectedBuildingIds, ...ids]));
} else {
this.selectedBuildingIds = this.selectedBuildingIds.filter(id => !ids.includes(id));
}
},
applyBuildingFilter() {
const kw = this.buildingKeyword.toLowerCase();
this.filteredBuildings = this.buildings.filter(row => !kw || row.name.toLowerCase().includes(kw));
this.selectedBuildingIds = this.selectedBuildingIds.filter(id => this.filteredBuildings.some(r => r.id === id));
},
switchToStructure(unitName) {
const target = this.units.find(u => u.name === unitName);
if (target) this.currentUnitId = target.id;
this.activeTab = 'structure';
this.notify('已切换到结构管理分区');
},
get currentUnitName() {
const row = this.units.find(u => u.id === this.currentUnitId);
return row ? row.name : '-';
},
get currentRooms() {
return this.roomMap[this.currentUnitId] || [];
},
get selectedRoomCount() {
return this.selectedRoomIds.length;
},
get allRoomsSelected() {
return this.currentRooms.length > 0 && this.currentRooms.every(r => this.selectedRoomIds.includes(r.id));
},
toggleRoom(id, checked) {
if (checked) {
if (!this.selectedRoomIds.includes(id)) this.selectedRoomIds.push(id);
} else {
this.selectedRoomIds = this.selectedRoomIds.filter(x => x !== id);
}
},
toggleAllRooms(checked) {
const ids = this.currentRooms.map(r => r.id);
if (checked) {
this.selectedRoomIds = Array.from(new Set([...this.selectedRoomIds, ...ids]));
} else {
this.selectedRoomIds = this.selectedRoomIds.filter(id => !ids.includes(id));
}
},
openEditDrawer() {
this.editDrawerOpen = true;
this.editErrors = {};
},
closeEditDrawer() {
this.editDrawerOpen = false;
this.editErrors = {};
this.editSubmitting = false;
},
saveEdit() {
this.editErrors = {};
if (!this.editForm.propertyUsage) this.editErrors.propertyUsage = '请选择物业类型';
if (!this.editForm.buildingStructure) this.editErrors.buildingStructure = '请选择楼栋结构';
if (Object.keys(this.editErrors).length > 0) return;
this.editSubmitting = true;
setTimeout(() => {
this.editSubmitting = false;
this.editDrawerOpen = false;
this.notify('楼盘信息已保存(原型)');
}, 550);
},
notify(message) {
this.toast.title = '操作反馈';
this.toast.message = message;
this.toast.show = true;
clearTimeout(this.__toastTimer);
this.__toastTimer = setTimeout(() => (this.toast.show = false), 1800);
}
};
}
</script>
</body>
</html>

View File

@@ -6,7 +6,7 @@
你的核心方法论:**以终为始**——从用户需求出发,验证每一层设计决策是否正确传导,找出断层、矛盾和遗漏。
你的 Review 不是挑错,而是帮助团队在编码前发现最高代价的问题。
**工作目录**`~/Workspace/nexus`
**工作目录**`/mnt/d/Workspace/nexus`
**你的职责边界**
- ✅ 负责:跨文档一致性检查、设计风险识别、遗漏场景挖掘、改进建议输出
@@ -29,54 +29,80 @@
请读取以下文档,作为本次 Review 的全部输入:
**产品文档PRD**
- PRD_MVP: `Project/fonrey/PRD/PRD_MVP.md`
- 房源管理PRD: `Project/fonrey/PRD/房源管理/房源管理模块PRD.md`
- 楼盘管理PRD: `Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md`
- 客源管理PRD: `Project/fonrey/PRD/客源管理/客源管理模块PRD.md`
- 权限管理PRD: `Project/fonrey/PRD/权限管理/权限管理模块PRD.md`
- 组织人事管理PRD: `Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md`
- 系统管理PRD: `Project/fonrey/PRD/系统管理/系统管理模块PRD`
- 系统管理PRD: `Project/fonrey/PRD/系统管理/系统管理模块PRD.md`
- 登录管理PRD: `Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md`
- 发布管理PRD: `Project/fonrey/PRD/发布管理/客户端发布管理模块PRD.md`
**技术文档**
- TECH_STACK`Project/fonrey/TECH_STACK/TECH_STACK.md`
- 项目总TECH_STACK`Project/fonrey/TECH_STACK/TECH_STACK.md`
- 登录管理技术方案:`Project/fonrey/TECH_STACK/登录管理技术方案.md`
- 权限管理技术方案:`Project/fonrey/TECH_STACK/登录管理技术方案.md`
- 测试规范`Project/fonrey/TECH_STACK/测试规范`
- 房源管理技术方案`Project/fonrey/TECH_STACK/房源管理技术方案.md`
- 楼盘管理技术方案:`Project/fonrey/TECH_STACK/楼盘管理技术方案.md`
- 客源管理技术方案:`Project/fonrey/TECH_STACK/客源管理技术方案.md`
- 系统管理技术方案: `Project/fonrey/TECH_STACK/系统管理技术文档.md`
- 系统设置技术方案:`Project/fonrey/TECH_STACK/系统设置技术方案.md`
- API契约规范`Project/fonrey/TECH_STACK/API_CONTRACT.md`
- 测试规范:`Project/fonrey/TECH_STACK/测试规范.md`
**数据模型**
- DATA_MODEL`Project/fonrey/DATA_MODEL/DATA_MODEL.md`
- 项目总DATA_MODEL`Project/fonrey/DATA_MODEL/DATA_MODEL.md`
- 登录DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN`
- 房源 DATA_MODDL: `Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 客源 DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 楼盘 DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 组织人事DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 权限DATA_MODEL:`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 系统管理DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md`
- 系统设置DATA_MODEL: `Project/fonrey/DATA_MODEL/DATA_MODEL_SETTING`
- 统一枚举字典:`Project/fonrey/DATA_MODEL/ENUMS.md`
**UI 设计文档**
- UI SYSTEM`Project/fonrey/UI_SYSTEM/UI_SYSTEM.md`
- 组件清单:`Project/fonrey/UI_SYSTEM/组件清单.md`
- 组件设计规范:`Project/fonrey/UI_SYSTEM/组件规范设计.md`
- 模块UI Design:
- `Project/fonrey/UI_DESIGN/客源列表_UI.md`
- `Project/fonrey/UI_DESIGN/客源详情_UI.md`
- `Project/fonrey/UI_DESIGN/源管理/新增客源_UI`
- `Project/fonrey/UI_DESIGN/源管理/编辑客源_UI`
- `Project/fonrey/UI_DESIGN/源管理/房源列表_UI`
- `Project/fonrey/UI_DESIGN/登录管理/登录_UI.md`
- `Project/fonrey/UI_DESIGN/房源管理/房源列表_UI.md`
- `Project/fonrey/UI_DESIGN/源管理/房源详情_UI.md`
- `Project/fonrey/UI_DESIGN/源管理/新增房源_UI.md`
- `Project/fonrey/UI_DESIGN/源管理/编辑客源_UI.md`
- `Project/fonrey/UI_DESIGN/客源管理/客源列表_U.mdI
- `Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- `Project/fonrey/UI_DESIGN/客源管理/新增客源_UI.md`
- `Project/fonrey/UI_DESIGN/楼盘管理/楼盘列表_UI.md`
- `Project/fonrey/UI_DESIGN/楼盘管理/楼盘详情_UI.md`
- `Project/fonrey/UI_DESIGN/UI_设计任务总表.md`
**UI 原型页面**
- UI SYSTEM: `Project/fonrey/UI_DESIGN/preview.html`
- 总体页面预览: `Project/fonrey/UI_DESIGN/preview.html`
- Tenant登录`Project/fonrey/UI_DESIGN/登录_UI.html`
- 经纪人登录:`Project/fonrey/UI_DESIGN/登录_账号密码_UI.html`
- 客源列表:`Project/fonrey/UI_DESIGN/客源列表_UI.html`
- 客源详情:`Project/fonrey/UI_DESIGN/客源详情_UI.html`
- 编辑客源:`Project/fonrey/UI_DESIGN/编辑客源_UI.html`
- 新增客源:`Project/fonrey/UI_DESIGN/新增客源_UI.html`
- 房源列表:`Project/fonrey/UI_DESIGN/房源列表_UI.html`
- 房源详情:`Project/fonrey/UI_DESIGN/房源详情_UI.html`
- 楼盘列表:`Project/fonrey/UI_DESIGN/楼盘列表_UI.html`
- 楼盘详情:`Project/fonrey/UI_DESIGN/楼盘详情_UI.html`
**任务汇总**
- TASK`Project/fonrey/PRD/TASK.md`
- 任务总表(优化提示词)`Project/fonrey/PRD/TASK_AGENT_READY.md`
- P0任务总表(优化提示词)`Project/fonrey/PRD/TASK_AGENT_READY_P0.md`
**其他参考文档**
- 历史Review报告
- `Project/fonrey/REVIEW/REVIEW_全局_2026-04-25.md`
- `Project/fonrey/REVIEW/REVIEW_全局_2026-04-26.md`
---

View File

@@ -0,0 +1,78 @@
# API_CONTRACT 冲突提报模板(可复用)
> 版本v1.02026-04-28
> 适用范围:`PRD/TASK_AGENT_READY*.md`、模块级 Agent 执行提示词、代码交付回执
---
## 1) 目的
当实现与 `Project/fonrey/TECH_STACK/API_CONTRACT.md` 存在差异时,统一使用本模板提报,确保:
- 冲突可审计(知道“哪里不一致”)
- 影响可评估(前端/后端/测试/数据)
- 处置可回滚(不阻断主干稳定性)
> 约束:**不得擅自修改 API_CONTRACT**。先按契约实现;若确需调整,必须先提报并待确认。
---
## 2) 可直接粘贴到任务提示词的交付片段
将以下段落放入每条任务的 `【交付格式】`
```md
- API 契约核对清单(逐条对应 API_CONTRACT已对齐/偏差项)
- API 契约冲突说明(如无冲突写“无”)
- 冲突接口Method + Path
- 当前契约要求(来自 API_CONTRACT
- 实现差异 / 建议改动
- 影响面(前端/后端/测试/存量数据)
- 回滚方案
```
---
## 3) API 契约冲突提报单(标准格式)
> 若“无冲突”,填写:`API 契约冲突说明:无`
```md
### API 契约冲突提报单
- 任务ID<US-XXX>
- 关联模块:<登录/楼盘/房源/客源/...>
- 冲突接口Method + Path
- <GET|POST|PUT|PATCH|DELETE> <path>
- 当前契约要求(来自 API_CONTRACT
- 请求参数:<必填/可选/类型/约束>
- 响应结构:<字段、类型、是否可空>
- 错误码:<code + 含义>
- 实现差异 / 建议改动:
- 差异点1<描述>
- 差异点2<描述>
- 建议:<保持契约 / 调整契约(需审批)>
- 影响面:
- 前端:<页面/组件/调用方>
- 后端:<视图/序列化器/服务层>
- 测试:<需新增/需修正的用例>
- 存量数据:<是否需要迁移/回填>
- 风险等级:<低/中/高>
- 临时兜底方案:<可选>
- 回滚方案:<可执行步骤>
- 需要确认人:<产品/后端负责人/前端负责人>
- 期望确认时限:<YYYY-MM-DD HH:mm>
```
---
## 4) 最小核对清单(提交前自检)
- [ ] 已逐条对照 API_CONTRACT 的路径/方法/参数/响应/错误码
- [ ] 已明确“无冲突”或填写完整冲突提报单
- [ ] 已写明影响面(前端/后端/测试/数据)
- [ ] 已给出可执行回滚方案
- [ ] 未直接修改 API_CONTRACT 文档内容

View File

@@ -0,0 +1,10 @@
## 面向中小型房产经纪中介公司的信息管理系统
## 近乎零成本的房产经纪管理系统
## AI技术让你和大公司平台拉平信息差
## 小成本拉平信息差