包含多个会话的累积代码变更: - 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>
151 lines
8.2 KiB
Markdown
151 lines
8.2 KiB
Markdown
# 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`)中没有独立的"表单规范"章节,但在验收标准和设计要点中隐含了以下要求:
|
||
- AC4:Excel 上传校验(必填、金额精度、表头格式、类型合法)— ✅ 已实现
|
||
- 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 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。
|