包含多个会话的累积代码变更: - 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>
8.2 KiB
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 | 🟡 小 |
建议
-
EditModal 补充必填规则(低优先级):
role字段建议加rules={[{ required: true, message: "请选择角色" }]},siteId视业务需求决定是否必填。 -
注册 RequestValidationError 处理器(低优先级):在
apps/backend/app/main.py中添加: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 }响应格式。 -
文档化表单规范(可选):如需更高规范性,可在 NS4 或项目级文档中补充一节"表单验证约定",明确:
- 必填字段必须配置
required: true(Ant Design 自动显示红色星号) - 错误提示文案格式:
"请 + 动词 + 名词" - 验证时机:提交时触发(
validateFields/onFinish),依赖 Ant Design 默认onChange实时反馈 - 后端验证错误通过
message.error()全局提示,不映射到具体字段
当前项目规模(5 个页面、6 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。
- 必填字段必须配置