ERPTurbo_Admin/.claude/commands/create-page-component.md
shenyifei 65b5d42985 feat(reconciliation): 新增对账记录创建功能
- 添加 ReconciliationRecordCreate 组件实现对账单创建流程
- 更新 DealerAccountRecordList 组件中 dealerAccountRecordId 字段为可选
- 修改 DealerModal 中显示经销商名称格式
- 调整 LeftMenu 样式中的 margin 属性
- 重构 PaymentTask 和 Reconciliation 组件中的渲染逻辑
- 简化 ReconciliationSummary 组件的参数传递方式
- 更新国际化配置文件增加对账相关翻译
- 替换 ReconciliationCreate 页面使用新的创建组件
2026-01-12 18:14:04 +08:00

913 lines
26 KiB
Markdown
Raw 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.

# /create-page-component Command
当使用此命令时,创建一个新的页面组件及其相关的业务组件。
## 目的
在 ERPTurbo_Admin 项目中快速创建一个标准的页面组件,包括:
1. 业务组件(基于 BizContainer
2. 页面组件
3. 国际化翻译
4. 组件导出配置
## 前置条件
- 项目使用 UmiJS Max + Ant Design + TypeScript
- 已有 BizContainer 核心业务组件
- 已有 API 服务层(通过 OpenAPI 自动生成)
## 使用方法
```
/create-page-component <页面名称> <API服务名称>
```
**参数说明:**
- `<页面名称>`: 页面名称,如 `ReceivablesDetail`
- `<API服务名称>`: API 服务名称,如 `dealerAccountRecord`
## 执行步骤
### 1. 分析现有代码结构
首先查找类似页面作为参考:
- 查看目标页面组件(如 `FarmerPayment.tsx`)的实现模式
- 确定业务组件应该放在哪个目录(如 `components/Dealer/`
- 查找 API 服务定义(如 `services/business/dealerAccountRecord.ts`
- 查看 API 类型定义(`services/business/typings.d.ts` 中的 VO 类型)
### 2. 创建业务组件
`packages/app-operation/src/components/<业务目录>/` 下创建 `<ComponentName>List.tsx`
**组件结构模板:**
```tsx
import { BizContainer, BizValueType, ModeType } from '@/components';
import { business } from '@/services';
import { useIntl } from '@@/exports';
import { ProColumns } from '@ant-design/pro-components';
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
import React from 'react';
interface IComponentNameListProps {
ghost?: boolean;
// 筛选参数
xxxId?: BusinessAPI.XXXVO['xxxId'];
search?: boolean;
onValueChange?: () => void;
mode?: ModeType;
trigger?: () => React.ReactNode;
}
export default function ComponentNameList(props: IComponentNameListProps) {
const {
ghost = false,
xxxId,
search = true,
mode = 'page',
trigger,
onValueChange,
} = props;
const intl = useIntl();
const intlPrefix = 'componentName'; // 国际化前缀
// 定义表格列
const columns: ProColumns<BusinessAPI.XXXVO, BizValueType>[] = [
{
title: intl.formatMessage({ id: intlPrefix + '.column.xxx' }),
dataIndex: 'xxx',
key: 'xxx',
valueType: 'text', // text|money|dateTime|select等
},
// 更多列...
];
const detailColumns: ProDescriptionsItemProps<
BusinessAPI.XXXVO,
BizValueType
>[] = columns as ProDescriptionsItemProps<
BusinessAPI.XXXVO,
BizValueType
>[];
return (
<BizContainer<
typeof business.apiService,
BusinessAPI.XXXVO,
BusinessAPI.XXXPageQry
>
rowKey={'id'}
permission={'operation-xxx'}
func={business.apiService}
method={'apiService'}
methodUpper={'ApiService'}
intlPrefix={intlPrefix}
modeType={mode}
onValueChange={onValueChange}
container={{ ghost }}
status={false}
page={{
fieldProps: {
bordered: true,
ghost,
search,
params: {
...(xxxId && { xxxId }),
},
},
columns,
options: () => [],
}}
create={false} // 根据需要启用
update={false}
destroy={false}
detail={{
rowId: xxxId,
formType: 'drawer',
columns: detailColumns,
trigger,
}}
/>
);
}
```
**关键点:**
- 使用 `BizContainer` 作为容器组件
- 定义 `columns` 用于列表展示
- 定义 `detailColumns` 用于详情展示
- 通过 `params` 传递筛选条件
- 根据需求配置 `create/update/destroy`
### 3. 导出业务组件
`packages/app-operation/src/components/<业务目录>/index.ts` 中添加导出:
```typescript
export { default as ComponentNameList } from './ComponentNameList';
```
### 4. 创建页面组件
`packages/app-operation/src/pages/` 下创建 `<PageName>.tsx`
```tsx
import { ComponentNameList } from '@/components';
export default function Page() {
return <ComponentNameList />;
}
```
**关键点:**
- 文件名与路由一致
- 导出为 `Page` 函数
- 直接渲染业务组件
### 5. 添加国际化翻译
`packages/app-operation/src/locales/zh-CN.ts` 中添加翻译:
```typescript
componentName: {
column: {
// 列标题
fieldName: '字段名称',
// 枚举值
'enum.key': '枚举值显示文本',
option: '操作',
},
modal: {
view: {
title: '查看详情',
button: '详情',
},
},
},
```
**关键点:**
- 使用 `intlPrefix` 作为顶级键
- `column` 中包含所有列的翻译
- 枚举值使用 `'enum.key'` 格式
- 添加 `modal` 相关翻译
## 常见 BizContainer 配置
### 如何判断是否有内置 valueType
**快速检查方法:**
查看 `packages/app-operation/src/components/Biz/typing.ts` 中的 `BizValueType` 类型定义(第 18-28 行)。
**内置 valueType 列表:**
```typescript
export type BizValueType =
| 'user' // 用户
| 'customField' // 自定义字段
| 'status' // 状态
| 'remark' // 备注
| 'order' // 采购单 ✅
| 'dealer' // 经销商 ✅
| 'paymentTask' // 付款任务 ✅
| 'company' // 公司 ✅
| 'orderCost' // 订单费用 ✅
| 'supplier'; // 供应商 ✅
```
**判断流程:**
```
1. 检查关联对象类型(如 orderVO、dealerVO、orderShipVO
2. 去掉 "VO" 后缀得到类型名order → order, orderShip → orderShip
3. 在 BizValueType 中查找
4a. 找到 → 使用对应的 valueType
4b. 未找到 → 使用嵌套 dataIndex 数组
```
**示例:**
- `orderVO` → 去掉 "VO" → `order` → 在列表中 ✅ → 使用 `valueType: 'order'`
- `dealerVO` → 去掉 "VO" → `dealer` → 在列表中 ✅ → 使用 `valueType: 'dealer'`
- `orderShipVO` → 去掉 "VO" → `orderShip` → 不在列表 ❌ → 使用 `dataIndex: ['orderShipVO', 'orderSn']`
### valueType 类型
| 类型 | 说明 | dataIndex 格式 | key 格式 | 适用场景 |
|------|------|----------------|----------|----------|
| `text` | 文本 | `fieldName` | `fieldName` | 普通文本字段 |
| `money` | 金额 | `amount` | `amount` | 自动格式化货币 |
| `dateTime` | 日期时间 | `createdAt` | `createdAt` | 格式化时间戳 |
| `select` | 下拉选择 | `type` | `type` | 配合 valueEnum |
| `switch` | 开关 | `status` | `status` | 布尔值显示 |
| `order` | 采购单 | `orderVO` | `orderId` | orderVO 对象 |
| `dealer` | 经销商 | `dealerVO` | `dealerId` | dealerVO 对象 |
| `company` | 公司 | `companyVO` | `companyId` | companyVO 对象 |
| `paymentTask` | 付款任务 | `paymentTaskVO` | `paymentTaskId` | paymentTaskVO 对象 |
| `orderCost` | 订单费用 | `orderCostVO` | `orderCostId` | orderCostVO 对象 |
| `supplier` | 供应商 | `supplierVO` | `supplierId` | supplierVO 对象 |
### 对象关联列配置xxxVO 模式)
项目中大量使用对象关联模式,当 VO 类型中包含嵌套的 `xxxVO` 对象时,优先使用内置 valueType。
#### 通用规则
**有内置 valueType 的情况:**
```tsx
// 1. 检查 BizValueType 是否包含对应类型
// 2. 如果包含,使用 xxxVO + xxxId + xxx 模式
{
dataIndex: 'orderVO', // 完整的 VO 字段名
key: 'orderId', // 对应的主键字段
valueType: 'order', // BizValueType 中定义的类型
}
```
**无内置 valueType 的情况:**
```tsx
// 1. BizValueType 中没有对应类型
// 2. 使用嵌套 dataIndex 数组直接访问子字段
{
dataIndex: ['orderShipVO', 'orderSn'], // 嵌套路径
key: 'orderShipId',
// 不需要 valueType默认为文本
}
```
**示例对照表:**
| VO 字段 | 是否有 valueType | dataIndex | key | valueType | 说明 |
|---------|-----------------|-----------|-----|-----------|------|
| `orderVO?: OrderVO` | ✅ 是 | `orderVO` | `orderId` | `order` | 采购单信息 |
| `dealerVO?: DealerVO` | ✅ 是 | `dealerVO` | `dealerId` | `dealer` | 经销商信息 |
| `companyVO?: CompanyVO` | ✅ 是 | `companyVO` | `companyId` | `company` | 公司信息 |
| `supplierVO?: SupplierVO` | ✅ 是 | `supplierVO` | `supplierId` | `supplier` | 供应商信息 |
| `orderShipVO?: OrderShipVO` | ❌ 否 | `['orderShipVO', 'orderSn']` | `orderShipId` | - | 发货单信息 |
| `paymentTaskVO?: PaymentTaskVO` | ✅ 是 | `paymentTaskVO` | `paymentTaskId` | `paymentTask` | 付款任务信息 |
| `orderCostVO?: OrderCostVO` | ✅ 是 | `orderCostVO` | `orderCostId` | `orderCost` | 订单费用信息 |
#### order 列(采购单关联)
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.order' }),
dataIndex: 'orderVO',
key: 'orderId',
valueType: 'order',
},
```
**要点:**
- BizContainer 内部会自动处理订单链接和展示
- 支持点击跳转到订单详情页
- 自动显示订单编号或关键信息
**参考文件:** `components/Order/OrderSupplierList.tsx:54-59`
#### dealer 列(经销商关联)
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.dealer' }),
dataIndex: 'dealerVO',
key: 'dealerId',
valueType: 'dealer',
hidden: !!dealerId, // 可选:如果已传入 dealerId则隐藏此列
},
```
**要点:**
- 显示经销商名称链接
- 支持点击跳转到经销商详情
- 可根据业务需求使用 `hidden` 属性控制显示
**参考文件:**
- `components/Dealer/DealerPaymentAccountList.tsx:44-48`
- `components/Order/OrderShipList.tsx:41-45`
#### company 列(公司关联)
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.company' }),
dataIndex: 'companyVO',
key: 'companyId',
valueType: 'company',
},
```
**要点:**
- 显示公司名称链接
- 支持点击跳转到公司详情
- 常用于展示入账公司、销售公司等
**参考文件:** `components/Order/OrderShipList.tsx:47-51`
#### 无内置 valueType 的关联列
对于 BizValueType 中没有定义的类型(如 `orderShipVO`),使用嵌套 dataIndex 数组:
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.orderShip' }),
dataIndex: ['orderShipVO', 'orderSn'], // 直接访问嵌套字段
key: 'orderShipId',
// 不需要指定 valueType
},
```
**优点:**
- ✅ 简洁:无需自定义 render 函数
- ✅ 类型安全:利用 ProTable 的嵌套字段访问
- ✅ 自动处理空值ProTable 自动处理 undefined 情况
**更多嵌套示例:**
```tsx
// 访问嵌套对象的单个字段
{ dataIndex: ['orderShipVO', 'orderSn'] } // 发货单编号
{ dataIndex: ['dealerVO', 'shortName'] } // 经销商简称
{ dataIndex: ['companyVO', 'fullName'] } // 公司全称
{ dataIndex: ['orderVO', 'orderVehicle', 'plate'] } // 多层嵌套:订单的车辆车牌
```
**参考文件:** `components/Dealer/DealerAccountRecordList.tsx:73-77`
#### 条件隐藏关联列
当组件已通过 props 传入关联对象的 ID 时,可以隐藏该列避免冗余:
```tsx
// 接收 props
interface IDealerAccountRecordListProps {
dealerId?: BusinessAPI.DealerAccountRecordVO['dealerId'];
orderId?: BusinessAPI.DealerAccountRecordVO['orderId'];
}
// 列配置
{
dataIndex: 'dealerVO',
key: 'dealerId',
valueType: 'dealer',
hidden: !!dealerId, // 如果已传入 dealerId则隐藏此列
},
```
**使用场景:**
- 列表作为详情页的子组件时
- 已知特定经销商/订单的记录列表
- 避免显示冗余的关联信息
#### select 列(枚举类型)
对于有固定选项的枚举字段:
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.targetType' }),
dataIndex: 'targetType',
key: 'targetType',
valueType: 'select',
valueEnum: {
VALUE_1: { text: '选项1', status: 'Processing' },
VALUE_2: { text: '选项2', status: 'Success' },
},
},
```
#### money 列(金额格式化)
金额字段会自动格式化,也可以自定义渲染:
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.amount' }),
dataIndex: 'amount',
key: 'amount',
valueType: 'money',
search: false,
render: (_, record) => (
<span className={record.amount >= 0 ? 'text-green-600' : 'text-red-600'}>
{formatCurrency(record.amount)}
</span>
),
},
```
### 权限配置格式
```
operation-<资源>-<操作>
例如: operation-dealer-account-record-view
```
### 国际化前缀命名规范
- 页面级别: 使用小写+连字符,如 `dealer-account-record`
- 组件级别: 使用驼峰式,如 `dealerAccountRecord`
- 保持与 API 服务名称一致
## 快速参考:关联列配置决策树
```
需要显示关联对象xxxVO
检查 BizValueType 是否包含对应类型
┌─────────────┬─────────────────┐
│ 包含 ✅ │ 不包含 ❌ │
├─────────────┼─────────────────┤
│ 使用 │ 使用嵌套 │
│ valueType │ dataIndex 数组 │
├─────────────┼─────────────────┤
│ 示例: │ 示例: │
│ dataIndex: │ dataIndex: │
│ 'orderVO' │ ['orderShipVO',│
│ valueType: │ 'orderSn'] │
│ 'order' │ │
└─────────────┴─────────────────┘
```
## 常见关联列配置速查表
| 场景 | dataIndex | key | valueType | 备注 |
|------|-----------|-----|-----------|------|
| **有内置 valueType** |
| 采购单 | `orderVO` | `orderId` | `order` | 自动显示订单链接 |
| 经销商 | `dealerVO` | `dealerId` | `dealer` | 自动显示经销商链接 |
| 公司 | `companyVO` | `companyId` | `company` | 自动显示公司链接 |
| 供应商 | `supplierVO` | `supplierId` | `supplier` | 自动显示供应商链接 |
| 付款任务 | `paymentTaskVO` | `paymentTaskId` | `paymentTask` | 自动显示任务链接 |
| 订单费用 | `orderCostVO` | `orderCostId` | `orderCost` | 自动显示费用链接 |
| **无内置 valueType** |
| 发货单编号 | `['orderShipVO', 'orderSn']` | `orderShipId` | - | 直接访问嵌套字段 |
| 经销商简称 | `['dealerVO', 'shortName']` | `dealerId` | - | 直接访问嵌套字段 |
| 车牌号 | `['orderVO', 'orderVehicle', 'plate']` | - | - | 支持多层嵌套 |
## 示例参考
查看以下文件作为参考:
- 业务组件: `components/Dealer/DealerAccountRecordList.tsx`
- 页面组件: `pages/ReceivablesDetail.tsx`
- 组件导出: `components/Dealer/index.ts`
- 国际化: `locales/zh-CN.ts` 中的 `dealerAccountRecord` 部分
### DealerAccountRecordList 组件示例
该组件展示了以下特性:
-**dealer 列**: 展示关联的经销商(第 60-66 行)
- 使用 `valueType: 'dealer'`
- 条件隐藏:当已传入 `dealerId` 时隐藏此列
-**order 列**: 展示关联的采购单(第 67-72 行)
- 使用 `valueType: 'order'`
-**orderShip 列**: 使用嵌套 dataIndex 访问发货单字段(第 73-77 行)
- 使用 `dataIndex: ['orderShipVO', 'orderSn']`
- 无需自定义 render 函数
- 无需 valueTypeBizValueType 中未定义 orderShip
-**枚举类型列**: 变动类型使用 Tag 和颜色区分(第 89-103 行)
-**金额格式化**: 使用 formatCurrency 格式化金额(第 110-144 行)
-**动态样式**: 根据正负值显示不同颜色和图标
-**筛选条件**: 支持 dealerId 和 orderId 筛选(第 182-185 行)
## 验证清单
- [ ] 业务组件已创建在正确的目录
- [ ] 组件已正确导出
- [ ] 页面组件已创建
- [ ] 国际化翻译已添加
- [ ] API 服务和类型定义已确认存在
- [ ] 权限配置正确
- [ ] 组件 props 支持所需的筛选条件
## 故障排除
### 问题: 组件找不到
**解决**: 检查 `components/index.ts` 是否正确导出了业务组件目录
### 问题: 翻译未生效
**解决**: 检查 `intlPrefix` 是否与国际化文件中的键一致
### 问题: API 类型错误
**解决**:
1. 确认 API 服务文件存在于 `services/business/`
2. 检查类型定义是否存在于 `typings.d.ts`
3. 确保 `business.apiService` 导入正确
### 问题: 权限报错
**解决**: 确认 `permission` 属性格式为 `operation-<resource>-<action>`
### 问题: order 列不显示或报错
**原因分析**:
1. TypeScript 类型定义中可能没有 `orderVO` 字段
2. API 返回数据时实际包含 `orderVO` 字段(后端动态扩展)
**解决方案**:
- 方案1: 如果类型定义中缺少 `orderVO`,可以在组件中扩展类型:
```tsx
// 在组件顶部添加类型扩展
declare module '@/services/business' {
namespace BusinessAPI {
interface DealerAccountRecordVO {
orderVO?: OrderVO;
}
}
}
```
- 方案2: 使用类型断言(不推荐,但可用):
```tsx
const columns: ProColumns<any, BizValueType>[] = [
// ...
];
```
- 方案3: 联系后端更新 OpenAPI 规范,确保 `orderVO` 字段在类型定义中
### 问题: dealer 列不显示经销商名称
**原因**: BizContainer 内部会自动处理,但需要确保 `dealerVO` 字段存在
**解决**:
1. 确认 API 返回数据包含 `dealerVO` 对象
2. 检查 `dataIndex` 使用的是 `dealerVO` 而不是 `dealerId`
3. 检查 `valueType` 是否设置为 `dealer`
### 问题: 自定义关联列(如 orderShip显示为 [object Object]
**原因**: 直接渲染对象而没有提取具体字段
**解决**:
```tsx
// ❌ 错误写法 - 直接渲染整个对象
{
dataIndex: 'orderShipVO',
render: (_, record) => <span>{record.orderShipVO}</span>,
}
// ❌ 错误写法 - 使用自定义 render 访问字段
{
dataIndex: 'orderShipVO',
render: (_, record) =>
record.orderShipVO ? (
<span>{record.orderShipVO.orderSn}</span>
) : null,
}
// ✅ 正确写法 - 使用嵌套 dataIndex 数组
{
dataIndex: ['orderShipVO', 'orderSn'], // 直接访问嵌套字段
key: 'orderShipId',
// 不需要 renderProTable 自动处理
}
```
**优势对比:**
- ❌ render 方式:代码冗长,需要手动处理空值
- ✅ 嵌套 dataIndex简洁清晰ProTable 自动处理所有情况
### 问题: 关联列在嵌套组件中重复显示
**原因**: 当列表作为详情页的子组件时,父组件已通过 props 传入 ID
**解决**: 使用 `hidden` 属性条件隐藏
```tsx
{
dataIndex: 'dealerVO',
key: 'dealerId',
valueType: 'dealer',
hidden: !!dealerId, // 如果已传入 dealerId则隐藏此列
},
```
### 问题: order 列点击后不跳转
**解决**:
1. 确认 `dataIndex` 使用的是 `orderVO` 而不是 `orderId`
2. 确认 `key` 使用的是 `orderId`
3. 检查 BizContainer 配置中 `detail` 是否正确启用
## 高级配置Toolbar 和状态管理
### Toolbar Tab 配置
对于需要按状态筛选的列表页面,可以添加 Tab 类型的 toolbar
**完整示例(参考 PaymentTaskList**
```tsx
import React, { useState } from 'react';
export default function ComponentNameList(props: IComponentNameListProps) {
const intl = useIntl();
const intlPrefix = 'componentName';
// 1. 添加 activeKey 状态管理
const [activeKey, setActiveKey] = useState<string>('PENDING');
return (
<BizContainer
// ... 其他配置
page={{
fieldProps: {
// 2. 将 activeKey 传递给 params 作为筛选条件
params: {
...(activeKey !== 'ALL' && {
state: activeKey as BusinessAPI.XXXVO['state'],
}),
},
// 3. 配置 toolbar
toolbar: {
menu: {
type: 'tab',
activeKey: activeKey,
items: [
{
key: 'ALL',
label: intl.formatMessage({
id: intlPrefix + '.tab.all',
}),
},
// ... 其他状态 tab
],
onChange: (key) => {
setActiveKey(key as string);
},
},
// 4. 可选:添加操作按钮
actions: [
<ComponentCreate
key={'create'}
onFinish={() => {
actionRef.current?.reload();
onValueChange?.();
}}
/>,
],
},
},
columns,
options: () => [],
}}
/>
);
}
```
**关键点:**
1. **useState**: 使用 `useState` 管理当前选中的 tab
2. **params 筛选**: 当 `activeKey !== 'ALL'` 时,将状态传递给 API 查询参数
3. **toolbar.menu.type**: 设置为 `'tab'` 启用 tab 菜单
4. **onChange**: tab 切换时更新 `activeKey`
5. **actions**: 可选,在 toolbar 右侧添加操作按钮
**参考文件:** `components/PaymentTask/PaymentTaskList.tsx:538-587`
### 状态枚举映射stateMap
为状态字段创建枚举映射,统一管理状态显示文本和颜色:
**定义模式:**
```tsx
// 状态枚举映射
const stateMap: Record<string, { label: string; color: string }> = {
PENDING: {
label: intl.formatMessage({
id: intlPrefix + '.state.pending',
}),
color: 'default', // default | processing | success | warning | error
},
COMPLETED: {
label: intl.formatMessage({
id: intlPrefix + '.state.completed',
}),
color: 'success',
},
CANCELLED: {
label: intl.formatMessage({
id: intlPrefix + '.state.cancelled',
}),
color: 'error',
},
};
```
**在列配置中使用 stateMap**
```tsx
{
title: intl.formatMessage({ id: intlPrefix + '.column.state' }),
dataIndex: 'state',
key: 'state',
valueType: 'select',
valueEnum: Object.entries(stateMap).reduce(
(acc, [key, value]) => ({
...acc,
[key]: { text: value.label, status: value.color as any },
}),
{},
),
search: false, // 状态通常通过 tab 筛选,不需要在搜索表单中显示
render: (_, record) => {
const stateInfo = stateMap[record.state as string];
return stateInfo ? (
<span style={{ color: stateInfo.color === 'default' ? undefined : stateInfo.color }}>
{stateInfo.label}
</span>
) : (
<span>{record.state}</span>
);
},
},
```
**颜色映射规则:**
| 状态类型 | color 值 | ProTable status | 适用场景 |
|---------|----------|----------------|----------|
| 默认/待处理 | `default` | `Default` | 待处理、初始状态 |
| 进行中 | `processing` | `Processing` | 处理中、部分完成 |
| 成功/完成 | `success` | `Success` | 已完成、已通过 |
| 警告/注意 | `warning` | `Warning` | 部分完成、需注意 |
| 错误/取消 | `error` | `Error` | 已取消、已拒绝 |
**国际化翻译配置:**
```typescript
componentName: {
state: {
pending: '待处理',
completed: '已完成',
cancelled: '已取消',
},
tab: {
all: '全部',
pending: '待处理',
completed: '已完成',
cancelled: '已取消',
},
},
```
**最佳实践:**
1. **stateMap 和 tab items 保持一致**: 状态枚举值应与 tab 的 key 完全匹配
2. **使用国际化的 key**: stateMap 中的 key 使用后端枚举值(如 `PENDING`label 使用国际化
3. **颜色语义化**: 根据状态含义选择合适的颜色
4. **添加 ALL tab**: 除了具体状态外,通常需要"全部"选项用于显示所有数据
**参考文件:** `components/Reconciliation/ReconciliationRecordList.tsx:32-72`
### 完整示例:带 Tab 筛选的列表组件
```tsx
import { BizContainer, BizValueType, ModeType } from '@/components';
import { business } from '@/services';
import { useIntl } from '@@/exports';
import { ProColumns } from '@ant-design/pro-components';
import React, { useState } from 'react';
export default function ReconciliationRecordList(
props: IReconciliationRecordListProps,
) {
const { ghost = false, search = true, mode = 'page' } = props;
const intl = useIntl();
const intlPrefix = 'reconciliationRecord';
const [activeKey, setActiveKey] = useState<string>('PENDING');
// 状态枚举映射
const stateMap: Record<string, { label: string; color: string }> = {
PENDING: {
label: intl.formatMessage({ id: intlPrefix + '.state.pending' }),
color: 'default',
},
RECONCILED: {
label: intl.formatMessage({ id: intlPrefix + '.state.reconciled' }),
color: 'processing',
},
PARTIAL_INVOICE: {
label: intl.formatMessage({ id: intlPrefix + '.state.partialInvoice' }),
color: 'warning',
},
INVOICED: {
label: intl.formatMessage({ id: intlPrefix + '.state.invoiced' }),
color: 'success',
},
PARTIAL_PAYMENT: {
label: intl.formatMessage({ id: intlPrefix + '.state.partialPayment' }),
color: 'warning',
},
PAID: {
label: intl.formatMessage({ id: intlPrefix + '.state.paid' }),
color: 'success',
},
};
const columns: ProColumns<BusinessAPI.ReconciliationVO, BizValueType>[] = [
// ... 列配置
{
title: intl.formatMessage({ id: intlPrefix + '.column.state' }),
dataIndex: 'state',
key: 'state',
valueType: 'select',
valueEnum: Object.entries(stateMap).reduce(
(acc, [key, value]) => ({
...acc,
[key]: { text: value.label, status: value.color as any },
}),
{},
),
search: false,
render: (_, record) => {
const stateInfo = stateMap[record.state as string];
return stateInfo ? (
<span className={`text-${stateInfo.color}-600`}>
{stateInfo.label}
</span>
) : (
<span>{record.state}</span>
);
},
},
];
return (
<BizContainer>
page={{
fieldProps: {
params: {
...(activeKey !== 'ALL' && {
state: activeKey as BusinessAPI.ReconciliationVO['state'],
}),
},
toolbar: {
menu: {
type: 'tab',
activeKey: activeKey,
items: [
{
key: 'ALL',
label: intl.formatMessage({ id: intlPrefix + '.tab.all' }),
},
{
key: 'PENDING',
label: intl.formatMessage({ id: intlPrefix + '.tab.pending' }),
},
{
key: 'RECONCILED',
label: intl.formatMessage({ id: intlPrefix + '.tab.reconciled' }),
},
// ... 其他状态 tab
],
onChange: (key) => {
setActiveKey(key as string);
},
},
},
},
columns,
options: () => [],
}}
/>
);
}
```
**参考文件:**
- `components/PaymentTask/PaymentTaskList.tsx` - 完整的 Tab 筛选示例
- `components/Reconciliation/ReconciliationRecordList.tsx` - 对账记录 Tab 筛选