feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本

包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -0,0 +1,150 @@
# P10-NS4-07表单验证的统一规范
## 简要结论
- 状态:⚠️ 部分解决
- 各表单已实现基本验证逻辑且风格较一致,但缺少文档化的统一规范,部分表单必填标识缺失,后端 Pydantic 422 错误未映射到前端字段级提示。
## 详细审查
### 前端代码
#### 1. 必填标识(`required: true`
| 表单 | 文件 | 必填字段 | 是否配置 `required` | 备注 |
|------|------|----------|---------------------|------|
| 登录 | `Login/index.tsx` | username, password | ✅ 均有 | — |
| 审核通过 | `UserApproval/index.tsx` (ReviewModal) | role | ✅ 有 | `suggestionIndex` 为可选,正确 |
| 审核拒绝 | `UserApproval/index.tsx` (ReviewModal) | reason | ✅ 有 | — |
| 用户编辑 | `UserManagement/index.tsx` (EditModal) | role, siteId | ❌ 均无 `required` | 角色和门店字段未标记必填,用户可提交空值 |
| 用户绑定 | `UserManagement/index.tsx` (BindModal) | assistantId, staffId | ✅ 正确无 `required` | 两个字段均为可选,符合业务逻辑 |
| 线索编辑 | `ClueEditor/index.tsx` | category, summary | ✅ 均有 | `detail` 为可选,正确 |
问题:`EditModal``role``siteId` 字段没有 `required` 规则。虽然后端 `UserEditRequest` 允许 `None`(部分更新语义),但前端 UI 上没有明确告知用户哪些字段是建议填写的。
#### 2. 错误提示文案
所有已配置 `rules` 的表单字段,`message` 均为中文:
- `"请输入用户名"` / `"请输入密码"` — Login
- `"请选择角色"` — UserApproval (approve)
- `"请填写拒绝原因"` — UserApproval (reject)
- `"请选择大类标签"` / `"请输入摘要"` / `"摘要不能超过 200 字符"` — ClueEditor
✅ 文案风格统一,均为 `"请 + 动词 + 名词"` 格式。
#### 3. 验证触发时机
| 表单 | 触发方式 | 说明 |
|------|----------|------|
| Login | `onFinish`(提交验证) | Form 的 `onFinish` 回调Ant Design 默认在提交时触发全量校验 |
| ReviewModal (approve/reject) | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
| EditModal | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
| BindModal | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
| ClueEditor | `form.validateFields()`(手动提交验证) | Modal `onOk` 中手动调用 |
✅ 统一采用提交时验证(`onFinish``validateFields`),未使用实时验证(`onChange`)。风格一致。
注意:所有 Modal 表单均未显式设置 `validateTrigger`Ant Design 默认值为 `onChange`,即用户修改字段后会实时显示/清除错误。这实际上是"提交触发首次校验 + 后续实时反馈"的混合模式,属于 Ant Design 最佳实践。
#### 4. 自定义验证器
`ClueEditor` 使用了 `max: 200` 长度限制规则,其余表单无自定义 `validator`
ExcelUpload 页面不使用 Ant Design Form 进行表单验证,而是:
- 文件类型限制通过 `Upload` 组件的 `accept=".xlsx,.xls"` 属性
- 空文件检查通过 `fileList.length === 0` 手动判断
- 数据校验完全由后端完成,前端展示后端返回的校验结果
这种设计合理——Excel 上传的校验逻辑复杂(表头格式、金额精度、人员匹配等),适合后端统一处理。
#### 5. 表单布局
所有 Modal 内表单统一使用 `layout="vertical"`Login 页面使用默认水平布局。风格基本一致。
### 后端代码
#### 1. Pydantic Schema 验证
| Schema | 文件 | 验证规则 | 备注 |
|--------|------|----------|------|
| `ApproveRequest` | `tenant_users.py` | `role: str = Field(..., min_length=1)` | 必填 + 非空 |
| `RejectRequest` | `tenant_users.py` | `reason: str = Field(..., min_length=1)` | 必填 + 非空 |
| `UserEditRequest` | `tenant_users.py` | 所有字段 `Optional` | 部分更新语义,合理 |
| `UserBindingRequest` | `tenant_users.py` | 所有字段 `Optional` | 合理 |
| `ClueEditRequest` | `tenant_clues.py` | `category: ClueCategory`(枚举), `summary: str = Field(..., min_length=1, max_length=200)` | 枚举校验 + 长度限制 |
| `ClueVisibilityRequest` | `tenant_clues.py` | `is_hidden: bool = Field(...)` | 必填 |
| `ConfirmRequest` | `tenant_excel.py` | `upload_id: int`, `resolutions: list[Resolution]` | 结构化验证 |
✅ 后端 Pydantic 验证规则与前端 rules 基本对齐。
#### 2. 手动验证(路由层)
Excel 上传路由 (`tenant_excel.py`) 包含额外的手动验证:
- `upload_type` 枚举校验
- 文件扩展名校验(`.xlsx/.xls`
- 文件内容非空校验
- Excel 解析成功校验
- 数据行非空校验
客户搜索路由 (`tenant_clues.py`) 使用 `Query(..., min_length=1)` 确保关键词非空。
#### 3. 错误响应格式
后端统一错误响应格式:`{ "code": <status_code>, "message": <detail> }`
- `http_exception_handler`HTTPException → `{ code, message }`
- `unhandled_exception_handler`:未捕获异常 → `{ code: 500, message: "Internal Server Error" }`
- ❌ 未注册 `RequestValidationError` 处理器 — Pydantic 验证失败时FastAPI 默认返回 422 + `{ "detail": [{ "loc": [...], "msg": "...", "type": "..." }] }` 格式,与统一的 `{ code, message }` 格式不一致
#### 4. 错误提示文案
后端 HTTPException 的 `detail` 均为中文:
- `"申请不存在"` / `"该申请已被处理"` / `"无效的球房编号"` / `"审核操作失败"`
- `"用户不存在"` / `"目标门店不在管辖范围内"` / `"编辑操作失败"`
- `"线索不存在"` / `"编辑操作失败"` / `"删除操作失败"`
- `"请上传有效的 Excel 文件(.xlsx/.xls"` / `"文件内容为空"`
✅ 中文化程度高,风格统一。
### 差距分析
P10 标杆文件(`P10-tenant-admin-web.md`)中没有独立的"表单规范"章节,但在验收标准和设计要点中隐含了以下要求:
- AC4Excel 上传校验(必填、金额精度、表头格式、类型合法)— ✅ 已实现
- 4 种模板的必填列定义 — ✅ 后端已实现校验
- 冲突处理流程 — ✅ 已实现
与通用表单规范最佳实践的差距:
| 维度 | 期望 | 现状 | 差距 |
|------|------|------|------|
| 必填标识 | 所有必填字段有 `required` 规则 + 红色星号 | 大部分有EditModal 缺失 | 🟡 小 |
| 错误提示位置 | 统一在字段下方 | Ant Design Form.Item 默认行为,一致 | ✅ 无 |
| 提示文案 | 中文,格式统一 | `"请 + 动词 + 名词"` 格式统一 | ✅ 无 |
| 验证时机 | 明确约定实时/提交 | 统一提交验证 + Ant Design 默认 onChange 反馈 | ✅ 无 |
| 后端 422 映射 | Pydantic 验证错误映射到前端字段 | 未注册 RequestValidationError 处理器 | 🟡 小 |
| 文档化规范 | 有明确的表单验证规范文档 | 无 | 🟡 小 |
| 前后端验证对齐 | 前端 rules 与后端 schema 一一对应 | 基本对齐,个别字段前端缺 rules | 🟡 小 |
### 建议
1. **EditModal 补充必填规则**(低优先级):`role` 字段建议加 `rules={[{ required: true, message: "请选择角色" }]}``siteId` 视业务需求决定是否必填。
2. **注册 RequestValidationError 处理器**(低优先级):在 `apps/backend/app/main.py` 中添加:
```python
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=422,
content={"code": 422, "message": "请求参数校验失败", "errors": exc.errors()},
)
```
使 Pydantic 验证错误也遵循统一的 `{ code, message }` 响应格式。
3. **文档化表单规范**(可选):如需更高规范性,可在 NS4 或项目级文档中补充一节"表单验证约定",明确:
- 必填字段必须配置 `required: true`Ant Design 自动显示红色星号)
- 错误提示文案格式:`"请 + 动词 + 名词"`
- 验证时机:提交时触发(`validateFields` / `onFinish`),依赖 Ant Design 默认 `onChange` 实时反馈
- 后端验证错误通过 `message.error()` 全局提示,不映射到具体字段
当前项目规模5 个页面、6 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。