Files
Neo 6f8f12314f 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>
2026-04-06 00:03:48 +08:00

8.2 KiB
Raw Permalink Blame History

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 为可选,正确

问题:EditModalrolesiteId 字段没有 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 中手动调用

统一采用提交时验证(onFinishvalidateFields),未使用实时验证(onChange)。风格一致。

注意:所有 Modal 表单均未显式设置 validateTriggerAnt 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_handlerHTTPException → { 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 中添加:

    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: trueAnt Design 自动显示红色星号)
    • 错误提示文案格式:"请 + 动词 + 名词"
    • 验证时机:提交时触发(validateFields / onFinish),依赖 Ant Design 默认 onChange 实时反馈
    • 后端验证错误通过 message.error() 全局提示,不映射到具体字段

    当前项目规模5 个页面、6 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。