Files
Neo-ZQYY/docs/prd/Neo_Specs/review-audit/P10-NS4-07.md
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

151 lines
8.2 KiB
Markdown
Raw Permalink 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.
# 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 个表单)下,现有的隐式一致性已足够,文档化为锦上添花。