diff --git a/Project/fonrey/PRD/PRD_MVP.md b/Project/fonrey/PRD/PRD_MVP.md
new file mode 100644
index 00000000..80c0af23
--- /dev/null
+++ b/Project/fonrey/PRD/PRD_MVP.md
@@ -0,0 +1,280 @@
+# Fonrey 房睿 — MVP 范围书
+
+**Status**: Draft
+**Author**: Product Team
+**Last Updated**: 2026-04-24
+**Version**: 1.0
+
+> **For AI assistants**: 本文件定义 Phase 1(MVP)的边界。在任何功能实现前,先对照本文确认是否在范围内。范围外的功能禁止在 MVP 阶段实现。
+
+---
+
+## 1. 产品背景与目标
+
+**Fonrey(房睿)** 是一套面向中小型房产经纪公司的 B2B SaaS 管理平台,解决以下核心痛点:
+
+- 房源/客源信息散乱,全靠人工记录
+- 跟进记录缺失,数据流失严重
+- 重复录入浪费大量经纪人时间
+- 无法支撑 89,000+ 数据量级下的高效房客匹配
+
+**MVP 目标**:在一家种子客户(单租户)环境下,完整跑通"录入房源 → 录入客源 → 匹配带看 → 成交"的核心业务链路。
+
+---
+
+## 2. MVP 核心功能清单(Phase 1 必须实现)
+
+### 2.1 优先级定义
+
+| 优先级 | 含义 |
+|--------|------|
+| **P0** | MVP 上线前必须完成,阻断核心业务链路 |
+| **P1** | MVP 上线后第一个迭代周期内完成 |
+| **P2** | 已规划,列入路线图但不阻断上线 |
+
+---
+
+### 2.2 模块优先级矩阵
+
+#### 🏠 房源管理
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 录入住宅(二手出售/出租) | **P0** | 核心业务入口 |
+| 房源列表(二手&租赁) | **P0** | 含筛选、排序、分页 |
+| 房源详情页 | **P0** | 含基本信息、产证、交易信息展示 |
+| 跟进记录(全部/写入/修改/其他) | **P0** | 含钥匙、委托、实勘 |
+| 图片管理(相册上传/分类/排序) | **P0** | 核心房源内容 |
+| 业主联系人管理 | **P0** | 含新增/编辑/查看同业主房源 |
+| 价格调整(调价/调价记录) | **P0** | 核心运营操作 |
+| 房源状态变更(在售/暂缓/成交/下架) | **P0** | 状态机核心 |
+| 房源维护完成度(诊断面板) | **P1** | 提升数据质量 |
+| 敏感信息跟进(查看权限控制) | **P1** | 需配合权限模块 |
+| 附件管理 | **P1** | 非阻断性 |
+| 市场报盘 | **P1** | 运营辅助功能 |
+| 价格解读 | **P1** | 分析辅助 |
+| 录入别墅/商铺/商住/写字楼/其他 | **P2** | 住宅优先,商业类低频 |
+| 全部商铺列表 / 全部写字楼列表 | **P2** | 配合 P2 录入功能 |
+| 房源广场 | **P2** | 跨租户/公共池功能 |
+
+#### 🏙️ 楼盘管理
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 楼盘列表 + 楼盘详情(楼盘信息/楼栋/结构) | **P0** | 房源数据底座,必须先行 |
+| 区域管理(城区/商圈) | **P0** | 房源关联必须 |
+| 楼盘照片管理 | **P1** | 数据完善 |
+| 楼盘价格走势 | **P1** | 分析辅助 |
+| 周边配套(学校管理) | **P1** | 补充信息 |
+| 应用数据标准 | **P2** | 明确不做 |
+
+#### 👥 客源管理
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 录入私客(求购/求租) | **P0** | 核心业务 |
+| 私客列表(全部/求购/求租) | **P0** | 含筛选、排序 |
+| 私客详情(基本信息/需求信息) | **P0** | |
+| 跟进记录(全部/写入/修改/其他) | **P0** | |
+| 带看管理(预约带看/新增带看) | **P0** | 房客匹配核心 |
+| 联系人管理 | **P0** | |
+| 客源状态变更(改等级/改状态) | **P0** | |
+| 转公客 / 转成交 / 转无效 | **P0** | 生命周期核心 |
+| 二手配房(智能匹配) | **P1** | 核心价值,但可后续迭代 |
+| 客源解读 | **P1** | AI 辅助分析 |
+| 客源信息概览 | **P1** | 汇总视图 |
+| 客源收藏夹 | **P1** | 辅助功能 |
+| 公客管理 | **P2** | 私客优先 |
+| 成交客管理 | **P2** | |
+| 暂缓私客 | **P2** | |
+
+#### 🏢 组织人事
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 公司组织结构(部门/门店树) | **P0** | 权限系统基础 |
+| 员工列表/员工详情 | **P0** | |
+| 员工入职/账号创建 | **P0** | |
+| 员工离职 / 调动 | **P1** | |
+| 员工通讯录 | **P1** | |
+| 异动记录 | **P1** | |
+| 奖惩记录 | **P2** | |
+| 职务管理 | **P1** | |
+| 门店分布地图 | **P2** | |
+
+#### 🔐 权限管理
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 角色管理(预设角色 + 自定义角色) | **P0** | 权限基础 |
+| 人员权限列表 | **P0** | |
+| 角色批量分配 | **P0** | |
+| 功能权限(菜单级) | **P0** | |
+| 数据权限(部门/个人/全司) | **P0** | |
+| 字段级权限(敏感字段可见性) | **P1** | 配合房源/客源敏感信息 |
+| 个人特定权限覆盖 | **P1** | |
+
+#### 🔑 用户登录
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 账号密码登录 | **P0** | |
+| 多租户识别(子域名/域名) | **P0** | |
+| Token 管理 / 会话超时 | **P0** | |
+| 短信验证码登录 | **P1** | |
+| 密码重置 | **P1** | |
+| 记住登录状态 | **P1** | |
+
+#### ⚙️ 系统配置
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 首页设置 | **P1** | |
+| 房源设置(字段必填/自定义字段/标签) | **P0** | 影响录入表单 |
+| 相关方设置 | **P1** | |
+| 客源设置(基本配置/参数配置) | **P1** | |
+| 人事OA设置 | **P2** | |
+| 交易设置 | **P2** | |
+| 财务设置 | **P2** | |
+| 合同设置 | **P2** | |
+
+#### 🖥️ 系统管理(运营后台)
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| 租户管理(开通/暂停/配置) | **P1** | 单租户种子阶段可手动 |
+| 系统健康监控 | **P1** | |
+| 操作审计日志 | **P2** | |
+| 灰度发布 / 滚动升级 | **P2** | |
+
+#### 💻 客户端发布
+
+| 功能 | 优先级 | 说明 |
+|------|--------|------|
+| Windows 桌面客户端(内置浏览器) | **P1** | 种子客户使用 Web 端可先行 |
+| 自动更新机制 | **P1** | 配合客户端 |
+
+---
+
+## 3. 非目标(Out of Scope — MVP 阶段绝对不做)
+
+以下功能在 MVP 阶段**明确不实现**,AI 生成代码时不得为这些功能预留接口或引入相关依赖:
+
+| 功能 | 原因 |
+|------|------|
+| 移动端适配 | v2 规划 |
+| 新房模块(新房管理/新房设置) | 独立模块,后续版本 |
+| 合同管理模块 | 独立模块,后续版本 |
+| 财务管理/提成结算 | 独立模块,后续版本 |
+| 三网发布(安居客/链家/贝壳对接) | 独立模块,后续版本 |
+| 数据报表/行程量化 | 独立模块,后续版本 |
+| 在线充值/增值服务 | 独立模块,后续版本 |
+| 任务管理(OA任务/入职祝福) | 低优先 |
+| 考勤管理 | 独立 HR 模块 |
+| 审批流程 | 独立 OA 模块 |
+| 智慧大屏 / VR换装 | 增值产品 |
+| 房源广场(跨租户公共池) | 多租户复杂场景 |
+
+---
+
+## 4. 用户故事(MVP 核心路径)
+
+### Story 1 — 经纪人录入房源
+> As a **一线经纪人**,
+> I want to **快速录入一套二手住宅并上传图片和业主联系方式**,
+> So that **这套房源的信息能被团队所有成员找到和跟进**.
+
+**验收标准**:
+- 可在 3 分钟内完成住宅基本信息录入
+- 上传图片后自动按分类展示
+- 录入后即刻出现在房源列表
+
+---
+
+### Story 2 — 经纪人跟进房源
+> As a **一线经纪人**,
+> I want to **对我负责的房源记录每次跟进(面访/电话/钥匙/实勘)**,
+> So that **我的跟进历史有据可查,团队不会重复联系同一业主**.
+
+**验收标准**:
+- 跟进记录按时间线倒序展示
+- 支持写入跟进、修改跟进、其他跟进(钥匙/委托/实勘)
+- 敏感信息跟进只对有权限的人员可见
+
+---
+
+### Story 3 — 经纪人录入客源
+> As a **一线经纪人**,
+> I want to **录入意向购房/租房客户并跟进其需求变化**,
+> So that **我能在合适时机将客户与合适房源匹配**.
+
+**验收标准**:
+- 区分求购/求租两种意向
+- 支持跟进记录
+- 可安排带看并记录带看结果
+
+---
+
+### Story 4 — 转成交
+> As a **一线经纪人**,
+> I want to **将已达成交易的客源标记为"成交"并关联成交房源**,
+> So that **成交数据进入系统留存,房源状态自动更新**.
+
+**验收标准**:
+- 转成交时必须选择关联房源
+- 成交后客源状态自动变为"成交客"
+- 关联房源状态建议变更为"成交"(可手动确认)
+
+---
+
+### Story 5 — 店长查看团队数据
+> As a **门店店长**,
+> I want to **查看本门店所有员工的房源和客源列表**,
+> So that **我能掌握团队整体情况并合理分配资源**.
+
+**验收标准**:
+- 数据权限按部门隔离,店长可见本门店数据
+- 可筛选查看特定员工的房源/客源
+- 无法看到其他门店的数据
+
+---
+
+## 5. MVP 技术边界
+
+| 约束 | 决策 |
+|------|------|
+| 租户数 | **单租户**种子阶段,多租户架构已就位但不激活多租户切换 UI |
+| 数据量 | 目标支撑 **89,000 条**房源,测试阶段以 10,000 条压测 |
+| 浏览器支持 | Chrome 最新版 / Edge 最新版,不支持 IE |
+| 语言 | 简体中文,不做国际化 |
+| 移动端 | **不做**,Web 端 Desktop-first |
+| 导出 | Excel/CSV 导出通过 Celery 异步,不超时 |
+
+---
+
+## 6. MVP 交付检查清单
+
+在 MVP 正式上线前,以下项目必须全部勾选:
+
+- [ ] 房源录入(住宅)完整流程可用
+- [ ] 房源列表可筛选/排序/分页
+- [ ] 客源录入(求购/求租)完整流程可用
+- [ ] 带看创建与记录可用
+- [ ] 转成交流程可用
+- [ ] 楼盘数据可录入(为房源提供底座)
+- [ ] 员工账号可创建/分配角色
+- [ ] 权限隔离:经纪人只能看自己数据,店长能看本店数据
+- [ ] 89,000 条数据量下列表查询 < 2 秒(含索引优化)
+- [ ] 图片上传到 Cloudflare R2 可用
+- [ ] 多租户 Schema 隔离验证通过
+
+---
+
+## 7. 版本路线图
+
+| 版本 | 目标 | 核心功能 |
+|------|------|---------|
+| **v0.1 MVP** | 单租户种子验证 | P0 功能全部上线 |
+| **v0.2** | 功能完善 | P1 功能上线,开始多租户测试 |
+| **v0.3** | 商业化就绪 | Windows 客户端、多租户正式开放、系统配置完善 |
+| **v1.0** | 正式发布 | 新房模块、合同/财务模块路线图确认 |
diff --git a/Project/fonrey/UI&UX/UI_SYSTEM.md b/Project/fonrey/UI&UX/UI_SYSTEM.md
new file mode 100644
index 00000000..81cfea29
--- /dev/null
+++ b/Project/fonrey/UI&UX/UI_SYSTEM.md
@@ -0,0 +1,987 @@
+> **For AI assistants**: Read this entire file before writing any frontend code or template. All decisions here are final. When in doubt about styling, spacing, color, or component behavior — the answer is in this document. Do not invent values.
+
+---
+
+## Design Philosophy
+
+**Core aesthetic**: Clean, functional, low-friction.
+We build tools, not experiences. Every UI element must earn its place.
+
+**Principles** (in priority order):
+
+1. **Clarity over cleverness** — if you have to explain the UI, it failed
+2. **Density over whitespace** — our users are power users; do not waste screen space
+3. **Consistency over novelty** — use existing patterns before inventing new ones
+4. **Motion is functional** — animate only to communicate state change, never for decoration
+
+**Anti-patterns we actively avoid**:
+
+- Skeleton loaders for data that loads in < 300ms (use a spinner instead)
+- Modal dialogs for destructive actions that are easily reversible
+- Infinite scroll (we use pagination; users need to share URLs to specific pages)
+- Tooltips on mobile
+- Full-width buttons on desktop (max-width: 320px for standalone CTAs)
+- Mixing card and table layouts in the same list view
+- Auto-submitting forms on change without explicit confirmation
+- Generic error messages ("Something went wrong" is never acceptable)
+- `window.alert` — always use our Dialog/Toast components
+
+---
+
+## Design Tokens
+
+All colors, spacing, radius, and shadow values must come from these tokens. **Never write raw hex values or arbitrary px values in components.**
+
+### Color System
+
+We use CSS custom properties (variables). Define these in `base.css` under `:root`.
+
+```css
+:root {
+ /* Backgrounds */
+ --color-bg-base: #ffffff; /* page background */
+ --color-bg-subtle: #f9fafb; /* card, sidebar, panel backgrounds */
+ --color-bg-muted: #f3f4f6; /* disabled states, placeholders, table zebra */
+
+ /* Text */
+ --color-text-primary: #111827; /* body text */
+ --color-text-secondary:#6b7280; /* labels, captions, helper text */
+ --color-text-disabled: #9ca3af; /* disabled text */
+ --color-text-inverse: #ffffff; /* text on dark/colored backgrounds */
+
+ /* Borders */
+ --color-border: #e5e7eb; /* default borders */
+ --color-border-strong: #d1d5db; /* focused, emphasized borders */
+
+ /* Brand / Accent — orange as primary action color */
+ --color-accent: #f97316; /* primary actions, links, active states */
+ --color-accent-hover: #ea6c0a; /* hover state of accent */
+ --color-accent-subtle: #fff7ed; /* light tint for accent backgrounds */
+
+ /* Semantic */
+ --color-success: #16a34a; /* confirmations, completed states */
+ --color-success-bg: #f0fdf4;
+ --color-warning: #d97706; /* non-blocking alerts */
+ --color-warning-bg: #fffbeb;
+ --color-danger: #dc2626; /* destructive actions, errors */
+ --color-danger-bg: #fef2f2;
+ --color-info: #2563eb; /* informational, neutral alerts */
+ --color-info-bg: #eff6ff;
+}
+```
+
+**Mapping to Tailwind**: Configure `tailwind.config.js` to extend colors using these CSS variables so you can write `text-accent`, `bg-bg-subtle`, etc. If that is not yet configured, use the following Tailwind equivalents as a temporary fallback — but never use arbitrary hex:
+
+| Token | Tailwind equivalent |
+|---|---|
+| `--color-accent` | `text-orange-500` / `bg-orange-500` |
+| `--color-accent-hover` | `hover:bg-orange-600` |
+| `--color-text-primary` | `text-gray-900` |
+| `--color-text-secondary` | `text-gray-500` |
+| `--color-text-disabled` | `text-gray-400` |
+| `--color-border` | `border-gray-200` |
+| `--color-border-strong` | `border-gray-300` |
+| `--color-bg-subtle` | `bg-gray-50` |
+| `--color-bg-muted` | `bg-gray-100` |
+| `--color-danger` | `text-red-600` / `bg-red-600` |
+| `--color-success` | `text-green-600` |
+
+**Rule**: If you find yourself writing `text-gray-500`, stop. Ask: is this a label, a caption, or secondary content? Then use the semantic token. If you find yourself writing `text-gray-400`, that is disabled text.
+
+---
+
+### Spacing Scale
+
+We use a **4px base grid**. Only these values are permitted:
+
+| px | Tailwind |
+|---|---|
+| 4px | `p-1` / `m-1` / `gap-1` |
+| 8px | `p-2` / `m-2` / `gap-2` |
+| 12px | `p-3` / `m-3` / `gap-3` |
+| 16px | `p-4` / `m-4` / `gap-4` |
+| 24px | `p-6` / `m-6` / `gap-6` |
+| 32px | `p-8` / `m-8` / `gap-8` |
+| 48px | `p-12` / `m-12` / `gap-12` |
+| 64px | `p-16` / `m-16` / `gap-16` |
+| 96px | `p-24` / `m-24` / `gap-24` |
+
+**Never use**: `p-5`, `p-7`, `p-9`, `p-10`, `p-11` — these break the grid.
+
+---
+
+### Border Radius
+
+```
+--radius-sm: 4px → rounded-sm (inputs, badges, table cells)
+--radius-md: 8px → rounded (cards, buttons) ← default
+--radius-lg: 12px → rounded-xl (modals, drawers, panels)
+--radius-full: 9999px → rounded-full (avatars, pill badges, toggles)
+```
+
+**Rule**: Never mix radius sizes within the same component. A card with `rounded` should not have children with `rounded-xl`.
+
+---
+
+### Elevation / Shadow
+
+```
+--shadow-sm → shadow-sm (subtle card lift, focused inputs)
+--shadow-md → shadow-md (dropdowns, popovers, floating panels)
+--shadow-lg → shadow-xl (modals, drawers, dialogs)
+```
+
+**Rules**:
+- Never use `drop-shadow` filter — use `box-shadow` (`shadow-*`) only
+- Never use `shadow-2xl` — `shadow-xl` is the maximum
+
+---
+
+## Typography
+
+**Font stack**:
+- UI: `Inter` (variable weight, loaded via `` from self-hosted or CDN)
+- Code / monospace data: `JetBrains Mono` (used for code blocks and numeric data columns only)
+- Never import fonts via Google Fonts `@import` inside CSS — use `` in `
`
+
+**Type scale** — use only these sizes, no arbitrary values:
+
+| Tailwind class | Size | Weight | Line-height | Usage |
+|---|---|---|---|---|
+| `text-xs` | 12px | 400 | 1.5 | Labels, badges, metadata, table captions |
+| `text-sm` | 14px | 400 | 1.5 | Body text, secondary content, form helpers |
+| `text-base` | 16px | 400 | 1.6 | Primary body text |
+| `text-lg` | 18px | 500 | 1.4 | Section headings, drawer titles |
+| `text-xl` | 20px | 600 | 1.3 | Page sub-headings |
+| `text-2xl` | 24px | 700 | 1.2 | Page titles |
+| `text-3xl` | 30px | 700 | 1.1 | Hero headings only — never in app UI |
+
+**Rules**:
+- Max 2 font sizes per component
+- `font-medium` (500) is the minimum weight for anything interactive (buttons, links, tab labels)
+- Never use `text-3xl` in application views — only marketing/landing pages
+- Body text line length: max 72 characters (`max-w-prose`)
+- Numbers in tables: use `font-mono` (JetBrains Mono) and `tabular-nums`
+
+---
+
+## Page Layout
+
+### App Shell
+
+The standard application layout is a **fixed sidebar + scrollable main content** pattern.
+
+```
+┌─────────────────────────────────────────────────────┐
+│ Top Nav Bar (h-14, fixed, z-30) │
+├──────────────┬──────────────────────────────────────┤
+│ │ Page Header (breadcrumb + actions) │
+│ Sidebar ├──────────────────────────────────────┤
+│ (w-56, │ │
+│ fixed, │ Main Content Area │
+│ z-20) │ (overflow-y-auto, flex-1) │
+│ │ │
+│ │ │
+└──────────────┴──────────────────────────────────────┘
+```
+
+- **Top Nav**: `h-14`, `bg-white`, `border-b border-gray-200`, `fixed top-0 inset-x-0 z-30`
+- **Sidebar**: `w-56`, `bg-gray-50`, `border-r border-gray-200`, `fixed left-0 top-14 bottom-0 z-20`, `overflow-y-auto`
+- **Main**: `ml-56 mt-14`, `min-h-screen`, `bg-white`
+- **Page content padding**: `p-6` on all sides
+
+### Page Header
+
+Every page has a header section directly below the top nav, inside the main area:
+
+```html
+
+
+
+
+
+
住宅出售
+
+
+
+
+
+```
+
+### Sidebar Navigation
+
+- Active item: `bg-orange-50 text-orange-600 font-medium border-r-2 border-orange-500`
+- Inactive item: `text-gray-600 hover:bg-gray-100`
+- Section label: `text-xs font-semibold text-gray-400 uppercase tracking-wide px-3 mt-4 mb-1`
+- Item height: `h-9` (`36px`)
+- Item padding: `px-3`
+- Icon size: `w-4 h-4`, `mr-2`
+- Second-level items: `pl-9`
+
+---
+
+## Core Component Specs
+
+### Button
+
+**Variants**:
+
+| Variant | Tailwind classes | Use case | Never use for |
+|---|---|---|---|
+| `primary` | `bg-orange-500 hover:bg-orange-600 text-white font-medium rounded` | Single main CTA per view | Destructive actions |
+| `secondary` | `bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium rounded` | Secondary actions | Main CTA |
+| `ghost` | `bg-transparent hover:bg-gray-100 text-gray-600 font-medium rounded` | Toolbar actions, low-priority | Standalone CTAs |
+| `danger` | `bg-red-600 hover:bg-red-700 text-white font-medium rounded` | Irreversible destructive actions only | Anything reversible |
+| `link` | `text-orange-500 hover:text-orange-600 underline-offset-2 hover:underline` | Inline navigation links only | Form submissions |
+
+**Sizes**:
+
+| Size | Height | Padding | Font |
+|---|---|---|---|
+| `sm` | `h-7` (28px) | `px-3` | `text-xs` |
+| `md` | `h-9` (36px) | `px-4` | `text-sm` — default |
+| `lg` | `h-11` (44px) | `px-6` | `text-base` |
+
+**States** (all must be handled in every button):
+
+- `default` — base styles above
+- `hover` — defined in variant above
+- `active` — `active:scale-95 active:opacity-90`
+- `focus-visible` — `focus-visible:outline-2 focus-visible:outline-orange-500 focus-visible:outline-offset-2`
+- `disabled` — `disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none`
+- `loading` — spinner icon replaces or precedes label; button is disabled
+
+**Loading state rule**: Show spinner and disable button immediately on click. Never allow double-submission.
+
+```html
+
+
+
+
+
+```
+
+**Icon buttons**: Always include `aria-label`. Never use icon-only buttons as the sole primary CTA.
+
+---
+
+### Form Inputs
+
+**Anatomy** (always in this order, no exceptions):
+
+```
+[Label] ← always visible, never placeholder-only
+[Input field]
+[Helper text] ← optional, describes expected format
+[Error message] ← replaces helper text on error
+```
+
+**Base input class**:
+```
+block w-full rounded border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900
+placeholder:text-gray-400
+focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500
+disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
+```
+
+**States**:
+
+| State | Additional classes |
+|---|---|
+| `default` | `border-gray-300` |
+| `focus` | `ring-2 ring-orange-500 border-orange-500` |
+| `error` | `border-red-500 ring-2 ring-red-500 focus:ring-red-500` |
+| `disabled` | `bg-gray-100 text-gray-400 cursor-not-allowed` |
+| `readonly` | `bg-gray-50 text-gray-600 cursor-default` |
+
+**Label**:
+```html
+
+```
+
+**Helper text**:
+```html
+
格式说明文字
+```
+
+**Error message**:
+```html
+
+
+ 具体的错误原因,可操作的
+
+```
+
+**Rules**:
+- Label always above input, never to the side (exception: checkbox and radio)
+- Placeholder text is NOT a label — both must exist
+- Error messages: specific and actionable ("请输入有效的手机号", not "格式错误")
+- Required fields: mark with `*` next to label; explain at top of form ("* 为必填项")
+- Never disable a submit button to prevent submission — show errors inline instead
+- `aria-describedby` must link input to its error element when error is shown
+
+**Select / Dropdown**:
+```
+block w-full rounded border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900
+focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500
+```
+
+**Textarea**: same as input, add `resize-y min-h-[80px]`. Always include character counter when there is a max length.
+
+---
+
+### Data Table
+
+**Structure**:
+```
+[Toolbar: bulk actions + filter chips + column visibility + export]
+[Table: sticky header, checkbox col, data cols, actions col]
+[Pagination: count + page controls + per-page selector + jump-to]
+```
+
+**Table base classes**:
+```html
+
+
+
+
+
+
+
+
+
+
+ 列名
+
+
+
+
+
+
...
+
+
+ ...
+
+
+
+
+
+```
+
+Add `group` class to `
` to enable `group-hover` on the actions column.
+
+**Column rules**:
+- Numbers: `text-right font-mono tabular-nums`
+- Dates: relative time for < 7 days ("2小时前"), absolute date for older ("2025-10-15")
+- Status: always a colored badge — never plain text
+- Long text: `truncate max-w-[200px]` with `title` attribute showing full value
+
+**Default page size**: 25 rows. Options: 10 / 25 / 50 / 100.
+
+**Pagination display**: Always show total count — "共 3,629 条,第 1–25 条"
+
+**Empty state** (never just "暂无数据"):
+```html
+
+```
+
+**Rules**:
+- Always trap focus inside modal (`x-trap` from Alpine.js Focus plugin)
+- ESC key always closes (Alpine handles this automatically with `x-trap`)
+- Click outside backdrop closes — unless form has unsaved changes → show confirmation
+- Never nest modals — use a multi-step flow instead
+- Destructive confirm dialogs: danger button on RIGHT, cancel on LEFT
+- Never auto-close a modal after an async action — wait for user to dismiss
+
+---
+
+### Drawer / Slide-over Panel
+
+Used for editing content with many fields where the main page should remain visible for reference.
+
+**When to use Drawer vs Modal**:
+
+| Scenario | Component |
+|---|---|
+| Many fields, needs scrolling | Drawer |
+| Few fields (≤ 5), simple confirm | Modal |
+| User needs to reference main page data while editing | Drawer |
+
+**Standard drawer** (slides in from the right):
+```html
+
+```
+
+---
+
+### Date Range Picker
+
+Use **Flatpickr** (CDN, ~16KB, zero framework dependency):
+
+```javascript
+flatpickr("#date-range-input", {
+ mode: "range",
+ showMonths: 2,
+ dateFormat: "Y-m-d",
+ locale: "zh",
+});
+```
+
+Override Flatpickr default styles with Tailwind to match our design system. Never build a date picker from scratch.
+
+---
+
+### Photo Gallery / Image Management
+
+- Upload: **Filepond** (~50KB, zero framework dependency) — drag-and-drop, preview, progress, multi-file queue
+- Drag-to-reorder: **SortableJS** (~3KB) — use `handle: '.drag-handle'`
+- Lightbox preview: **Viewer.js** (~5KB) — zoom, rotate, thumbnail strip
+
+All three are framework-free pure JS libraries, fully compatible with HTMX + Alpine.js.
+
+---
+
+## State & Feedback Patterns
+
+### Loading States
+
+| Duration | Pattern |
+|---|---|
+| < 300ms | Nothing — avoid flash of spinner |
+| 300ms – 1s | Inline spinner (`animate-spin`) |
+| 1s – 3s | Spinner + "加载中..." text |
+| > 3s | Progress bar + estimated time |
+| Background Celery task | Subtle pulsing indicator in top nav |
+
+HTMX automatically adds `htmx-request` class during requests — use it to show/hide indicators:
+```html
+
+
+
+```
+
+---
+
+### Empty States
+
+Every list, table, and data view must handle the empty state. Required elements:
+
+1. Relevant icon (not a generic "no data" icon — use something contextually relevant)
+2. Friendly headline ("暂无房源")
+3. Explanation ("符合当前筛选条件的房源将出现在这里")
+4. CTA if the user can fix it ("新增第一条房源 →")
+
+```html
+