Files
th-hotel-simple/docs/project/frontend-development-guidelines.md
2026-07-10 17:36:56 +08:00

489 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TH Hotel 前端开发规范
## 1. 文档定位
本文整理 TH Hotel 当前前端开发约定,供本项目 UI 开发和其他 agent 协作参考。
本文是当前项目专属补充,不应整份复制到其他项目。可复用的通用前端规则应沉淀到 `docs/import/reusable/frontend-development-guidelines.md`
前端只负责展示、交互、人工确认、调试入口和调用本项目后端。前端不得直接调用 OHIP、
SuperAgent、AgentBus 或任何持有 Secret 的外部系统。
## 2. 技术栈
`client/package.json` 为准,当前前端技术栈如下:
| 类别 | 当前选择 |
| --- | --- |
| 运行时 | Node.js 22.13+ LTS |
| 框架 | Vue 3.5.x |
| 语言 | TypeScript ~6.0.xStrict 模式 |
| 构建 | Vite 8.x |
| 组件写法 | Composition API`<script setup lang="ts">` |
| 路由 | Vue Router 5.x |
| 客户端状态 | Pinia 3.x |
| 服务端状态 | TanStack Vue Query 5.x |
| UI 组件库 | PrimeVue 4.x |
| 图标 | PrimeIcons |
| 国际化 | vue-i18n 11.x |
| Vue SFC 类型检查 | vue-tsc |
| 测试 | Vitest 4.x、Vue Test Utils 2.x、jsdom |
| Lint | ESLint 10.x、typescript-eslint 8.x、eslint-plugin-vue |
不引入通用 Admin Template。新增 UI 应延续现有设计语言和样式 token。
兼容性要求:
- Vite、ESLint 和 Vitest 的 Node.js 要求必须同时满足,当前统一使用 Node.js 22.13+ LTS。
- TypeScript 版本必须与 `typescript-eslint` 支持范围一致;在确认 `typescript-eslint` 支持更高版本前TypeScript 锁定为 `~6.0.x`,不得使用宽泛的 `^6.0.0`
- Vue SFC 项目必须配置 `vue-tsc` 做类型检查,不能只依赖 `tsc`
- 升级 Vue、Vite、TypeScript、ESLint、Vitest、PrimeVue、Pinia、Vue Router 或 Vue Query 前,必须先检查版本兼容性并更新本文。
## 3. 目录约定
当前前端目录:
```text
client/src
├── components
│ ├── common
│ └── reservation
├── composables
├── i18n
│ └── locales
├── layouts
├── router
├── services
├── stores
├── styles
├── tests
├── types
└── views
└── reservation
```
目录规则:
- API 请求封装放入 `src/services`
- 手写类型定义放入 `src/types`
- 可复用组合逻辑放入 `src/composables`
- 路由定义放入 `src/router`
- Pinia Store 放入 `src/stores`
- i18n 入口和语言包放入 `src/i18n`
- 页面级组件放入 `src/views`
- 可复用业务组件放入 `src/components/<domain>`
- 通用布局放入 `src/layouts`
- 全局样式和设计 token 放入 `src/styles`
- 未来 OpenAPI 生成代码放入 `src/generated`,禁止手工修改。
## 4. 组件规范
- 页面和组件统一使用 Vue 3 Composition API。
- `.vue` 文件使用 `<script setup lang="ts">`
- Props、Emits、响应式状态和服务返回值必须有明确类型。
- 避免在模板中写复杂业务逻辑,复杂逻辑放入 computed、composable 或服务层。
- 不在组件里直接拼接后端 URL统一通过 services。
- 不在组件里直接访问浏览器全局存储保存业务事实。
- 不把中文或英文显示文案作为业务判断依据。
- 不为未知的财务部、前台流程硬编码页面、状态或菜单。
## 5. 状态管理规则
Pinia 只保存客户端状态,例如:
- 当前用户上下文
- 当前酒店
- 当前部门
- 语言和页面偏好
- 轻量 UI 状态
服务端数据使用 TanStack Vue Query
- 列表、详情、Timeline、Task、Preflight、SourceMessage 等后端数据通过 Query 获取。
- Mutation 完成后按 query key 精准失效或更新缓存。
- 不把服务端数据长期复制进 Pinia。
- 不用 Pinia 绕过后端权限、状态机或校验。
## 6. API 请求规范
- 浏览器只能调用本项目后端。
- 所有请求封装到 `src/services`
- 服务函数返回明确 TypeScript 类型。
- 统一使用 `httpClient` 处理 JSON、错误和 Problem Detail。
- 前端不直接调用 OHIP、SuperAgent、AgentBus、数据库或对象存储。
- 前端不发送后端 Secret、replay access key、Provider API Key 或 Oracle 凭证。
- 调试页面如果需要触发受控后端能力,应由后端提供 debug-only 包装接口Secret 保留在后端。
## 7. 国际化规范
首期交付:
```text
zh-CN
en-US
```
架构预留:
```text
th-TH
```
规则:
- 新增页面和组件不得硬编码中英文业务文案。
- 系统固定文案必须使用稳定 i18n key。
- 后端返回稳定业务代码和必要 `labelKey`,前端按 key 显示。
- 前端不得把中文或英文文本作为业务判断依据。
- 酒店配置名称、客人消息、姓名、备注、邮件正文和附件内容保持原文。
- 日期、时间、数字和货币按当前语言、酒店时区和币种配置格式化。
- API 仍传递结构化原始值,不传本地化展示字符串作为业务参数。
## 8. 类型与业务代码
- TypeScript 类型应贴近后端 API 契约。
- 稳定业务代码使用 string union 或枚举型常量,避免散落 magic string。
- 动态操作参数使用稳定 `fieldKey`
- 表单字段展示使用 `labelKey` 或 i18n key。
- 不用 `label`、中文标题或英文标题做字段标识。
- 接口字段变更时,同步更新 `src/types`、services、页面和测试。
## 9. UI 与交互规范
- UI 组件库使用 PrimeVue。
- 避免引入大型 Admin Template。
- 页面应优先表达业务主线,而不是堆叠技术字段。
- Debug 页面可以显示技术 ID但业务页面应优先展示可理解的业务状态和来源摘要。
- 预检、人工复核、任务确认要清楚区分:
- 外部 Provider 建议
- 人工确认值
- 最终执行结果
- 邮件原文、附件详情和外部响应详情应通过受控详情接口读取,不默认塞进所有 Task 页面。
- 涉及客户信息、付款信息和附件时,应默认最小展示。
## 10. 前端安全规范
- `VITE_*` 变量会暴露到浏览器构建产物,不能保存 Secret。
- 前端不得保存数据库密码、OHIP 凭证、SuperAgent API Key、AgentBus Token、replay key。
- 不在 LocalStorage、SessionStorage 或 URL 中保存敏感业务数据。
- 错误提示不展示 Authorization、Cookie、Token、原始邮件全文或附件 URL。
- 测试夹具不得使用真实客人、真实酒店或真实支付信息。
## 11. 配置规范
常用公开配置:
```text
VITE_API_BASE_URL=http://localhost:8080
VITE_APP_ENV=local
VITE_DEFAULT_LOCALE=zh-CN
VITE_ENABLE_MOCKS=false
```
本地开发如需代理后端:
```text
VITE_API_PROXY_TARGET=http://127.0.0.1:8081
```
规则:
- 公开配置可以放 `VITE_*`
- Secret 一律不进入 `VITE_*`
- Reservation 查询默认不再依赖 `VITE_RESERVATION_HOTEL_ID`。单酒店阶段可不传 `hotel_id`,由后端按平台酒店表唯一 `ACTIVE` 酒店或当前登录用户上下文解析;如前端有酒店选择器,只传当前选中的酒店,后端负责校验访问权限。
- `VITE_RESERVATION_HOTEL_ID` 仅允许作为本地夹具或临时调试覆盖,不作为 test / prod 业务事实来源。
- 生产需要运行时配置时,应由部署系统生成公开配置文件,例如 `/app-config.json`
- 需要秘密的外部调用一律经后端代理或适配器。
## 12. 路由规范
- Vue Router 管理部门级路由。
- 新增业务路由前确认对应后端 API 和权限边界。
- 平台 Shell 只保留已确认部门入口,不硬编码未知流程。
- 详情页路由参数使用稳定 ID。
- 页面刷新后必须能通过后端详情接口恢复必要状态,不依赖内存临时状态。
## 13. 表单与人工确认规范
- 人工确认表单优先使用后端返回的字段契约。
- 保存草稿时使用稳定 `fieldKey`
- 不把显示文案作为提交字段名。
- 表单应展示字段来源Provider 建议、来源消息、人工修正、最终值。
- 提交前前端可做基础格式校验,但最终业务校验在后端。
- 发生版本冲突时,应提示用户刷新或重新确认,不静默覆盖。
## 14. Agent 前端开发 Skill 使用
当前项目后续如果由 Codex 或其他 agent 开发前端,涉及 UI、交互、视觉或浏览器验证时应优先使用当前环境可用的前端相关 skill。Skill 只作为辅助,不能覆盖本项目 Vue、TypeScript、Vite、PrimeVue、Pinia、Vue Query、i18n、接口契约和安全边界。
当前项目建议:
- 新页面、复杂交互或业务页面信息架构:优先使用 `design-taste-frontend``ui-ux-pro-max`
- 设计 token、组件规范和样式体系优先使用 `design-system`
- 已有页面视觉重构:优先使用 `redesign-existing-projects`
- 根据截图、设计稿或视觉参考还原页面:优先使用 `image-to-code`
- 浏览器验证、截图检查、点击路径和响应式检查:优先使用 `browser:control-in-app-browser`
- 需要生成插图、图片或占位视觉资产:可使用 `imagegen`
- `ui-styling` 可参考其可访问性、组件状态和样式组织原则,但本项目未确认使用 shadcn 或 Tailwind 前,不得因此引入 shadcn、Tailwind 或替换 PrimeVue。
- `superpowers:brainstorming``superpowers:writing-plans``superpowers:test-driven-development``superpowers:verification-before-completion` 用于需求澄清、计划、测试和验收,不属于 UI 框架选择依据。
交付要求:
- 涉及 UI 或交互变更时,应说明使用了哪些 skill或说明为什么未使用。
- 涉及可视化结果时,应尽量提供浏览器截图、响应式检查或交互验证结果。
- 如果 skill 建议与本项目规范冲突,以本项目规范、接口契约和用户明确要求为准。
## 15. 测试与检查命令
前端最低检查:
```bash
cd client
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```
`pnpm typecheck` 应执行 `vue-tsc --noEmit` 或等价命令,确保 `.vue` 单文件组件也被类型检查覆盖。
聚焦开发时可运行:
```bash
cd client
pnpm test -- SomeSpecName.spec.ts
```
如果命令尚未配置或因环境问题无法运行,必须明确说明,不能假装通过。
## 16. Git 与协作流程
- 改动前说明目标、范围和将修改的文件。
- 每次只处理一个模块、一个页面或一个纵向切片。
- 不修改与当前任务无关的用户变更。
- 前后端接口变化前,先确认 API 契约和字段映射。
- 修改后运行项目已配置的检查命令。
- Git commit message 使用中文,清楚说明本次提交的业务或技术变更。
## 17. 前端提交前检查清单
- [ ] 是否只调用本项目后端?
- [ ] 是否没有把 Secret 放入 `VITE_*`、源码、测试或 URL
- [ ] 新文案是否已进入 i18n
- [ ] 是否避免用中文或英文显示文本做业务判断?
- [ ] API 请求是否放在 `src/services`
- [ ] 手写类型是否放在 `src/types`
- [ ] 可复用逻辑是否放在 `src/composables`
- [ ] 服务端数据是否使用 Vue Query而不是长期塞进 Pinia
- [ ] 是否没有手工修改 `src/generated`
- [ ] 涉及 UI、交互或视觉变更时是否使用或说明未使用合适的前端 / 设计 skill
- [ ] 涉及可视化结果时,是否做过浏览器、截图或响应式验证?
- [ ] 是否运行了 `pnpm lint` 或说明了无法运行原因?
- [ ] 是否运行了 `pnpm typecheck` 或说明了无法运行原因?
- [ ] 是否运行了 `pnpm test` 或说明了无法运行原因?
- [ ] 是否运行了 `pnpm build` 或说明了无法运行原因?
## 18. Reservation 前端低保真结构图与流程图
本节只记录当前 Reservation 前端第一版信息架构草图,用于对齐页面结构和业务主线。具体 checkpoint 设计、接口契约和实施计划应放入单独需求或设计文档,不在本规范中展开。
### 18.1 订单工作台低保真结构图
```text
┌────────────────────────────────────────────────────────────────────────────┐
│ Hotel OMS 中文 / EN / ไทย 主题 / 用户 │
├───────────────┬────────────────────────────────────────────────────────────┤
│ │ 订单工作台 │
│ 订单管理 │ │
│ 任务队列 │ ┌──────────────────────────────────────────────────────┐ │
│ 消息来源 │ │ 筛选区 │ │
│ 系统状态 │ │ 日期范围 | 业务号 | 订单状态 | 任务状态 | 任务卡类型 │ │
│ │ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │
│ │ │ 订单 / 任务列表 │ │
│ │ │ 业务号 订单状态 当前任务 任务状态 操作 │ │
│ │ │ GRP-001 ACTIVE New Booking 待确认 查看 │ │
│ │ │ TMP-002 TEMP Fallback 人工复核 查看 │ │
│ │ │ CNF-003 ACTIVE Cancel READY 执行 │ │
│ │ └──────────────────────────────────────────────────────┘ │
└───────────────┴────────────────────────────────────────────────────────────┘
```
中文说明:
- 左侧导航只保留已确认的 Reservation 相关入口,不提前暴露未确认部门流程。
- 工作台首屏服务于“找到需要处理的订单或任务”,不是普通订单 CRUD 首页。
- 列表行应同时表达订单状态、当前任务、任务状态和下一步动作。
### 18.2 任务列表低保真结构图
```text
┌────────────────────────────────────────────────────────────────────────────┐
│ Hotel OMS 中文 / EN / ไทย 主题 / 用户 │
├───────────────┬────────────────────────────────────────────────────────────┤
│ │ 任务列表 │
│ 订单列表 │ │
│ 任务列表 │ ┌──────────────────────────────────────────────────────┐ │
│ 消息来源 │ │ 筛选区 │ │
│ 系统状态 │ │ 日期范围 | 订单号/业务号 | 任务类型 | 任务状态 | 可处理 │ │
│ │ │ 来源邮件关键词 | 是否参与队列 │ │
│ │ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │
│ │ │ 任务数据 │ │
│ │ │ 任务号 订单号 任务卡 状态 来源邮件 │ │
│ │ │ #10001 GRP-001 New Booking 待确认 Booking Req │ │
│ │ │ #10002 GRP-001 Update 阻塞中 Booking Upd │ │
│ │ │ #10003 TMP-002 Fallback 人工复核 Unknown Msg │ │
│ │ │ │ │
│ │ │ 队列 | 可处理 | 邮件会话 | 查看任务 | 查看订单 │ │
│ │ └──────────────────────────────────────────────────────┘ │
└───────────────┴────────────────────────────────────────────────────────────┘
```
中文说明:
- 任务列表是独立菜单,服务于“按任务处理”的工作方式,不是订单列表的复制。
- 列表数据来自 `GET /api/reservation/tasks`;该接口已存在第一版,前端应直接接真实接口,不展示 fixture 假数据。
- 每行任务至少展示 `task_id`、关联订单、任务卡名称、任务类型 / 子类型、任务状态、队列顺序、是否可处理、只读原因和来源邮件摘要。
- 每行操作保留“查看任务”“查看订单”“查看邮件会话”三个入口;邮件会话入口按该任务的 `source_message_id``external_conversation_id` 打开。
- 如果来源邮件会话字段暂未接入,邮件会话入口置灰或展示“接口待补”,不使用假会话数据。
### 18.3 订单详情与任务卡低保真结构图
```text
┌────────────────────────────────────────────────────────────────────────────┐
│ 订单详情 GRP-001 / TMP-20260707-0001 ACTIVE / TEMPORARY │
│ 主来源消息 #190... 业务号来源 AI_CANDIDATE 最近更新 2026-07-08 │
├────────────────────────────────────────────────────────────────────────────┤
│ 订单摘要 │
│ Group Code / Confirmation No. / 临时单号 / 订单状态 / 主来源消息 / 创建时间 │
├───────────────────────────────┬────────────────────────────────────────────┤
│ 同订单任务队列 │ 当前任务卡 │
│ │ │
│ ① New Booking 待确认 邮件会话│ New Booking 卡 │
│ ② Update 阻塞中 邮件会话│ ┌ 证据区 ───────────────────────────────┐ │
│ ③ Trace Notes 未开始 邮件会话│ │ 生成原因 / 来源消息摘要 / 附件 / 邮件会话│ │
│ │ └──────────────────────────────────────┘ │
│ │ ┌ 基础信息 ─────────────────────────────┐ │
│ │ │ Group Code / Confirmation No. / 类型 │ │
│ │ └──────────────────────────────────────┘ │
│ │ ┌ 入住与房型 ───────────────────────────┐ │
│ │ │ 入住日期 / 离店日期 / 房量 / 房型 / Rate│ │
│ │ └──────────────────────────────────────┘ │
│ │ │
│ │ [保存草稿] [确认任务] │
├───────────────────────────────┴────────────────────────────────────────────┤
│ OPERA 模拟操作 │
│ ① 预检查 PENDING / SUCCEEDED / FAILED ② 写入 PENDING / SUCCEEDED / FAILED │
│ │
│ 审计时间线AI 创建 → 保存草稿 → 人工确认 → OPERA 预检查 → OPERA 写入 │
└────────────────────────────────────────────────────────────────────────────┘
```
中文说明:
- 订单详情页必须沿用“订单归档容器 + 同订单任务队列 + 当前任务卡 + OPERA 模拟操作 + 审计时间线”的既有结构,不另起一套订单详情布局。
- 后续任务如果被前置任务阻塞,应在任务队列和任务卡操作区同时体现只读状态。
- 任务卡字段必须按后端字段矩阵分组渲染,不直接把 AI 原始 JSON 展示成自由表单。
- 邮件入口按任务粒度展示:每个任务可能来自不同 `source_message_id`,也可能落在不同 `external_conversation_id`。点击“邮件会话”只打开该任务来源消息所在的完整邮件会话,不代表订单下所有来源消息的合集。
- 当前任务卡证据区展示来源消息摘要、附件摘要和“查看邮件会话”入口完整邮件正文、HTML 和附件详情进入单独邮件会话详情页查看,避免订单详情页被长邮件撑开。
- OPERA 模拟和审计是任务确认后的执行轨迹,不应和 AI 原始建议混在同一区域。
### 18.4 任务详情低保真结构图
```text
┌────────────────────────────────────────────────────────────────────────────┐
│ 任务详情 #10002 Update Booking / RATE_CHANGE PENDING_CONFIRM │
│ 关联订单 GRP-001 [查看订单] 来源邮件 Booking Update [查看邮件会话] │
├────────────────────────────────────────────────────────────────────────────┤
│ 处理状态 │
│ 可处理 / 阻塞原因 / 队列顺序 / 前置任务状态 / 只读说明 │
├────────────────────────────────────────────────────────────────────────────┤
│ 证据区 │
│ 生成原因 / 来源消息摘要 / 附件摘要 / 完整邮件会话入口 │
├────────────────────────────────────────────────────────────────────────────┤
│ 任务卡字段 │
│ 基础信息Group Code / Confirmation No. / 业务动作 │
│ 变更内容before_after / rate_code / fix_charge_items │
│ 人工复核visible_reason / required fields / validation │
├────────────────────────────────────────────────────────────────────────────┤
│ 操作区 │
│ [保存草稿] [确认任务] │
├────────────────────────────────────────────────────────────────────────────┤
│ OPERA 模拟操作 │
│ 预检查 / 写入 / 重试状态 / 最近错误 │
├────────────────────────────────────────────────────────────────────────────┤
│ 审计流水 │
│ AI 创建 → 草稿保存 → 人工确认 → OPERA 预检查 → OPERA 写入 │
└────────────────────────────────────────────────────────────────────────────┘
```
中文说明:
- 任务详情页聚焦单个任务卡的查看、编辑、确认和执行轨迹,不承载同订单全局队列;同订单全局队列仍回到订单详情页查看。
- 任务详情读取来自 `GET /api/reservation/tasks/{taskId}`草稿保存、确认任务、OPERA 模拟和审计流水使用后端已存在的任务工作流接口。
- 证据区只展示来源消息摘要和附件摘要完整邮件正文、HTML、历史邮件和回复进入邮件会话详情页。
- 字段区按后端 `fields[]``display_area` 和字段规则分组渲染,不把 AI 原始 JSON 直接展示为自由表单。
- 如果任务不可处理,操作区应保持只读,并展示 `availability``readonly_reason_code` 给出的原因。
### 18.5 邮件会话详情低保真结构图
```text
┌────────────────────────────────────────────────────────────────────────────┐
│ 邮件会话详情 Re: Booking Update 返回订单 / 返回任务 │
│ Conversation thread-20260708-001 共 6 封 最近收到 2026-07-08 11:28 │
├────────────────────────────────────────────────────────────────────────────┤
│ 会话摘要 │
│ 酒店 / 渠道 EMAIL / 发件人汇总 / 主题 / 关联订单 GRP-001 / 关联任务 3 个 │
├────────────────────────────────────────────────────────────────────────────┤
│ 邮件时间线 │
│ │
│ ┌ 2026-07-06 09:10 guest@example.com Booking Request ────────────────┐ │
│ │ 邮件全文正文或已清洗 HTML │ │
│ │ 附件rooming-list.xlsx / voucher.pdf │ │
│ │ 关联New Booking #10001 │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ ┌ 2026-07-07 14:22 hotel@example.com Re: Booking Request ────────────┐ │
│ │ 邮件全文正文或已清洗 HTML │ │
│ │ 附件:无 │ │
│ │ 关联:无 │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ ┌ 2026-07-08 11:28 guest@example.com Re: Booking Update ─────────────┐ │
│ │ 邮件全文正文或已清洗 HTML │ │
│ │ 附件change-request.pdf │ │
│ │ 关联Update Booking #10002 │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
```
中文说明:
- 邮件会话详情页展示同一个 `external_conversation_id` 下的全部邮件,包括历史邮件、当前邮件和后续回复。
- 页面入口可以来自订单详情任务队列、当前任务卡证据区或任务详情页,但落地页统一为“邮件会话详情”,不是单封邮件详情。
- 每封邮件应展示完整正文或清洗后的 HTML、附件列表、内联图片信息、发送 / 接收时间、发件人展示值、主题和关联订单 / 任务。
- 前端不在本页展示假数据;后端接口未接入时显示“接口待接入 / 暂无邮件会话数据”。
- 邮件全文属于用户在邮箱中本来可见的信息,前端页面不做业务层面的截断隐藏;但仍必须通过本项目后端接口读取,不在前端保存外部系统 Secret 或访问 key。
### 18.6 Reservation 任务主流程图
```mermaid
flowchart LR
A["SourceMessage Inbox<br/>来源消息"] --> B["AI 过渡层<br/>保存 AI 原始 JSON"]
B --> C["订单<br/>业务号或临时单号"]
B --> D["任务队列<br/>按 execution_order 排序"]
D --> E["任务卡详情<br/>字段矩阵驱动渲染"]
E --> F["保存草稿<br/>draft_payload"]
E --> G["最终确认<br/>confirmed_payload"]
G --> H["OPERA 模拟操作<br/>预检查 / 写入"]
H --> I["审计时间线<br/>确认、执行、重试记录"]
```
中文说明:
- `SourceMessage Inbox` 是来源事实层,不表达订单、任务或业务结论。
- AI 输出只作为原始建议和证据保存,不能直接改变最终业务状态。
- 用户确认后的 `confirmed_payload` 才能作为 OPERA 模拟操作输入。
- 审计时间线用于串联人工确认、OPERA 执行和重试记录,便于后续追溯。