---
title: 开发文档
order: 3
category: plus
---

# OrderList 订单列表 - 开发文档

## 技术架构

### 技术栈

- **React**: 18.x
- **TypeScript**: 5.x
- **Ant Design**: 5.x
- **dayjs**: 时间处理库
- **@pisell/materials**: 基础物料库
- **@pisell/utils**: 工具库

### 组件依赖关系

```
OrderList (主组件)
├── PisellDataSourceContainer (数据源容器)
│   ├── useContainerContext (Context Hook)
│   ├── Table (表格子组件)
│   └── Pagination (分页子组件)
├── PisellGridPro (网格布局)
│   ├── Header (标题区域)
│   ├── ToolBar (工具栏区域)
│   ├── GridView (内容区域)
│   └── Footer (底部区域)
├── PisellToolBar (工具栏)
├── QuickFilter (快速筛选)
│   └── PisellQuickFilter
├── FilterList (快捷筛选按钮组)
├── HandleActions (操作按钮组)
│   ├── ColumnsSetting (列设置)
│   ├── PisellSort (排序)
│   └── PisellFilter (筛选)
└── Reset (重置按钮)
```

## 目录结构

```
orderList/
├── index.tsx                 # 主组件入口
├── index.less                # 样式文件
├── config.tsx                # 配置文件（表格列、筛选、排序配置）
├── serve.ts                  # API 接口
├── locales.ts                # 多语言配置
├── components/               # 子组件目录
│   ├── QuickFilter.tsx       # 快速筛选组件
│   ├── FilterList.tsx        # 快捷筛选按钮组
│   ├── HandleActions.tsx     # 操作按钮组
│   └── Reset.tsx             # 重置按钮
└── docs/                     # 文档目录
    ├── orderList.md          # 使用文档
    ├── orderList.$tab-design.md    # 设计文档
    ├── orderList.$tab-dev.md       # 开发文档
    └── orderList.$tab-test.md      # 测试文档
```

## 核心实现

### 1. 主组件 (index.tsx)

```
interface OrderListProps {
  business_type: string;
}

const OrderList = (props: OrderListProps) => {
  const { business_type } = props;
  const context = useEngineContext();

  // 初始化多语言
  locales.init(localeTexts, context?.engine?.props?.locale || 'zh-CN');

  // 获取表格列配置
  const tableColumns = useMemo(() => getTableColumns(locales), [locales]);

  // 设置请求工具
  const utils = context?.appHelper?.utils || {};
  request.setRequest(utils?.request);

  return (
    <Page>
      <PisellDataSourceContainer
        dataSource={{ key: 'orderList' }}
        actions={{ list: listApi }}
        pagination={true}
        extraParams={{ list: { business_type } }}
      >
        {/* ... */}
      </PisellDataSourceContainer>
    </Page>
  );
};
```

**关键点**:

- 使用 `useEngineContext` 获取应用上下文
- 通过 `locales.init` 初始化多语言
- 使用 `useMemo` 缓存表格列配置，避免重复计算
- 通过 `extraParams` 传递业务类型参数

### 2. 数据接口 (serve.ts)

#### 核心接口

```
/**
 * 订单列表接口
 */
export const listApi = async (params: Record<string, any>) => {
  const res = await request
    .getRequest()
    .post(`/shop/order/v2/list`, formatParams(params), {
      abort: true,
      fullResult: true
    });
  return res?.data;
};
```

#### 参数格式化

```
const formatParams = (params: Record<string, any>) => {
  const { pageNumber, pageSize, orderDate, total_amount, ...rest } = params;

  return {
    ...rest,
    skip: pageNumber || 1,
    num: pageSize || 10,
    // 时间范围处理
    ...(orderDate?.[0] && {
      start_time: dayjs(orderDate[0]).format('YYYY-MM-DD')
    }),
    ...(orderDate?.[1] && {
      end_time: dayjs(orderDate[1]).format('YYYY-MM-DD')
    }),
    // 金额范围处理
    ...(total_amount?.min !== undefined && {
      min_total_amount: total_amount.min
    }),
    ...(total_amount?.max !== undefined && {
      max_total_amount: total_amount.max
    }),
    // 关联数据
    with: [
      'customer',
      'contactsInfo',
      'tag',
      'createAccount',
      'paidPayment',
      'lastEditAccount'
    ],
  };
};
```

**关键点**:

- 将前端分页参数 `pageNumber/pageSize` 转换为后端参数 `skip/num`
- 将时间范围数组转换为 `start_time/end_time`
- 将金额范围对象转换为 `min_total_amount/max_total_amount`
- 通过 `with` 参数指定需要关联查询的数据

#### 辅助接口

```
// 客户下拉
export const getCustomerSelectOptions = async (params?: string) => {
  const res = await request
    .getRequest()
    .get(`/shop/customer`, { search: params, skip: 1, num: 100 }, {
      abort: true,
      fullResult: true
    });
  return res?.data?.list || [];
};

// 订单标签下拉
export const getTagSelectOptions = async (params?: any) => {
  const res = await request
    .getRequest()
    .get(`/shop/order/tag`, { name: params, skip: 1, num: 100 }, {
      abort: true,
      fullResult: true
    });
  return res?.data?.list || [];
};

// 销售渠道下拉
export const getChannelSelectOptions = async (params?: any) => {
  const res = await request
    .getRequest()
    .get(`/shop/product/channel`, {}, {
      abort: true,
      fullResult: true
    });
  return res?.data;
};

// 支付方式下拉
export const getPaymentSelectOptions = async (params?: any) => {
  const res = await request
    .getRequest()
    .get(`/shop/pay/custom-payment/all`, {}, {
      abort: true,
      fullResult: true
    });
  return res?.data;
};

// 物流公司下拉
export const getLogisticsSelectOptions = async (params?: any) => {
  const res = await request
    .getRequest()
    .get(`/shop/order/logistics-company`, {}, {
      abort: true,
      fullResult: true
    });
  return res?.data;
};

// 配送点下拉
export const getLocationSelectOptions = async (params?: any) => {
  const res = await request
    .getRequest()
    .get(`/shop/shop/location`, {}, {
      abort: true,
      fullResult: true
    });
  return res?.data;
};
```

### 3. 配置文件 (config.tsx)

#### 表格列配置

```
export const getTableColumns = (locales: any) => [
  {
    title: locales.getText('pisell2.orderList.column.orderNumber'),
    dataIndex: 'shop_order_number',
    key: 'shop_order_number',
    render: (value: any, record: any) => {
      const { note } = record || {};
      return (
        <div className="order-id-cell">
          <span className="order-id-link">{value}</span>
          {note && (
            <Tooltip title={<div style={{ whiteSpace: 'pre-wrap' }}>{note}</div>}>
              <span className="order-id-link-badge cursor-pointer">
                {locales.getText('pisell2.orderList.common.note')}
              </span>
            </Tooltip>
          )}
        </div>
      );
    },
  },
  // ... 其他列配置
];
```

**关键点**:

- 所有列标题使用多语言配置
- 通过 `render` 函数自定义列渲染
- 复杂字段使用 `Tooltip` 展示更多信息
- 使用 `isShow: false` 控制列的默认隐藏

#### 支付信息列的特殊处理

```
{
  title: locales.getText('pisell2.orderList.column.paymentInfo'),
  dataIndex: 'payment_status',
  key: 'payment_status',
  render: (value: any, record: any) => {
    const PAYMENT_STATUS_MAP = getPaymentStatusMap(locales);

    const total_amount = Number(record.total_amount || 0);
    const paid_amount = Number(record.paid_amount || 0);
    const total_refund_amount = Number(record.total_refund_amount || 0);
    const unpaid = Number(total_amount - paid_amount).toFixed(2);

    // 根据支付状态生成不同的 tooltip 内容
    let tooltipContent = '';

    if (['payment_pending', 'payment_processing', 'unpaid'].includes(value)) {
      tooltipContent = `${locales.getText('pisell2.orderList.common.totalAmount')}: $${total_amount.toFixed(2)}\n${locales.getText('pisell2.orderList.common.unpaidAmount')}: $${unpaid}`;
    } else if (value === 'partially_paid') {
      tooltipContent = `${locales.getText('pisell2.orderList.common.totalAmount')}: $${total_amount.toFixed(2)}\n${locales.getText('pisell2.orderList.common.paidAmount')}: $${paid_amount.toFixed(2)}\n${locales.getText('pisell2.orderList.common.dueAmount')}: $${unpaid}`;
    }
    // ... 其他状态处理

    const color = PAYMENT_STATUS_COLOR_MAP[value] || '';

    return (
      <Tooltip title={<div style={{ whiteSpace: 'pre-wrap' }}>{tooltipContent}</div>}>
        <PisellTags
          className="cursor-pointer"
          style={{ backgroundColor: color, whiteSpace: 'nowrap' }}
        >
          {renderMapValue(value, PAYMENT_STATUS_MAP)}
        </PisellTags>
      </Tooltip>
    );
  },
}
```

#### 快速筛选配置

```
export const getQuickFilterList = (locales: any) => {
  return [
    {
      name: 'keyword',
      type: 'search',
      key: 'keyword',
      other: {
        placeholder: locales.getText('pisell2.orderList.quickFilter.keyword.placeholder'),
        trigger: ['onChange'],
      },
    },
    {
      name: 'orderDate',
      type: 'rangePicker',
      key: 'orderDate',
      label: locales.getText('pisell2.orderList.quickFilter.orderDate.label'),
      other: {
        placeholder: locales.getText('pisell2.orderList.quickFilter.orderDate.placeholder'),
        style: { width: '800px' },
      },
    },
  ];
};
```

#### 详细筛选配置

```
export const getFilterButtonList = (locales: any) => {
  return [
    {
      name: 'status',
      type: 'select',
      key: 'status',
      label: locales.getText('pisell2.orderList.filter.orderStatus.label'),
      other: {
        formItemProps: { labelCol: { span: 24 } },
        mode: 'multiple',
        placeholder: locales.getText('pisell2.orderList.filter.orderStatus.placeholder'),
        options: Object.entries(getOrderStatusMap(locales)).map(([key, value]) => ({
          label: value,
          value: key,
        })),
      },
    },
    // 支持服务端搜索的下拉框
    {
      name: 'customer_id',
      type: 'select',
      key: 'customer_id',
      label: locales.getText('pisell2.orderList.filter.customer.label'),
      other: {
        formItemProps: { labelCol: { span: 24 } },
        isSearchServer: true,
        showSearch: true,
        placeholder: locales.getText('pisell2.orderList.filter.customer.placeholder'),
        options: (params?: string) =>
          getCustomerSelectOptions(params).then((res: any) =>
            res.map((item: any) => ({
              label: item.display_name,
              value: item.id,
            }))
          ),
      },
    },
    // ... 其他筛选项
  ];
};
```

**关键点**:

- `isSearchServer: true` 表示支持服务端搜索
- `options` 可以是数组或返回 Promise 的函数
- 使用 `mode: 'multiple'` 支持多选

#### 排序配置

```
export const getSortList = (locales: any) => {
  return [
    {
      type: 'string',
      isCustom: false,
      name: 'shop_full_order_number',
      label: locales.getText('pisell2.orderList.sort.orderNumber'),
    },
    {
      type: 'time',
      isCustom: false,
      name: 'created_at',
      label: locales.getText('pisell2.orderList.sort.createdAt'),
    },
    // ... 其他排序项
  ];
};
```

### 4. 子组件实现

#### QuickFilter 组件

```
const QuickFilter = (props: { locales: any }) => {
  const { locales } = props;
  const { serverActions, refManager, ...otherProps } = useContainerContext();
  const quickFilterList = useMemo(() => getQuickFilterList(locales), [locales]);

  return <PisellQuickFilter {...otherProps} filterList={quickFilterList} />;
};
```

**关键点**:

- 使用 `useContainerContext` 获取数据源上下文
- 将上下文中的属性传递给 `PisellQuickFilter`
- 排除 `serverActions` 和 `refManager`，避免不必要的传递

#### FilterList 组件

```
const FilterList: React.FC<FilterListProps> = ({ locales }) => {
  const { form, serverActions } = useContainerContext();
  const { list } = serverActions;
  const [valueType, setValueType] = useState<number | undefined>(undefined);

  const handleTodayClick = () => {
    const today = dayjs();
    const params = {
      time: [today.startOf('day'), today.endOf('day')],
      payment_status: undefined,
    };
    form.setFieldsValue(params);
    list.onSearch(params);
    setValueType(1);
  };

  return (
    <div style={{ display: 'flex', gap: '8px' }}>
      <Button
        type="primary"
        ghost={valueType !== 1}
        onClick={handleTodayClick}
      >
        {locales.getText('pisell2.orderList.filterButton.todayOrders')}
      </Button>
      {/* ... 其他按钮 */}
    </div>
  );
};
```

**关键点**:

- 使用 `form.setFieldsValue` 设置筛选参数
- 使用 `list.onSearch` 触发搜索
- 使用 `valueType` 状态控制按钮的激活状态
- 使用 `ghost` 属性控制按钮样式

#### HandleActions 组件

```
const HandleActions = (props: { locales: any }) => {
  const { locales } = props;
  const { serverActions, refManager, ...otherProps } = useContainerContext();

  const filterButtonListValue = useMemo(
    () => ({
      otherFilter: getFilterButtonList(locales),
    }),
    [locales]
  );

  const sortList = useMemo(() => getSortList(locales), [locales]);

  return (
    <>
      <ColumnsSetting />
      <PisellSort {...otherProps} list={sortList} />
      <PisellFilter {...otherProps} value={filterButtonListValue} />
    </>
  );
};
```

### 5. 多语言配置 (locales.ts)

```
export default {
  en: {
    'pisell2.orderList.title': 'Order List',
    'pisell2.orderList.column.orderNumber': 'Order Number',
    // ... 其他英文配置
  },
  'zh-CN': {
    'pisell2.orderList.title': '订单列表',
    'pisell2.orderList.column.orderNumber': '订单号',
    // ... 其他简体中文配置
  },
  'zh-HK': {
    'pisell2.orderList.title': '訂單列表',
    'pisell2.orderList.column.orderNumber': '訂單號',
    // ... 其他繁体中文配置
  },
};
```

**关键点**:

- 使用命名空间 `pisell2.orderList` 避免冲突
- 按功能模块分组（column、filter、sort 等）
- 保持三种语言的 key 一致

## 开发建议

### 1. 状态管理

- 使用 `PisellDataSourceContainer` 的上下文管理数据状态
- 避免在组件内部维护冗余状态
- 使用 `useMemo` 缓存计算结果

### 2. 性能优化

```
// ✅ 推荐：使用 useMemo 缓存配置
const tableColumns = useMemo(() => getTableColumns(locales), [locales]);

// ❌ 不推荐：每次渲染都重新计算
const tableColumns = getTableColumns(locales);
```

### 3. 类型安全

```
// 定义清晰的 Props 类型
interface OrderListProps {
  business_type: string;
}

// 使用类型断言时注意安全性
const customer = (record.customer || {}) as CustomerInfo;
```

### 4. 错误处理

```
// API 请求时处理错误
try {
  const res = await listApi(params);
  return res?.data;
} catch (error) {
  console.error('Failed to fetch order list:', error);
  return { list: [], total: 0 };
}
```

### 5. 代码组织

- 将配置与逻辑分离（config.tsx vs index.tsx）
- 将可复用的子组件提取到 components 目录
- 将 API 接口统一管理在 serve.ts

## 扩展开发

### 添加新的筛选项

1. 在 `config.tsx` 的 `getFilterButtonList` 中添加配置：

```
{
  name: 'new_field',
  type: 'select',
  key: 'new_field',
  label: locales.getText('pisell2.orderList.filter.newField.label'),
  other: {
    formItemProps: { labelCol: { span: 24 } },
    placeholder: locales.getText('pisell2.orderList.filter.newField.placeholder'),
    options: [
      { label: '选项1', value: 'option1' },
      { label: '选项2', value: 'option2' },
    ],
  },
}
```

2. 在 `locales.ts` 中添加多语言配置

3. 在 `serve.ts` 的 `formatParams` 中处理参数（如果需要）

### 添加新的表格列

1. 在 `config.tsx` 的 `getTableColumns` 中添加列配置：

```
{
  title: locales.getText('pisell2.orderList.column.newColumn'),
  dataIndex: 'new_field',
  key: 'new_field',
  isShow: false, // 默认隐藏
  render: (value: any) => {
    return value || '-';
  },
}
```

2. 在 `locales.ts` 中添加多语言配置

### 添加新的快捷筛选按钮

在 `components/FilterList.tsx` 中添加：

```
const handleCustomClick = () => {
  const params = {
    // 自定义筛选参数
  };
  form.setFieldsValue(params);
  list.onSearch(params);
  setValueType(5);
};

// 在 JSX 中添加按钮
<Button
  type="primary"
  ghost={valueType !== 5}
  onClick={handleCustomClick}
>
  {locales.getText('pisell2.orderList.filterButton.custom')}
</Button>
```

## 调试技巧

### 1. 查看数据源上下文

```
const context = useContainerContext();
console.log('DataSource Context:', context);
```

### 2. 查看请求参数

```
const formatParams = (params: Record<string, any>) => {
  console.log('Original Params:', params);
  const formatted = { /* ... */ };
  console.log('Formatted Params:', formatted);
  return formatted;
};
```

### 3. 查看表格数据

在浏览器控制台中：

```javascript
// 查看表格数据
document.querySelector('.ant-table-tbody').innerText;
```

## 常见问题

### 1. 多语言不生效

**原因**: 多语言未正确初始化

**解决**:

```
// 确保在组件顶部初始化多语言
locales.init(localeTexts, context?.engine?.props?.locale || 'zh-CN');
```

### 2. 筛选参数未传递到接口

**原因**: `formatParams` 中未处理该参数

**解决**:

```
const formatParams = (params: Record<string, any>) => {
  const { newField, ...rest } = params;
  return {
    ...rest,
    new_field: newField, // 添加参数映射
  };
};
```

### 3. 表格列不显示

**原因**: 列被默认隐藏了

**解决**:

- 点击列设置按钮，勾选需要显示的列
- 或在配置中设置 `isShow: true`

### 4. 下拉选项加载失败

**原因**: API 接口返回数据格式不匹配

**解决**:

```
options: (params?: string) =>
  getCustomerSelectOptions(params).then((res: any) => {
    console.log('API Response:', res); // 调试输出
    return res.map((item: any) => ({
      label: item.display_name,
      value: item.id,
    }));
  })
```

## 测试要点

1. **功能测试**

   - 所有筛选条件是否正常工作
   - 排序是否正确
   - 分页是否正确
   - 快捷筛选按钮是否正确

2. **边界测试**

   - 空数据展示
   - 大量数据性能
   - 特殊字符处理

3. **多语言测试**

   - 切换语言后文案是否正确
   - 日期格式是否符合语言习惯

4. **兼容性测试**
   - 不同浏览器兼容性
   - 不同屏幕尺寸适配
