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

26 KiB
Raw Permalink Blame History

/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

组件结构模板:

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 中添加导出:

export { default as ComponentNameList } from './ComponentNameList';

4. 创建页面组件

packages/app-operation/src/pages/ 下创建 <PageName>.tsx

import { ComponentNameList } from '@/components';

export default function Page() {
	return <ComponentNameList />;
}

关键点:

  • 文件名与路由一致
  • 导出为 Page 函数
  • 直接渲染业务组件

5. 添加国际化翻译

packages/app-operation/src/locales/zh-CN.ts 中添加翻译:

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 列表:

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 的情况:

// 1. 检查 BizValueType 是否包含对应类型
// 2. 如果包含,使用 xxxVO + xxxId + xxx 模式

{
	dataIndex: 'orderVO',      // 完整的 VO 字段名
	key: 'orderId',            // 对应的主键字段
	valueType: 'order',        // BizValueType 中定义的类型
}

无内置 valueType 的情况:

// 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 列(采购单关联)

{
	title: intl.formatMessage({ id: intlPrefix + '.column.order' }),
	dataIndex: 'orderVO',
	key: 'orderId',
	valueType: 'order',
},

要点:

  • BizContainer 内部会自动处理订单链接和展示
  • 支持点击跳转到订单详情页
  • 自动显示订单编号或关键信息

参考文件: components/Order/OrderSupplierList.tsx:54-59

dealer 列(经销商关联)

{
	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 列(公司关联)

{
	title: intl.formatMessage({ id: intlPrefix + '.column.company' }),
	dataIndex: 'companyVO',
	key: 'companyId',
	valueType: 'company',
},

要点:

  • 显示公司名称链接
  • 支持点击跳转到公司详情
  • 常用于展示入账公司、销售公司等

参考文件: components/Order/OrderShipList.tsx:47-51

无内置 valueType 的关联列

对于 BizValueType 中没有定义的类型(如 orderShipVO),使用嵌套 dataIndex 数组:

{
	title: intl.formatMessage({ id: intlPrefix + '.column.orderShip' }),
	dataIndex: ['orderShipVO', 'orderSn'],  // 直接访问嵌套字段
	key: 'orderShipId',
	// 不需要指定 valueType
},

优点:

  • 简洁:无需自定义 render 函数
  • 类型安全:利用 ProTable 的嵌套字段访问
  • 自动处理空值ProTable 自动处理 undefined 情况

更多嵌套示例:

// 访问嵌套对象的单个字段
{ dataIndex: ['orderShipVO', 'orderSn'] }           // 发货单编号
{ dataIndex: ['dealerVO', 'shortName'] }             // 经销商简称
{ dataIndex: ['companyVO', 'fullName'] }             // 公司全称
{ dataIndex: ['orderVO', 'orderVehicle', 'plate'] }  // 多层嵌套:订单的车辆车牌

参考文件: components/Dealer/DealerAccountRecordList.tsx:73-77

条件隐藏关联列

当组件已通过 props 传入关联对象的 ID 时,可以隐藏该列避免冗余:

// 接收 props
interface IDealerAccountRecordListProps {
	dealerId?: BusinessAPI.DealerAccountRecordVO['dealerId'];
	orderId?: BusinessAPI.DealerAccountRecordVO['orderId'];
}

// 列配置
{
	dataIndex: 'dealerVO',
	key: 'dealerId',
	valueType: 'dealer',
	hidden: !!dealerId, // 如果已传入 dealerId则隐藏此列
},

使用场景:

  • 列表作为详情页的子组件时
  • 已知特定经销商/订单的记录列表
  • 避免显示冗余的关联信息

select 列(枚举类型)

对于有固定选项的枚举字段:

{
	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 列(金额格式化)

金额字段会自动格式化,也可以自定义渲染:

{
	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,可以在组件中扩展类型:

    // 在组件顶部添加类型扩展
    declare module '@/services/business' {
      namespace BusinessAPI {
        interface DealerAccountRecordVO {
          orderVO?: OrderVO;
        }
      }
    }
    
  • 方案2: 使用类型断言(不推荐,但可用):

    const columns: ProColumns<any, BizValueType>[] = [
      // ...
    ];
    
  • 方案3: 联系后端更新 OpenAPI 规范,确保 orderVO 字段在类型定义中

问题: dealer 列不显示经销商名称

原因: BizContainer 内部会自动处理,但需要确保 dealerVO 字段存在

解决:

  1. 确认 API 返回数据包含 dealerVO 对象
  2. 检查 dataIndex 使用的是 dealerVO 而不是 dealerId
  3. 检查 valueType 是否设置为 dealer

问题: 自定义关联列(如 orderShip显示为 [object Object]

原因: 直接渲染对象而没有提取具体字段

解决:

// ❌ 错误写法 - 直接渲染整个对象
{
	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 属性条件隐藏

{
	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

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

为状态字段创建枚举映射,统一管理状态显示文本和颜色:

定义模式:

// 状态枚举映射
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

{
	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 已取消、已拒绝

国际化翻译配置:

componentName: {
	state: {
		pending: '待处理',
		completed: '已完成',
		cancelled: '已取消',
	},
	tab: {
		all: '全部',
		pending: '待处理',
		completed: '已完成',
		cancelled: '已取消',
	},
},

最佳实践:

  1. stateMap 和 tab items 保持一致: 状态枚举值应与 tab 的 key 完全匹配
  2. 使用国际化的 key: stateMap 中的 key 使用后端枚举值(如 PENDINGlabel 使用国际化
  3. 颜色语义化: 根据状态含义选择合适的颜色
  4. 添加 ALL tab: 除了具体状态外,通常需要"全部"选项用于显示所有数据

参考文件: components/Reconciliation/ReconciliationRecordList.tsx:32-72

完整示例:带 Tab 筛选的列表组件

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 筛选