- 添加 ReconciliationRecordCreate 组件实现对账单创建流程 - 更新 DealerAccountRecordList 组件中 dealerAccountRecordId 字段为可选 - 修改 DealerModal 中显示经销商名称格式 - 调整 LeftMenu 样式中的 margin 属性 - 重构 PaymentTask 和 Reconciliation 组件中的渲染逻辑 - 简化 ReconciliationSummary 组件的参数传递方式 - 更新国际化配置文件增加对账相关翻译 - 替换 ReconciliationCreate 页面使用新的创建组件
913 lines
26 KiB
Markdown
913 lines
26 KiB
Markdown
# /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 函数
|
||
- 无需 valueType(BizValueType 中未定义 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',
|
||
// 不需要 render,ProTable 自动处理
|
||
}
|
||
```
|
||
|
||
**优势对比:**
|
||
- ❌ 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 筛选
|