文档更新

This commit is contained in:
2026-06-04 14:34:32 +08:00
parent 7b91c310eb
commit e01ca79bdc
7 changed files with 1036 additions and 2 deletions

View File

@@ -13,6 +13,8 @@
| 2026-04-30 | Atlas | 初始化 ADR 动态决策记录;补录当日关键技术与需求决策 |
| 2026-05-02 | Sisyphus | 新增 ADR-20260502-001合并系统管理与客户端发布两份 PRD 为统一的『平台管理后台 PRD』原文件删除 |
| 2026-05-02 | Sisyphus | 新增 ADR-20260502-003定义『PRD 与 Tech 文档职责边界』规则PRD 管 what/why、Tech 管 how首次落地于登录管理 PRD v3.0 |
| 2026-06-04 | Sisyphus | 新增 ADR-20260604-001列表分页全局采用 Keyset(cursor),允许 page-based 作过渡兼容;同步修订 `UI_DESIGN/ROUTES.md``UI_DESIGN/房源管理/房源列表_UI.md` |
| 2026-06-04 | Sisyphus | 新增 ADR-20260604-002ROUTES.md 客源条目"重复客源(成交)"与"重复客源(公客)"合并为单条 `/clients/duplicates/?tab=transacted\|public`,客源条目数稳定为 19 |
## 一、记录规范(必须遵守)
@@ -247,6 +249,43 @@
---
## 2026-06-04
### ADR-20260604-001
- **类型**TECH
- **模块**:列表分页(全局)
- **状态**accepted
- **背景**`AGENTS.md` §4.5 与 `UI_DESIGN/ROUTES.md` §1 全局约定明文要求"所有列表查询必须使用 Keyset 分页(参数禁止包含 `offset`,统一使用 `cursor`"。但 `UI_DESIGN/房源管理/房源列表_UI.md` 现存示例(行 97 query params 列表、行 741 HTMX `hx-vals='{"page": N}'`)仍是 page-based 风格与全局规范冲突。MVP 阶段一次性切到 Keyset 会拖慢前端模板与示例代码改造节奏,且页码 UI`[1][2][3]...[N]`)已被多份截图与组件清单采用。
- **决策**
1. **目标口径**:列表分页长期统一为 Keyset(cursor);新增列表页与新增分页 API 不得使用 page/offset。
2. **过渡兼容**MVP Phase 1 期间,已有 page-based 示例的模块文档与代码可保留 page UI 表现(页码导航 + 跳页),但**数据获取层必须支持 cursor 参数**;后端 API 接受 `cursor` 为主参数,`page` 仅作过渡兼容输入且不得用于 1000 条以上数据集。
3. **文档承接**`UI_DESIGN/ROUTES.md` §1 通用约定新增"过渡兼容"说明;`UI_DESIGN/房源管理/房源列表_UI.md` 示例代码改为 `cursor`,分页 UI 表现保留。
4. **退出条件**MVP GA 后 1 个版本内,所有 page-based 示例必须清理;届时新增 ADR 关闭本过渡条款。
- **影响范围**
- `UI_DESIGN/ROUTES.md` §1
- `UI_DESIGN/房源管理/房源列表_UI.md` §2.1.1、§2.1.3 分页栏
- 后续所有列表页 HTMX 模板与 API 视图
- **关联文档**
- `AGENTS.md` §4.5
- `UI_DESIGN/ROUTES.md`
- `UI_DESIGN/房源管理/房源列表_UI.md`
- **备注**B-04Keyset 分页规范缺位)由本 ADR 部分关闭;剩余的列表索引矩阵补全留待后续 Major 修订。
### ADR-20260604-002
- **类型**REQ
- **模块**:客源管理(路由汇总)
- **状态**accepted
- **背景**`UI_DESIGN/ROUTES.md` §5 客源条目数与历史 handoff 口径"客源 19 项"不一致——实际列出 20 行,多出的 1 行来自将"重复客源(成交)"与"重复客源(公客)"拆分为两条独立路由。两者复用同一列表页结构、同一权限范围、同一组件清单,仅业务子集不同,符合 ROUTES.md 全局约定中"Tab 维度统一用 `?tab=` 表达"的模式。
- **决策**:将两条重复客源路由合并为单条 `/clients/duplicates/`,子集通过 `?tab=transacted|public` 区分;客源条目总数稳定为 19。
- **影响范围**
- `UI_DESIGN/ROUTES.md` §5合并 2 行为 1 行)
- **关联文档**
- `UI_DESIGN/ROUTES.md`
- `UI_DESIGN/客源管理/客源列表_UI.md`
- **备注**:本 ADR 不变更任何业务规则,仅统一 URL 表达形式与条目计数。
---
## 三、按模块分类记录(视图索引)
## 3.1 测试治理(全局)
@@ -274,6 +313,12 @@
- `ADR-20260502-001`:合并系统管理 PRD 与客户端发布 PRD 为统一的『平台管理后台 PRD』REQ
- `ADR-20260502-002`合并系统管理技术文档与客户端发布技术方案为统一的『平台管理后台技术方案』TECH
## 3.7 列表分页(全局)
- `ADR-20260604-001`Keyset(cursor) 为目标口径MVP 期允许 page-based 作过渡兼容TECH
## 3.8 客源管理(路由)
- `ADR-20260604-002`:重复客源合并为单条 `/clients/duplicates/?tab=transacted|public`,客源条目数=19REQ
---
## 四、历史记录Append-Only Log
@@ -295,6 +340,8 @@
| ADR-20260502-001 | 2026-05-02 | 平台管理后台 | REQ | accepted | 合并系统管理 PRD + 客户端发布 PRD 为『平台管理后台 PRD』原文件删除 | `PRD/平台管理后台/平台管理后台PRD.md` |
| ADR-20260502-002 | 2026-05-02 | 平台管理后台 | TECH | accepted | 合并系统管理技术文档 + 客户端发布技术方案为『平台管理后台技术方案』(覆盖技术选型/页面路由表/API 设计三维度),原文件删除 | `TECH_STACK/平台管理后台技术方案.md` |
| ADR-20260502-003 | 2026-05-02 | 文档治理 | REQ | accepted | PRD 管 what/why、Tech 管 howPRD 必须移出 API 路径/Redis Key/字段类型/框架 API 等实现细节,由 Tech 与 DATA_MODEL 承接 | `PRD/登录管理/用户登录管理模块PRD.md` v3.0 |
| ADR-20260604-001 | 2026-06-04 | 列表分页(全局) | TECH | accepted | Keyset(cursor) 为目标口径MVP 期允许 page-based 作过渡兼容 | `UI_DESIGN/ROUTES.md``UI_DESIGN/房源管理/房源列表_UI.md` |
| ADR-20260604-002 | 2026-06-04 | 客源管理(路由) | REQ | accepted | 重复客源合并为单条 `/clients/duplicates/?tab=transacted\|public`,客源条目数=19 | `UI_DESIGN/ROUTES.md` |
---

View File

@@ -0,0 +1,322 @@
> For AI assistants: Read this entire file before writing any code. All decisions here are final.
# Fonrey — 统一状态机规范 (STATE_MACHINE)
**定位**:本文档是 Fonrey 项目所有实体生命周期与状态流转的唯一权威定义。
**变更历史**
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-06-04 | Sisyphus | 初始版本:整合各模块状态机逻辑,对齐 ENUMS v2.2 |
---
## 1. 通用约定
- **状态值命名**:统一采用 `lower_snake_case`,必须与 `DATA_MODEL/ENUMS.md` 完全一致。
- **触发动作**状态迁移必须由显式的动作Action或事件Event触发。
- **操作日志**:所有关键状态迁移(除系统内部中间态外)必须记录对应的 `status_log``audit_log` 表。
- **不可逆规则**:标注为“终态”的状态禁止迁回初始态或中间态。
- **权限要求**:状态迁移接口必须校验操作者的 DataScope 与 Function Permission。
---
## 2. 租户生命周期 (Public.Tenant)
**实体**: `public.tenants` | **字段**: `status`
**参考**: `DATA_MODEL_PUBLIC.md` §2.1, §4.1
```mermaid
stateDiagram-v2
[*] --> creating
creating --> active: 初始化完成
creating --> failed: 初始化失败
active --> suspended: 逾期/违规/申请
suspended --> active: 恢复条件满足
active --> pending_delete: 申请注销
suspended --> pending_delete: 申请注销
pending_delete --> deleted: 30天后/人工确认
deleted --> [*]
```
### 状态枚举值
(domain: `public.tenant.status`)
- `creating`: 创建中
- `active`: 正常
- `suspended`: 已挂起
- `pending_delete`: 待删除
- `deleted`: 已删除
- `failed`: 创建/初始化失败
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `creating` | `active` | 租户 Schema 初始化成功 | System | 写 `tenant_status_logs` | 否 |
| `creating` | `failed` | 初始化脚本执行失败 | System | 记录错误日志 | 否 |
| `active` | `suspended` | 欠费/违规/License过期 | System/SuperAdmin | 设置 `suspended_until`, 写日志 | 是 |
| `suspended` | `active` | 缴费成功/解封/到期恢复 | System/SuperAdmin | 清除挂起原因 | 是 |
| `active`/`suspended` | `pending_delete` | 提交注销申请 | TenantAdmin | 设置 `deleted_at` | 是 |
| `pending_delete` | `deleted` | 确认注销/静默期结束 | SuperAdmin/System | 物理删除或深度标记 | 否 |
**来源章节**: `DATA_MODEL_PUBLIC.md` §4.1
---
## 3. 客源状态机 (Client)
**实体**: `client.clients` | **字段**: `status`
**参考**: `DATA_MODEL_CLIENT.md` §3.1, §4 | `ENUMS.md` §3.6
```mermaid
stateDiagram-v2
[*] --> buying
[*] --> renting
[*] --> buy_or_rent
state "活跃态" as Active {
buying
renting
buy_or_rent
}
Active --> suspended: 暂时无需求
suspended --> Active: 重新激活
Active --> public: 手动转公/超时自动
suspended --> public: 手动转公
Active --> bought: 转成交(我购)
Active --> rented_done: 转成交(我租)
Active --> invalid: 转无效
suspended --> invalid: 转无效
invalid --> Active: 经理审批恢复
bought --> [*]
rented_done --> [*]
public --> [*]
```
### 状态枚举值
(domain: `client.status`)
- `buying`: 求购
- `renting`: 求租
- `buy_or_rent`: 租购
- `suspended`: 暂缓
- `bought`: 已购 (我购)
- `rented_done`: 已租 (我租)
- `public`: 公客
- `invalid`: 无效
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `buying`/`renting`/`buy_or_rent` | `suspended` | 修改状态为暂缓 | 经纪人 | 写 `client_status_logs` | 是 |
| `suspended` | `buying`/`renting`/`buy_or_rent` | 重新激活需求 | 经纪人 | 更新 `last_active_at` | 是 |
| `Active`/`suspended` | `public` | 手动转公/超时自动转公 | 经纪人/System | 修改 `client_type='public'` | 否 |
| `Active` | `bought`/`rented_done` | 录入成交合同 | 经纪人 | 修改 `client_type='transacted'` | 否 |
| `Active`/`suspended` | `invalid` | 标记无效 | 经纪人 | 记录 `invalid_reason` | 需审批 |
### 禁迁规则
- `transacted` (bought/rented_done) 状态为绝对终态,禁止迁往任何其他状态。
- `public` 状态客源禁止直接修改为 `private` 状态下的需求状态,必须走领用流程。
**来源章节**: `DATA_MODEL_CLIENT.md` §4, `ENUMS.md` §3.6
---
## 4. 房源状态机 (Property)
**实体**: `property.properties` | **字段**: `status`
**参考**: `DATA_MODEL_PROPERTY.md` §3, §4.1
```mermaid
stateDiagram-v2
[*] --> unlisted
unlisted --> for_sale: 挂牌出售
unlisted --> for_rent: 挂牌出租
state "在盘态" as Listed {
for_sale
for_rent
for_sale_rent
}
Listed --> suspended: 业主暂缓
suspended --> Listed: 重新上架
Listed --> sold: 本司成交
Listed --> sold_elsewhere: 他售
Listed --> rented_elsewhere: 他租
Listed --> unlisted: 下架
sold --> [*]
sold_elsewhere --> [*]
```
### 状态枚举值
(domain: `property.status`)
- `for_sale`: 出售
- `for_rent`: 出租
- `for_sale_rent`: 租售
- `suspended`: 暂缓
- `sold_elsewhere`: 他售
- `rented_elsewhere`: 他租
- `sold`: 成交
- `unlisted`: 未挂牌
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `unlisted` | `for_sale`/`for_rent` | 房源挂牌 | 经纪人 | 新增 `listing_histories` | 是 |
| `Listed` | `suspended` | 业主通知暂缓 | 经纪人 | 写跟进日志 | 是 |
| `Listed` | `sold` | 本司录入成交 | 经纪人 | 自动结束挂牌历史 | 否 |
| `Listed` | `sold_elsewhere`/`rented_elsewhere` | 核实他司已成交 | 经纪人 | | 否 |
| `Listed` | `unlisted` | 强制下架 | 管理员 | | 是 |
**来源章节**: `DATA_MODEL_PROPERTY.md` §3
---
## 5. 客户/房源等级 (Grade)
**实体**: `client.clients` / `property.properties` | **字段**: `grade`
**参考**: `DATA_MODEL_CLIENT.md` §3.1 | `DATA_MODEL_PROPERTY.md` §4.1
### 状态枚举值
(domain: `client.grade` / `property.grade`)
- `A`: A (急迫)
- `B`: B (较强)
- `C`: C (一般)
- `D`: D (较弱)
- `E`: E (暂不关注) - 仅客源
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
| ---- | ------------------- | ------- | ---- | ----------------------------- | ---- |
| 任意状态 | `A`/`B`/`C`/`D`/`E` | 手动调整等级 | 经纪人 | 写 `status_log` / `follow_log` | 是 |
**来源章节**: `DATA_MODEL_CLIENT.md` §4, `DATA_MODEL_PROPERTY.md` §3
---
## 6. 账号状态机 (Account)
**实体**: `login.user_accounts` | **字段**: `status`
**参考**: `DATA_MODEL_LOGIN.md` §5.1
```mermaid
stateDiagram-v2
[*] --> active: 创建(初始密码)
active --> locked: 密码错误≥5次
locked --> active: 30min到期/手动解锁
active --> disabled: 离职/手动停用
disabled --> active: 复职/手动恢复
```
### 状态枚举值
(domain: `login.user_account.status`)
- `active`: 启用
- `disabled`: 停用
- `locked`: 锁定
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `active` | `locked` | 密码连续错误5次 | System | 设置 `locked_until` | 是 |
| `locked` | `active` | 时间到期/管理员解锁 | System/Admin | | 是 |
| `active` | `disabled` | 员工离职/手动停用 | Admin/System | 踢出所有 Session | 是 |
**来源章节**: `DATA_MODEL_LOGIN.md` §5.1
---
## 7. 升级事件状态机 (Upgrade Event)
**实体**: `public.upgrade_events` | **字段**: `status`
**参考**: `DATA_MODEL_PUBLIC.md` §4.2
```mermaid
stateDiagram-v2
[*] --> draft
draft --> pre_check: 提交
pre_check --> pre_backup: 健康检查通过
pre_backup --> batch_running: 备份完成
batch_running --> batch_done: 批次租户成功
batch_done --> batch_running: 下一批次
batch_done --> succeeded: 全部完成
batch_running --> halted: 失败/门控拦截
halted --> batch_running: 人工继续
halted --> rollback_running: 人工回滚
rollback_running --> rolled_back: 回滚完成
batch_running --> failed: 严重故障
```
### 状态枚举值
(domain: `public.upgrade_event.status`)
- `draft`: 草稿
- `pre_check`: 预检查
- `pre_backup`: 预备份
- `batch_running`: 批次执行中
- `batch_done`: 批次完成
- `halted`: 已暂停
- `succeeded`: 已成功
- `failed`: 失败
- `rollback_running`: 回滚中
- `rolled_back`: 已回滚
**来源章节**: `DATA_MODEL_PUBLIC.md` §4.2
---
## 8. 其他辅助状态
### 8.1 备份/导出任务 (Backup/Export)
**字段**: `status` | **参考**: `ENUMS.md` §2.3
- `pending` -> `in_progress` -> `success` / `failed`
### 8.2 客户端发布 (Client Release)
**字段**: `status` | **参考**: `ENUMS.md` §2.4
- `draft` -> `published` -> `archived`
- **约束**: 同平台架构下仅允许一个 `published`
### 8.3 员工状态 (Staff)
**字段**: `status` | **参考**: `DATA_MODEL_ORG.md` §4
- `probation` -> `active` -> `resigned`
- `active` <-> `frozen`
### 8.4 审批流 (Approval)
**实体**: `number_holder_approvals` | **字段**: `status`
- `pending` -> `approved` / `rejected`
---
## X. 状态机一致性校验清单
1. **枚举同步**: 状态字段值是否在 `ENUMS.md` 中定义且拼写一致?
2. **日志完备**: 每次 `status` 变更是否伴随对应的 `status_log` 插入动作?
3. **并发控制**: 状态变更是否包含 `version` 乐观锁检查?
4. **终态拦截**: 业务逻辑是否显式禁止从终态(如 `sold`, `deleted`, `rolled_back`)向外迁移?
5. **副作用触发**: 状态变更相关的异步任务(如转公客通知、缓存清理)是否已在 Service 层注册?
---
## 待澄清问题
1. **Client Invalid 恢复路径**: `DATA_MODEL_CLIENT.md` §4 提到“需经理审批后可恢复”,但未定义恢复后的目标状态(是恢复到 `suspended` 还是之前的活跃态?)。
- *涉及文件*: `DATA_MODEL_CLIENT.md`
2. **Property Unlisted 属性**: 房源在 `unlisted` 状态下,其 `attribute` (公/私盘) 是否有意义?若从 `private` 挂牌变为 `unlisted`,再次挂牌时是否应默认保留 `private` 属性?
- *涉及文件*: `DATA_MODEL_PROPERTY.md`
3. **Tenant Deleted 物理性**: `DATA_MODEL_PUBLIC.md` §4.1 提到“硬删除直接到 deleted”但在 §2.1 又说“硬删除直接物理删除行”。需要明确 `deleted` 状态是代表软删除的终点还是物理删除前的标记。
- *涉及文件*: `DATA_MODEL_PUBLIC.md`
4. **Staff Resigned 恢复**: 离职员工复职(`rejoin`)后,其 `status` 是回到 `probation` 还是 `active`
- *涉及文件*: `DATA_MODEL_ORG.md`

View File

@@ -36,6 +36,9 @@
| 技术总纲 | [`TECH_STACK/TECH_STACK.md`](./TECH_STACK/TECH_STACK.md) | 跨模块技术决策、模块索引、测试策略 |
| 数据模型总览 | [`DATA_MODEL/DATA_MODEL.md`](./DATA_MODEL/DATA_MODEL.md) | 全局数据架构与子文档索引 |
| API 全局契约 | [`TECH_STACK/API_CONTRACT.md`](./TECH_STACK/API_CONTRACT.md) | 请求/响应、错误码、分页、上传下载规范 |
| 本地开发与启动 | [`TECH_STACK/本地开发手册.md`](./TECH_STACK/本地开发手册.md) | 前置依赖、环境变量、`django-tenants` 初始化、种子数据、本地启动与验证 |
| 路由与组件映射 | [`UI_DESIGN/ROUTES.md`](./UI_DESIGN/ROUTES.md) | 全局唯一权威:模块 → 页面 → URL → 复用组件 → 四态 |
| 状态机权威 | [`DATA_MODEL/STATE_MACHINE.md`](./DATA_MODEL/STATE_MACHINE.md) | 全局唯一权威:所有实体生命周期与状态迁移规则 |
| 测试规范 | [`TECH_STACK/测试规范.md`](./TECH_STACK/测试规范.md) | 测试流程、报告字段、CI 要求 |
| 测试编号规则 | [`TEST_CASES/TEST_CASE_ID_SPEC.md`](./TEST_CASES/TEST_CASE_ID_SPEC.md) | `TC-FON-XXXXXX` 规则 |
| 测试注册表 | [`TEST_CASES/TEST_CASE_REGISTRY.md`](./TEST_CASES/TEST_CASE_REGISTRY.md) | 测试编号水位与批次登记 |

View File

@@ -0,0 +1,260 @@
> For AI assistants: Read this entire file before writing any code. All decisions here are final.
# 本地开发手册 (Local Development Manual)
本手册为 Fonrey 项目本地环境搭建与开发验证的权威指南。旨在帮助新入场开发人员或 AI agent 快速启动项目并确保开发环境与生产环境的技术决策一致。
## §0 文档定位 + 变更历史表
| 日期 | 变更人 | 变更内容 | 版本 |
|---|---|---|---|
| 2026-06-04 | Sisyphus-Junior | 初始化本地开发手册,整合 ADR 与技术总纲决策 | v1.0 |
---
## §1 前置依赖
运行 Fonrey 前,请确保本地环境已安装以下基础组件:
- **操作系统**: 推荐 Linux (Ubuntu 22.04+) 或 macOS。Windows 用户建议使用 WSL2。
- **Python**: 3.12+
- **Node.js**: 20.x+ (用于 Tailwind CSS 编译)
- **Docker & Docker Compose**: 必须,用于运行数据库、缓存与消息队列。
- **PostgreSQL**: 16.x (通过 Docker 运行)
- **Redis**: 7.x (通过 Docker 运行)
- **浏览器**: 最新版 Google Chrome 或 Chromium (用于 Playwright E2E 测试)
---
## §2 仓库初始化
按照以下步骤克隆仓库并安装依赖:
```bash
# 克隆仓库
git clone <repository_url> fonrey
cd fonrey
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/macOS
# .\venv\Scripts\activate # Windows
# 安装 Python 依赖
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements/test.txt # 仅开发环境需要
# 安装前端依赖
npm install
```
---
## §3 环境变量 (`.env`)
在项目根目录创建 `.env` 文件。以下为必需项清单及示例值:
```bash
# Django 基础配置
DEBUG=True
SECRET_KEY=django-insecure-development-key-change-me
ALLOWED_HOSTS=localhost,127.0.0.1,.fonrey.test
# 数据库配置 (需配合 Docker Compose)
DATABASE_URL=postgres://fonrey_user:fonrey_password@localhost:5432/fonrey_db
# 如果使用 PgBouncer
# DATABASE_URL=postgres://fonrey_user:fonrey_password@localhost:6432/fonrey_db
# Redis 配置
REDIS_URL=redis://localhost:6379/0
# Celery 配置
CELERY_BROKER_URL=redis://localhost:6379/1
CELERY_RESULT_BACKEND=redis://localhost:6379/1
# Cloudflare R2 / S3 存储 (本地开发推荐使用 MinIO)
AWS_ACCESS_KEY_ID=minio_admin
AWS_SECRET_ACCESS_KEY=minio_password
AWS_STORAGE_BUCKET_NAME=fonrey-media
AWS_S3_ENDPOINT_URL=http://localhost:9000
AWS_S3_REGION_NAME=auto
# Sentry (本地开发可选)
SENTRY_DSN=
# 加密密钥 (AES-256-GCM必须为 32 字节 Base64)
FIELD_ENCRYPTION_KEY=your-32-byte-base64-encoded-key
```
---
## §4 数据库与缓存启动
推荐使用 Docker Compose 快速拉起基础服务。项目根目录应包含 `docker-compose.yml`
```bash
# 启动 PostgreSQL, Redis, MinIO
docker-compose up -d
# 验证服务状态
docker-compose ps
```
**PgBouncer 提示**: 生产环境强制使用 PgBouncer 进行连接池管理。本地开发环境下,如果需要模拟高并发连接,可开启 docker-compose 中的 pgbouncer 服务并将 `DATABASE_URL` 指向 6432 端口。
---
## §5 django-tenants 初始化
由于项目采用 `django-tenants` 多租户架构,必须先初始化公共 Schema (public schema)
```bash
# 1. 运行公共 Schema 的迁移
python manage.py migrate_schemas --shared
# 2. 创建第一个租户 (手动交互或使用脚本)
# 注意:在本地开发时,建议将租户域名映射到 .fonrey.test
```
---
## §6 测试租户与种子数据
目前项目尚未完成自动化 seed 命令。建议通过以下方式手动创建测试环境:
### 6.1 创建初始租户 (建议命令)
*预期放置位置: `apps/tenant/management/commands/setup_dev_tenant.py`*
```bash
# 尚未实现,需后续补写。目前请进入 python shell 手动创建:
python manage.py shell
>>> from apps.tenant.models import Tenant, Domain
>>> tenant = Tenant.objects.create(schema_name='test_tenant', name='测试经纪公司')
>>> Domain.objects.create(domain='test.fonrey.test', tenant=tenant, is_primary=True)
```
### 6.2 导入枚举种子数据
```bash
# 导入 ENUMS.md 定义的公共枚举值
python manage.py loaddata shared/fixtures/enum_labels.json
```
---
## §7 后端启动
本地开发环境直接使用 Django 开发服务器即可。生产环境必须使用 Gunicorn + Uvicorn。
```bash
# 本地开发模式
python manage.py runserver 8000
```
---
## §8 Celery 与 Celery Beat 启动
涉及异步任务(如图片处理、导出、配房)时,必须启动 Worker 和 Beat
```bash
# 启动 Worker
celery -A core worker -l info
# 启动 Beat (定时任务)
celery -A core beat -l info
```
---
## §9 前端资源
Fonrey 依赖 Tailwind CSS 进行样式构建。
```bash
# 开发模式:监听文件变化并实时编译
npm run dev
# 生产构建
npm run build
```
---
## §10 静态资源与对象存储本地替代方案
本地开发建议使用 MinIO 替代 Cloudflare R2。
1. 修改 `.env` 中的 `AWS_S3_ENDPOINT_URL` 指向 MinIO 地址。
2. 确保已手动或通过脚本创建了 `media``releases` 桶。
3. 访问 `http://localhost:9001` 进入 MinIO 控制台。
---
## §11 测试运行手册
所有测试基于 `pytest` 运行。
### 11.1 运行单元与集成测试
```bash
# 运行全量测试
pytest
# 按目录跑集成测试
pytest tests/integration/property/
# 查看覆盖率
pytest --cov=apps --cov=core
```
### 11.2 运行 E2E 测试 (Playwright)
```bash
# 安装浏览器驱动 (仅需一次)
playwright install chromium
# 运行 E2E
pytest tests/e2e/
```
---
## §12 dev / staging / production 配置差异表
| 配置项 | Local Dev | Staging | Production |
|---|---|---|---|
| DEBUG | True | False | False |
| 多租户驱动 | django-tenants | django-tenants | django-tenants |
| 数据库 | Postgres 16 | Postgres 16 (RDS/Self-host) | Postgres 16 + PgBouncer |
| 存储服务 | MinIO (本地) | Cloudflare R2 | Cloudflare R2 |
| 任务队列 | Redis | Redis | Redis (Cluster/Sentinel) |
| 缓存 | Redis | Redis | Redis |
| 静态资源 | 本地处理 | Cloudflare CDN | Cloudflare CDN |
| 错误日志 | 控制台 | Sentry | Sentry + Grafana |
| 服务运行器 | runserver | Gunicorn + Uvicorn | Gunicorn + Uvicorn + Nginx |
---
## §13 常见故障排查
- **schema 切换失败**: 检查 `django-tenants` 中间件是否已启用,且 `ALLOWED_HOSTS` 包含对应的子域名。
- **R2 401**: 验证 `.env` 中的 AWS 凭证是否正确,桶是否存在。
- **Celery 任务不执行**: 检查 Redis 是否连通Worker 是否已启动,且任务是否使用了 `@tenant_task` 装饰器。
- **HTMX 局刷 404**: 确认视图是否处理了 `HX-Request` 头,且 URL 路由配置正确。
---
## §14 关联文档索引
- [`AGENTS.md`](../AGENTS.md), 技术栈总览与禁止项
- [`ADR.md`](../ADR.md), 关键技术决策追溯
- [`TECH_STACK/测试规范.md`](./测试规范.md), 详细测试要求
- [`DATA_MODEL/DATA_MODEL.md`](../DATA_MODEL/DATA_MODEL.md), 数据架构定义
---
## §15 待澄清/待补项
1. **种子数据脚本**: 尚未实现 `setup_dev_tenant` 命令,目前需手动在 Shell 创建租户。
2. **MinIO 预设**: 需要一个初始化脚本自动创建所需的 S3 桶media, releases
3. **Playwright 浏览器环境**: 尚未在 CI 配置中固定特定 Chromium 版本,本地开发建议使用最新版。
4. **系统配置 DDL**: `AGENTS.md` 提到系统配置 PRD 暂为空,对应的 DDL 迁移可能不完整。

View File

@@ -0,0 +1,227 @@
# Fonrey 路由与组件映射表 (ROUTES.md)
> **文档定位**Fonrey MVP 全局路由与 UI 组件映射的权威来源。
> **适用对象**Web 开发、前端工程、后端视图开发。
> **更新原则**新增页面必须在此登记。URL 命名严格遵循 RESTful + Django 习惯。
> **对齐基线**:本表与 `UI_DESIGN/*/*.md` 各模块设计文档对齐。模块设计文档的页面清单是权威,本文为汇总入口。
---
## 1. 通用约定
- **URL 规范**:资源复数形式 + 末尾斜杠(例如:`/properties/`)。
- **多租户**:业务端 URL 不携带租户 IDSchema 由 `django-tenants` 中间件按 subdomain 切换);平台管理后台为 `public` schema 下独立子域,前缀 `/admin/`
- **分页规范**:所有列表页强制使用 **Keyset 分页**。API 主参数统一为 `cursor`,禁止使用 `offset`。**过渡兼容**MVP Phase 1 期,详见 `ADR-20260604-001`):已有 page-based 示例的模块可保留页码 UI 表现(页码导航 + 跳页),但后端 API 必须以 `cursor` 为主参数;`page` 仅作过渡输入且不得用于 1000 条以上数据集。MVP GA 后 1 个版本内须清理所有 page-based 示例。
- **Tab 维度**:列表页的 Tab 切换统一以 query param `?tab=` 表达;切换走 HTMX 局刷,由后端根据 `tab` 返回对应数据。
- **HTMX Partial 路径**:业务页面的局部刷新走 `/<resource>/_partials/<purpose>/`;与某条记录强绑定的子区块走 `/<resource>/<uuid>/<sub>/partial`
- **JSON-only API 端点**:对外暴露给前端 AJAX/HTMX 的纯数据端点统一前缀 `/api/`
- **组件引用**:本表"复用组件"列必须严格引用 [`UI_SYSTEM/组件清单.md`](../UI_SYSTEM/组件清单.md) 中的标准名称,统一双语形式 `English中文`。新增组件必须先入组件清单再在此引用,禁止造词。
- **页面权威来源**每个模块的路由细节、Tab/筛选、交互态以同模块下 `UI_DESIGN/<模块>/*_UI.md` 为准;本表仅做全局汇总。
---
## 2. 全局基础页面
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **仪表盘 (Dashboard)** | `/` | 统计页 | `Tab Navigation标签页导航`, `Collapsible Card Grid折叠展开面板` | **空**: 暂无统计数据(图表显示 0) / **加载**: Skeleton 占位 / **错误**: 全局 500 / **权限**: 重定向至登录 |
| **404 错误页** | `/404/` | 错误页 | 无 | **空**: 不适用 / **加载**: 不适用 / **错误**: 展示 404 图文 / **权限**: 公开 |
| **500 错误页** | `/500/` | 错误页 | 无 | **空**: 不适用 / **加载**: 不适用 / **错误**: 展示 500 图文 / **权限**: 公开 |
| **403 无权限页** | `/403/` | 错误页 | 无 | **空**: 不适用 / **加载**: 不适用 / **错误**: 展示 403 图文 / **权限**: 已登录但无权 |
---
## 3. 登录管理模块 (Account)
> 权威来源:[`UI_DESIGN/登录管理/登录_UI.md`](./登录管理/登录_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **Tenant 识别页** | `/account/tenant/verify/` | 基础页 | 无 | **空**: 不适用 / **加载**: 提交按钮 Loading / **错误**: 租户未识别/网络错误内联提示 / **权限**: 公开 |
| **登录页(密码 Tab** | `/account/login/` | 基础页 | `Tab Navigation标签页导航` | **空**: 不适用 / **加载**: 提交按钮 Loading / **错误**: 凭证错误 / 锁定 / 禁用 / 会话过期 字段级提示 / **权限**: 公开 |
| **登录页(验证码 Tab** | `/account/login/?tab=otp` | 基础页 | `Tab Navigation标签页导航` | **空**: 不适用 / **加载**: OTP 发送 / 倒计时态 / **错误**: OTP 错误 / 过期 内联提示 / **权限**: 公开 |
| **设置新密码页** | `/account/password/reset/` | 基础页 | 无 | **空**: 不适用 / **加载**: 强度校验态 + 提交 Loading / **错误**: 两次不一致/校验失败 内联提示 / **权限**: 仅通过 OTP 校验的合法 Token |
| **退出登录** | `/account/logout/` | 处理页 | 无 | **空**: 不适用 / **加载**: 渐进式遮罩 / **错误**: Toast 提示 / **权限**: 仅登录用户 |
| **(Partial) 租户识别** | `POST /api/auth/tenant/verify/` | API | — | **错误**: JSON 错误码 + 字段级提示 / **权限**: 公开 |
| **(Partial) 密码登录** | `POST /api/auth/login/` | API | — | **错误**: JSON 错误码(凭证/锁定/禁用) / **权限**: 公开 |
| **(Partial) OTP 发送** | `POST /api/auth/login/phone/` | API | — | **错误**: 频率限制/号码非法 / **权限**: 公开 |
| **(Partial) 找回密码-请求** | `POST /api/auth/recover/password/request/` | API | — | **错误**: 账号不存在/限流 / **权限**: 公开 |
| **(Partial) 找回密码-校验** | `POST /api/auth/recover/password/verify/` | API | — | **错误**: OTP 错误/过期 / **权限**: 公开 |
| **(Partial) 找回密码-重置** | `POST /api/auth/recover/password/reset/` | API | — | **错误**: Token 过期/校验失败 / **权限**: 持有有效重置 Token |
---
## 4. 房源管理模块 (Property)
> 权威来源:[`UI_DESIGN/房源管理/房源列表_UI.md`](./房源管理/房源列表_UI.md) / [`房源详情_UI.md`](./房源管理/房源详情_UI.md) / [`新增房源_UI.md`](./房源管理/新增房源_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **房源列表(全部)** | `/properties/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Pagination分页组件`, `Toolbar操作工具栏`, `Export Button导出按钮`, `Smart Sort智能排序`, `Tab Navigation标签页导航` | **空**: 暂无房源(引导图 + 新增 CTA) / **加载**: 表格 Skeleton / **错误**: 列表刷新失败 Toast / **权限**: 403 遮罩 |
| **房源列表(出售 Tab** | `/properties/?tab=for_sale` | 列表页 | 同上 | 同上 |
| **房源列表(出租 Tab** | `/properties/?tab=for_rent` | 列表页 | 同上 | 同上 |
| **房源列表(未挂牌 Tab** | `/properties/?tab=unlisted` | 列表页 | 同上 | 同上 |
| **房源列表(成交 Tab** | `/properties/?tab=sold` | 列表页 | 同上 | 同上 |
| **重复房源** | `/properties/duplicates/` | 列表页 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无重复 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅管理员/店长 |
| **嫌疑号码** | `/properties/suspect-numbers/` | 列表页 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无嫌疑号 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅管理员/店长 |
| **新增房源** | `/properties/create/` | 编辑页(独立) | `Tab Navigation标签页导航`, `Multi-select Tag Input多选标签选择器`, `Tree Select / Cascading Dropdown树形下拉选择器` | **空**: 字段默认值 / **加载**: 表单骨架屏 / **错误**: 422 字段级 / 500 Toast / **权限**: 仅有权经纪人 |
| **房源详情** | `/properties/<uuid>/` | 详情页 | `Tab Navigation标签页导航`, `Accordion Progress Panel可折叠进度检查面板`, `Collapsible Card Grid折叠展开面板`, `Image Lightbox View全屏图片灯箱预览器` | **空**: 房源不存在(404) / **加载**: 模块化局部 Skeleton / **错误**: 详情加载失败 Toast / **权限**: 403 页面 |
| **房源全量编辑** | `/properties/<uuid>/edit/` | 编辑页(独立) | `Tab Navigation标签页导航`, `Inline Edit Mode页面级读写切换`, `Multi-select Tag Input多选标签选择器` | **空**: 不适用 / **加载**: 表单骨架屏 / **错误**: 提交失败(422/500) / **权限**: 仅维护人/管理员 |
| **挂牌历史** | `/properties/<uuid>/history/` | 子详情页 | `Collapsible Card Grid折叠展开面板` | **空**: 暂无挂牌记录 / **加载**: 列表 Skeleton / **错误**: Toast / **权限**: 403 遮罩 |
| **(Partial) 写跟进** | `/properties/<uuid>/_partials/followup/` | Drawer | `Drawer / Slide-over Panel右侧抽屉面板`, `Tab Navigation标签页导航` | **加载**: Drawer 内 Loading / **错误**: 提交失败 Toast / **权限**: 仅参与经纪人 |
| **(Partial) 相册管理** | `/properties/<uuid>/_partials/gallery/` | Modal | `Photo Gallery Manager相册管理器`, `Sortable Table with Drag Handle带拖拽手柄的可排序表格` | **空**: 暂无照片 / **加载**: 缩略图 Skeleton / **错误**: 上传失败 Toast / **权限**: 仅维护人/管理员 |
---
## 5. 客源管理模块 (Client)
> 权威来源:[`UI_DESIGN/客源管理/客源列表_UI.md`](./客源管理/客源列表_UI.md) / [`客源详情_UI.md`](./客源管理/客源详情_UI.md) / [`新增客源_UI.md`](./客源管理/新增客源_UI.md) / [`编辑客源_UI.md`](./客源管理/编辑客源_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **私客列表(全部)** | `/clients/private/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Pagination分页组件`, `Toolbar操作工具栏`, `Tab Navigation标签页导航` | **空**: 暂无客源(引导录入) / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 403 遮罩 |
| **私客列表(求购 Tab** | `/clients/private/?tab=buying` | 列表页 | 同上 | 同上 |
| **私客列表(求租 Tab** | `/clients/private/?tab=renting` | 列表页 | 同上 | 同上 |
| **私客列表(暂缓 TabP2** | `/clients/private/?tab=suspended` | 列表页 | 同上 | 同上 |
| **公客列表P2** | `/clients/public/` | 列表页 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无公客 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅店长/管理员 |
| **成交客列表P2** | `/clients/transacted/` | 列表页 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无成交客 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅管理员 |
| **重复客源** | `/clients/duplicates/?tab=transacted\|public` | 列表页 | `Data Table数据表格`, `Pagination分页组件`, `Tab Navigation标签页导航` | **空**: 暂无重复 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅管理员(成交/公客子集见 `ADR-20260604-002` |
| **客源回收站** | `/clients/deleted/` | 列表页 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无回收 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅管理员 |
| **录入客源** | `/clients/create/` | 编辑页(独立) | `Tree Select / Cascading Dropdown树形下拉选择器`, `Multi-select Tag Input多选标签选择器` | **空**: 字段默认值 / **加载**: 表单 Loading / **错误**: 字段级 422 / 500 Toast / **权限**: 仅有权经纪人 |
| **客源详情** | `/clients/<uuid>/` | 详情页 | `Tab Navigation标签页导航`, `Collapsible Card Grid折叠展开面板` | **空**: 客源不存在 / **加载**: 局部 Skeleton / **错误**: Toast / **权限**: 403 页面 |
| **编辑客源** | `/clients/<uuid>/edit/` | 编辑页(独立) | `Tab Navigation标签页导航`, `Inline Edit Mode页面级读写切换`, `Tree Select / Cascading Dropdown树形下拉选择器` | **空**: 不适用 / **加载**: 表单 Loading / **错误**: 字段级 422 / **权限**: 仅归属人/首录人/管理员(其他重定向至详情 + Toast |
| **(Partial) 电话重复检测** | `POST /clients/check-duplicate-phone/` | API | — | **错误**: 返回橙色警告 Partial / **权限**: 仅有权经纪人 |
| **(Partial) 跟进日志筛选/分页** | `/clients/<uuid>/follow-logs/partial/` | Partial | `Tab Navigation标签页导航` | **空**: 暂无跟进 / **错误**: Toast / **权限**: 仅参与经纪人 |
| **(Partial) 带看记录筛选** | `/clients/<uuid>/viewings/partial/` | Partial | — | **空**: 暂无带看 / **错误**: Toast / **权限**: 仅参与经纪人 |
| **(Partial) 客源解读刷新** | `/clients/<uuid>/insights/partial/` | Partial | — | **空**: 暂无洞察 / **错误**: Toast / **权限**: 仅参与经纪人 |
| **(Partial) 配房筛选/分页** | `/clients/<uuid>/matches/partial/` | Partial | `Pagination分页组件` | **空**: 暂无匹配 / **错误**: Toast / **权限**: 仅参与经纪人 |
| **(Partial) 查看号码** | `/clients/<uuid>/contacts/<cid>/reveal-phone/` | API | — | **错误**: 频次超限/无权 Toast / **权限**: 仅归属人/上级 |
| **(Partial) 标记号码无效** | `PATCH /api/clients/<uuid>/contacts/<cid>/mark-invalid/` | API | — | **错误**: 422 / 500 Toast / **权限**: 仅归属人/管理员 |
| **(Partial) 编辑基础信息弹窗** | `PATCH /api/clients/<uuid>/basic-info/` | Modal | `Modal Dialog模态对话框` | **错误**: 字段级 422 / **权限**: 仅归属人/管理员 |
---
## 6. 楼盘/区域管理模块 (Complex / Region)
> 权威来源:[`UI_DESIGN/楼盘管理/楼盘列表_UI.md`](./楼盘管理/楼盘列表_UI.md) / [`楼盘详情_UI.md`](./楼盘管理/楼盘详情_UI.md) / [`区域管理_UI.md`](./楼盘管理/区域管理_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **楼盘列表** | `/complexes/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Pagination分页组件`, `Toolbar操作工具栏` | **空**: 暂无楼盘 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 全员可见/不可改 |
| **楼盘详情** | `/complexes/<uuid>/` | 详情页 | `Tab Navigation标签页导航`, `Collapsible Card Grid折叠展开面板` | **空**: 楼盘不存在 / **加载**: 局部 Skeleton / **错误**: Toast / **权限**: 全员可见 |
| **区域/商圈管理** | `/regions/` | 设置页 | `Tree Select / Cascading Dropdown树形下拉选择器`, `Dynamic Form Table动态可增删行表格` | **空**: 暂无配置 / **加载**: 树形 Skeleton / **错误**: 保存失败 Toast / **权限**: 仅系统管理员 |
---
## 7. 组织人事模块 (Org / Staff)
> 权威来源:[`UI_DESIGN/组织人事管理/组织人事_UI.md`](./组织人事管理/组织人事_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **组织架构** | `/org/` | 树形页 | `Tree Select / Cascading Dropdown树形下拉选择器`, `Drawer / Slide-over Panel右侧抽屉面板` | **空**: 暂无门店 / **加载**: 树形 Skeleton / **错误**: Toast / **权限**: 仅管理员 |
| **人员列表** | `/staff/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Pagination分页组件`, `Toolbar操作工具栏` | **空**: 暂无员工 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅管理员/店长 |
| **人员详情** | `/staff/<uuid>/` | 详情页 | `Inline Edit Mode页面级读写切换`, `Collapsible Card Grid折叠展开面板` | **空**: 人员不存在 / **加载**: 局部 Skeleton / **错误**: Toast / **权限**: 仅本人/上级/管理员 |
---
## 8. 权限管理模块 (Permission)
> 权威来源:[`UI_DESIGN/权限管理/权限管理_UI.md`](./权限管理/权限管理_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **角色权限配置** | `/permissions/roles/` | 设置页 | `Tab Navigation标签页导航`, `Dynamic Form Table动态可增删行表格`, `Drawer / Slide-over Panel右侧抽屉面板` | **空**: 暂无角色 / **加载**: 列表 Loading / **错误**: 配置失败 Toast / **权限**: 仅系统管理员 |
| **人员权限分配** | `/permissions/assignments/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Tree Select / Cascading Dropdown树形下拉选择器` | **空**: 暂无分配 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅系统管理员 |
---
## 9. 系统配置模块 (Setting)
> 权威来源:[`UI_DESIGN/系统配置/系统配置_UI.md`](./系统配置/系统配置_UI.md) / [`首页设置_UI.md`](./系统配置/首页设置_UI.md)
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **全局参数配置** | `/settings/params/` | 设置页 | `Inline Edit Mode页面级读写切换`, `Dynamic Form Table动态可增删行表格`, `Multi-Table Independent Pagination同页多表格独立分页` | **空**: 暂无参数 / **加载**: 局部 Skeleton / **错误**: 保存失败 Toast / **权限**: 仅系统管理员 |
| **首页设置** | `/settings/homepage/` | 设置页 | `Inline Edit Mode页面级读写切换`, `Collapsible Card Grid折叠展开面板` | **空**: 默认模板 / **加载**: 局部 Skeleton / **错误**: 保存失败 Toast / **权限**: 仅系统管理员 |
| **关注楼盘** | `/settings/watch-complexes/` | 设置页 | `Tree Select / Cascading Dropdown树形下拉选择器`, `Multi-select Tag Input多选标签选择器` | **空**: 暂未关注 / **加载**: Tree Skeleton / **错误**: 保存失败 Toast / **权限**: 仅本人/管理员 |
| **(Partial) 字段自定义** | `/settings/_partials/fields/` | Drawer | `Drawer / Slide-over Panel右侧抽屉面板`, `Sortable Table with Drag Handle带拖拽手柄的可排序表格` | **加载**: Drawer Loading / **错误**: 422 Toast / **权限**: 仅系统管理员 |
---
## 10. 平台管理后台 (Platform Admin)
> 仅由平台超级管理员在 Public Schema 下访问,独立子域,统一前缀 `/admin/`。
> 权威来源:[`UI_DESIGN/平台管理后台/平台管理后台_UI.md`](./平台管理后台/平台管理后台_UI.md) §3.1。
| 页面名称 | URL 路径 | 页面类型 | 复用组件 | 空/加载/错误/权限状态描述 |
| :--- | :--- | :--- | :--- | :--- |
| **平台管理员登录** | `/admin/login/` | 基础页 | 无 | **空**: 不适用 / **加载**: 提交按钮 Loading / **错误**: 凭证错误内联提示 / **权限**: 公开 |
| **平台仪表盘** | `/admin/` | 统计页 | `Collapsible Card Grid折叠展开面板` | **空**: 暂无数据 / **加载**: Skeleton / **错误**: Toast / **权限**: 仅平台管理员 |
| **租户列表** | `/admin/tenants/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Pagination分页组件`, `Toolbar操作工具栏` | **空**: 暂无租户 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **新建租户** | `/admin/tenants/new/` | 编辑页(独立) | `Tab Navigation标签页导航`, `Multi-select Tag Input多选标签选择器` | **空**: 字段默认值 / **加载**: 表单 Loading / **错误**: 422 字段级 / **权限**: 仅平台超管 |
| **租户详情 - 基本信息** | `/admin/tenants/<id>/` | 详情页 | `Tab Navigation标签页导航`, `Collapsible Card Grid折叠展开面板`, `Inline Edit Mode页面级读写切换` | **空**: 租户不存在 / **加载**: 局部 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **租户详情 - 用户管理** | `/admin/tenants/<id>/users/` | 子列表 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无用户 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **租户详情 - 套餐信息** | `/admin/tenants/<id>/plan/` | 子设置页 | `Inline Edit Mode页面级读写切换`, `Modal Dialog模态对话框` | **空**: 默认套餐 / **加载**: 局部 Skeleton / **错误**: 422 字段级 / **权限**: 仅平台超管 |
| **租户详情 - 监控** | `/admin/tenants/<id>/monitoring/` | 子统计页 | `Collapsible Card Grid折叠展开面板` | **空**: 暂无指标 / **加载**: 图表 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **租户详情 - 备份记录** | `/admin/tenants/<id>/backups/` | 子列表 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无备份 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **租户详情 - 操作历史** | `/admin/tenants/<id>/history/` | 子列表 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无记录 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营/审计 |
| **系统版本管理** | `/admin/system/versions/` | 列表页 | `Data Table数据表格`, `Pagination分页组件`, `Drawer / Slide-over Panel右侧抽屉面板` | **空**: 暂无版本 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管 |
| **系统备份管理** | `/admin/system/backups/` | 列表页 | `Data Table数据表格`, `Pagination分页组件` | **空**: 暂无备份 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **监控与告警** | `/admin/monitoring/` | 统计页 | `Collapsible Card Grid折叠展开面板`, `Tab Navigation标签页导航` | **空**: 暂无告警 / **加载**: 图表 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营 |
| **客户端版本管理** | `/admin/client-releases/` | 列表页 | `Data Table数据表格`, `Pagination分页组件`, `Drawer / Slide-over Panel右侧抽屉面板` | **空**: 暂无发布 / **加载**: 列表 Skeleton / **错误**: 发布失败 Toast / **权限**: 仅平台超管 |
| **审计日志** | `/admin/audit-logs/` | 列表页 | `Data Table数据表格`, `Column Visibility Panel自定义列显示`, `Pagination分页组件` | **空**: 暂无日志 / **加载**: 表格 Skeleton / **错误**: Toast / **权限**: 仅平台超管/运营/审计 |
| **管理员设置** | `/admin/settings/admins/` | 列表+设置 | `Data Table数据表格`, `Drawer / Slide-over Panel右侧抽屉面板`, `Inline Edit Mode页面级读写切换` | **空**: 暂无管理员 / **加载**: 表格 Skeleton / **错误**: 422 字段级 / **权限**: 仅平台超管 |
---
## X. 非 MVP 占位 (v2 规划)
- **移动端适配页**`/m/` 起始的所有路由。
- **合同管理**`/contracts/`
- **财务结算**`/finance/`
- **营销/三网发布**`/marketing/`
- **新房模块**`/new-houses/`
---
## 附录 A: 页面状态定义 (Page States)
- **空 (Empty)**: 使用 `Empty State` 设计,包含图标、说明文案及主要行动点 (CTA)。
- **加载 (Loading)**: 优先使用 `Skeleton` 骨架屏(针对表格、详情块);按钮操作使用 `Spinner` 及禁用态。
- **错误 (Error)**:
- 页面级404/500 全屏错误页。
- 异步/局部:通过 `HX-Trigger` 弹出 `fonrey:toast` 提示。
- 表单422 返回包含错误信息的 Partial红色文字定位到字段。
- **权限 (Permission)**:
- 无权访问页面:重定向至 403 页面。
- 无权操作按钮:不渲染该按钮或置灰。
---
## 附录 B: 页面类型定义 (Page Types)
- **列表页 (List)**: 主体为数据表格,含筛选、分页。
- **详情页 (Detail)**: 多维展示单一资源,多为 Tab 布局。
- **编辑页(独立) (Edit Page)**: 独立 URL 的全量表单页(非 Drawer/Modal
- **设置页 (Setting)**: 多为左侧导航 + 右侧表单/列表,含读写切换。
- **基础页 (Base)**: 独立布局,如登录、错误页。
- **统计页 (Stats)**: 以指标卡 + 图表为主。
- **子详情页 / 子列表 / 子设置页**: 嵌于某条记录上下文下的二级页面。
- **Partial**: 供 HTMX 调用的局部 HTML 片段。
- **API**: 仅返回 JSON / 局部 HTML 的端点,无独立页面。
---
## 附录 C: 与 UI_DESIGN 模块文档的对齐索引
| 模块 | 权威 UI 设计文档 |
| :--- | :--- |
| 登录 | [`UI_DESIGN/登录管理/登录_UI.md`](./登录管理/登录_UI.md) |
| 房源 | [`房源列表_UI.md`](./房源管理/房源列表_UI.md) / [`房源详情_UI.md`](./房源管理/房源详情_UI.md) / [`新增房源_UI.md`](./房源管理/新增房源_UI.md) |
| 客源 | [`客源列表_UI.md`](./客源管理/客源列表_UI.md) / [`客源详情_UI.md`](./客源管理/客源详情_UI.md) / [`新增客源_UI.md`](./客源管理/新增客源_UI.md) / [`编辑客源_UI.md`](./客源管理/编辑客源_UI.md) |
| 楼盘/区域 | [`楼盘列表_UI.md`](./楼盘管理/楼盘列表_UI.md) / [`楼盘详情_UI.md`](./楼盘管理/楼盘详情_UI.md) / [`区域管理_UI.md`](./楼盘管理/区域管理_UI.md) |
| 组织人事 | [`组织人事_UI.md`](./组织人事管理/组织人事_UI.md) |
| 权限 | [`权限管理_UI.md`](./权限管理/权限管理_UI.md) |
| 系统配置 | [`系统配置_UI.md`](./系统配置/系统配置_UI.md) / [`首页设置_UI.md`](./系统配置/首页设置_UI.md) |
| 平台管理后台 | [`平台管理后台_UI.md`](./平台管理后台/平台管理后台_UI.md) |

View File

@@ -94,7 +94,7 @@
#### 2.1.1 页面概述
- **URL**`/properties/`query params`tab`, `status`, `grade`, `page`, `q`, `sort`, `order` 等)
- **URL**`/properties/`query params`tab`, `status`, `grade`, `cursor`, `q`, `sort`, `order`;分页采用 Keyset详见 `ADR-20260604-001`
- **访问入口**:顶部全局导航栏「房源」菜单 → 默认进入全部房源列表
- **页面职责**:展示经纪人名下(或门店/全司)的房源列表,支持多维度搜索筛选、批量操作、状态快览
- **竞品参考截图**
@@ -738,7 +738,7 @@
| 左侧 | 总条数「共 N 条」 |
| 中间 | 页码导航:`← 上一页 [1] [2] [3] [4] [5] … [N] 下一页 →` |
| 右侧 | 每页条数「20条/页 ▾」选项20/50/100+ 跳页「跳至」+ 输入框 + 「页」 |
| HTMX | `hx-get="/properties/" hx-vals='{"page": N}' hx-target="#property-list-container" hx-swap="innerHTML" hx-include="closest form"` |
| HTMX | `hx-get="/properties/" hx-vals='{"cursor": LAST_ID}' hx-target="#property-list-container" hx-swap="innerHTML" hx-include="closest form"`Keyset 分页,`LAST_ID` 为当前页最后一条 `id`;详见 `ADR-20260604-001`。MVP 过渡期允许保留页码 UI 表现,但 `cursor` 是后端主参数) |
| 当前页 | `bg-primary-600 text-white rounded-md w-8 h-8 font-medium` |
| 非当前页 | `text-neutral-600 hover:bg-neutral-100 rounded-md w-8 h-8` |

View File

@@ -0,0 +1,175 @@
这是一个非常实际的问题。用 OpenCode或类似 AI 编码工具)做长周期项目,核心挑战是**每次会话 AI 都是"失忆"的**,你需要一套机制让它快速恢复上下文。
以下是一套完整的项目管理方案:
---
## 核心思路:用文档作为 AI 的"外部记忆"
AI 没有跨会话记忆,但它每次都能读文件。所以你的目标是:**把项目状态写进文件,而不是靠 AI 记住。**
---
## 一、项目文档结构
在项目根目录维护以下几个关键文件:
```
project-root/
├── CLAUDE.md # AI 行为规则 + 项目概览OpenCode 每次自动读取)
├── TASK.md # 任务总表P0/P1/P2 优先级
├── PROGRESS.md # 每日进度日志(最重要)
├── ARCHITECTURE.md # 技术架构
└── .opencode/
└── context.md # 会话级补充上下文(可选)
```
---
## 二、最关键的文件:`PROGRESS.md`
这是你每天结束时必须更新的文件,格式建议如下:
```markdown
# 项目进度日志
## 2025-01-15今日
### ✅ 已完成
- [x] 实现了 PropertyListing 的 CRUD API
- [x] 修复了权限校验的 bug见 commit abc123
### 🔄 进行中
- [ ] 房源搜索功能(已完成 70%
- 已做Elasticsearch 索引配置
- 未做:前端搜索组件、过滤器逻辑
- 卡点:全文搜索中文分词需要引入 IK 插件
### ⏭️ 明日计划
- 完成搜索前端组件
- 开始客源管理模块的数据模型
### 🐛 已知问题 / 技术债
- PropertyImage 上传后缩略图生成有时失败,原因未查
### 📁 今日改动的关键文件
- `apps/listings/views.py`
- `apps/listings/serializers.py`
- `templates/listings/search.html`
```
---
## 三、`CLAUDE.md` — AI 的启动说明书
OpenCode 会自动读取项目根目录的 `CLAUDE.md`。把它写成"给 AI 的操作手册"
```markdown
# Fonrey 项目 AI 开发规范
## 项目简介
B2B SaaS 房产中介管理系统Django + HTMX + Alpine.js + PostgreSQL
## 每次会话开始前,你必须做的事
1. 阅读 `PROGRESS.md` 了解当前进度
2. 阅读 `TASK.md` 了解当前 Sprint 目标
3. 确认今天要完成哪个具体任务后再写代码
## 技术约束
- 前端HTMX + Alpine.js + Tailwind禁止引入 React/Vue
- 文件上传:统一用 FilePond
- 权限:使用自定义 RBAC不使用 django-guardian
## 代码规范
- 所有新文件必须有注释说明用途
- 每完成一个功能,更新 PROGRESS.md
## 禁止事项
- 不要修改 DATA_MODEL.md 中已定稳的数据表结构
- 不要在没有确认的情况下重构已有模块
```
---
## 四、每日工作流程
### 早上开始2 分钟)
给 AI 发这条固定的"启动 prompt"
```
请阅读 PROGRESS.md 和 TASK.md告诉我
1. 昨天完成了什么
2. 今天应该继续做什么
3. 有没有未解决的卡点
然后我们开始今天的工作。
```
AI 读完文件后会给你一个摘要,你确认后直接开工。
### 晚上结束5 分钟)
让 AI 帮你更新进度日志:
```
今天的工作完成了,请帮我更新 PROGRESS.md
- 完成了:[你说]
- 未完成的:[你说]
- 明天继续:[你说]
```
---
## 五、处理"卡点"和"上下文切换"
### 当你在一个功能里遇到复杂问题时
创建一个临时文件 `SCRATCH.md`
```markdown
# 当前卡点:中文全文搜索
## 问题描述
PostgreSQL tsvector 对中文分词支持差
## 已尝试的方案
1. 方案A用 zhparser - 失败,原因 xxx
2. 方案B用 pg_jieba - 待验证
## 当前倾向
方案B明天继续
```
下次会话告诉 AI"先读 SCRATCH.md",秒回上下文。
### 当你需要切换模块时
在 PROGRESS.md 里明确标记"暂停点",记录文件名和函数名,这比描述更精确。
---
## 六、可选增强Git commit 作为进度锚点
养成每完成一个小功能就 commit 的习惯commit message 写清楚:
```
feat(listings): 完成房源搜索 API支持关键词+区域过滤
TODO: 前端搜索组件待完成
```
这样 AI 可以通过 `git log --oneline -20` 快速看到最近的工作轨迹,作为 PROGRESS.md 的补充。
---
## 总结
|文件|作用|更新频率|
|---|---|---|
|`CLAUDE.md`|AI 行为规范,一次写好|很少改|
|`TASK.md`|任务总表|每周调整|
|`PROGRESS.md`|每日进度日志|**每天必更新**|
|`SCRATCH.md`|当前卡点临时笔记|按需|
**最核心的一句话:每天花 5 分钟更新 `PROGRESS.md`,比什么方法都有效。** AI 能读文件,但它读不了你的脑子——把脑子里的状态写进文件,问题就解决了。