# P10-NS4-06:表格组件的统一规范
## 简要结论
- 状态:⚠️ 部分解决
- tenant-admin 各页面表格在分页大小、loading 状态、筛选器位置上已形成事实一致的模式,但缺少显式的统一配置抽象层;排序交互完全缺失;空数据展示未统一;部分表格分页大小不一致(20 vs 10)。
## 详细审查
### 前端代码
#### 1. 分页大小
| 页面/组件 | 默认 pageSize | showSizeChanger | showTotal | 后端分页 |
|-----------|:---:|:---:|:---:|:---:|
| UserApproval | 20 | ✅ | ✅ `共 N 条` | ✅ page/page_size |
| UserManagement | 20 | ✅ | ✅ `共 N 条` | ✅ page/page_size |
| ExcelUpload — 上传记录 | 20 | ✅ | ✅ `共 N 条` | ✅ page/page_size |
| ExcelUpload — 校验详情 | **10** | ❌ | ❌ | 前端分页 |
| RetentionClues — 客户搜索 | `false` | — | — | 无分页(LIMIT 50) |
| RetentionClues — 线索列表 | **10** | ❌ | ❌ | 无分页(全量返回) |
| DiffTable — 冲突表 | `false` | — | — | 前端数据 |
结论:主列表页统一为 20 条/页 + showSizeChanger + showTotal,但子表格(校验详情、线索列表)使用 10 条/页且缺少 showSizeChanger 和 showTotal,**不一致**。
#### 2. 排序交互
所有 5 个表格组件均未配置 `sorter` 属性,前端不支持列排序。后端 SQL 统一使用 `ORDER BY created_at DESC`(固定倒序),无动态排序参数。
结论:**完全缺失**。用户无法按任意列排序。
#### 3. 筛选器位置
| 页面 | 筛选器位置 | 筛选方式 |
|------|-----------|---------|
| UserApproval | 表格上方独立区域 | Select(状态筛选) |
| UserManagement | 表格上方独立区域 | Select(角色)+ Input.Search(关键词) |
| ExcelUpload — 上传记录 | 无筛选 | — |
| RetentionClues | Card title 区域 + Card extra 区域 | Input.Search + SiteSelector(上方);Select×2(Card extra 右侧) |
结论:筛选器统一放在表格上方(非表格内 column filter),**位置一致**。但 RetentionClues 的线索筛选器放在 Card extra 右侧,与其他页面的左对齐布局略有差异。
#### 4. loading 状态
所有带后端请求的表格均通过 `loading={loading}` 传入 Ant Design Table 的 loading 属性,**统一且正确**。
#### 5. 空数据展示
| 页面 | 空数据处理 |
|------|-----------|
| UserApproval | Ant Design Table 默认空状态(无自定义) |
| UserManagement | Ant Design Table 默认空状态(无自定义) |
| ExcelUpload | Ant Design Table 默认空状态(无自定义) |
| RetentionClues — 客户搜索 | `` ✅ 自定义 |
| RetentionClues — 线索列表 | Ant Design Table 默认空状态(无自定义) |
| DiffTable | Ant Design Table 默认空状态(无自定义) |
结论:仅 RetentionClues 客户搜索有自定义空状态文案,其余均依赖 Ant Design 默认的英文 "No Data"。**未统一**,且未做中文化。
对比 admin-web:`TaskManager` 页面使用了 `locale={{ emptyText: }}` 自定义空状态,tenant-admin 未跟进。
#### 6. 统一配置抽象
- tenant-admin 中**不存在**统一的表格配置文件、常量定义或封装组件
- 每个页面独立定义 `pageSize`、`pagination` 配置、`handleTableChange` 等
- 分页响应结构 `{ items, total, page, pageSize }` 在前后端已统一,但属于隐式约定
### 后端代码
#### 分页参数统一性
| 路由 | 参数名 | 默认值 | 范围约束 |
|------|--------|--------|---------|
| `GET /applications` | `page` + `page_size` | 1 / 20 | ge=1 / ge=1,le=100 |
| `GET /users` | `page` + `page_size` | 1 / 20 | ge=1 / ge=1,le=100 |
| `GET /excel/logs` | `page` + `page_size` | 1 / 20 | ge=1 / ge=1,le=100 |
| `GET /customers/search` | 无分页 | — | LIMIT 50 硬编码 |
| `GET /customers/{id}/clues` | 无分页 | — | 全量返回 |
结论:三个主列表接口的分页参数**完全统一**(`page`/`page_size`,默认 20,上限 100)。客户搜索和线索列表不分页,属于业务设计选择(数据量可控),但线索列表在数据量增长后可能需要补充分页。
响应格式统一为 `{ items: T[], total: int, page: int, pageSize: int }`,**一致**。
### 差距分析
与 P10 标杆文件要求的差距:
| 规范项 | P10 要求(推断) | 当前状态 | 差距 |
|--------|-----------------|---------|------|
| 默认分页大小 | 统一值(如 20) | 主表 20,子表 10 | 🟡 子表不一致 |
| showSizeChanger | 所有分页表格启用 | 仅主表启用 | 🟡 子表缺失 |
| showTotal | 所有分页表格显示 | 仅主表显示 | 🟡 子表缺失 |
| 排序交互 | 至少关键列支持排序 | 完全缺失 | 🔴 缺失 |
| 筛选器位置 | 统一在表格上方 | 基本一致 | ✅ |
| loading 状态 | 统一 loading 属性 | 全部已配置 | ✅ |
| 空数据展示 | 统一中文空状态 | 仅 1 处自定义,其余默认 | 🟡 未统一 |
| 统一配置抽象 | 提取公共 Table 配置 | 不存在 | 🔴 缺失 |
### 建议
1. **提取统一表格配置常量**(优先级:高)
在 `apps/tenant-admin/src/constants/table.ts` 中定义:
```ts
export const DEFAULT_PAGE_SIZE = 20;
export const PAGE_SIZE_OPTIONS = ['10', '20', '50'];
export const TABLE_LOCALE = {
emptyText: '暂无数据',
};
export const defaultPagination = (total: number, page: number, pageSize: number) => ({
current: page,
pageSize,
total,
showSizeChanger: true,
showTotal: (t: number) => `共 ${t} 条`,
pageSizeOptions: PAGE_SIZE_OPTIONS,
});
```
2. **统一空数据展示**(优先级:高)
通过 Ant Design ConfigProvider 全局设置中文 locale 和空状态:
```tsx
import zhCN from 'antd/locale/zh_CN';
}>
```
3. **子表格分页对齐**(优先级:中)
ExcelUpload 校验详情和 RetentionClues 线索列表的 pageSize 改为 20,并补充 showSizeChanger 和 showTotal。
4. **排序交互**(优先级:低)
对时间列(申请时间、上传时间、记录时间)添加前端 `sorter` 支持。如数据量增长需后端排序,可在 API 中增加 `sort_by` + `sort_order` 参数。当前数据量下前端排序即可。
5. **线索列表补充分页**(优先级:低)
`GET /customers/{member_id}/clues` 当前全量返回,建议在数据量可能超过 50 条时补充后端分页。