wsj
3 days ago cd2b7d36f3610417c268d062454b7ec43e9ca937
src/pages/business/orders/index.tsx
@@ -1,36 +1,17 @@
import { useMemo, useState } from 'react';
import { DownloadOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
import {
  App as AntdApp,
  Button,
  Card,
  Input,
  Select,
  Table,
  Tag,
} from 'antd';
import type { TableProps } from 'antd';
import { useCallback, useMemo, useState } from 'react';
import { DownloadOutlined } from '@ant-design/icons';
import { ProTable } from '@ant-design/pro-components';
import { App as AntdApp, Button } from 'antd';
import { createOrderColumns } from './columns';
import type { OrderRecord, OrderStatus } from './types';
import './Orders.scss';
type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';
interface OrderRecord {
  key: string;
  id: string;
  customer: string;
  contact: string;
  amount: number;
  channel: string;
  status: OrderStatus;
  createdAt: string;
interface OrderQuery {
  id?: string;
  customer?: string;
  status?: OrderStatus;
  orders: OrderRecord[];
}
const statusMeta: Record<OrderStatus, { label: string; color: string }> = {
  pending: { label: '待付款', color: 'orange' },
  processing: { label: '处理中', color: 'processing' },
  completed: { label: '已完成', color: 'success' },
  cancelled: { label: '已取消', color: 'default' },
};
const initialOrders: OrderRecord[] = [
  {
@@ -137,103 +118,42 @@
const Orders = () => {
  const [orders, setOrders] = useState<OrderRecord[]>(initialOrders);
  const [keyword, setKeyword] = useState('');
  const [status, setStatus] = useState<OrderStatus | 'all'>('all');
  const [summary, setSummary] = useState(() => ({
    count: initialOrders.length,
    amount: initialOrders.reduce((sum, order) => sum + order.amount, 0),
  }));
  const { message, modal } = AntdApp.useApp();
  const filteredOrders = useMemo(() => {
    const normalizedKeyword = keyword.trim().toLowerCase();
    return orders.filter((order) => {
      const matchesKeyword =
        !normalizedKeyword ||
        order.id.toLowerCase().includes(normalizedKeyword) ||
        order.customer.toLowerCase().includes(normalizedKeyword);
      const matchesStatus = status === 'all' || order.status === status;
      return matchesKeyword && matchesStatus;
    });
  }, [orders, keyword, status]);
  const tableParams = useMemo(() => ({ orders }), [orders]);
  const handleProcess = (record: OrderRecord) => {
    modal.confirm({
      title: '处理订单',
      content: `确定将订单「${record.id}」标记为已完成吗?`,
      okText: '标记完成',
      cancelText: '取消',
      onOk: () => {
        setOrders((prev) =>
          prev.map((order) =>
            order.key === record.key ? { ...order, status: 'completed' } : order,
          ),
        );
        message.success('订单状态已更新');
      },
    });
  };
  const handleProcess = useCallback(
    (record: OrderRecord) => {
      modal.confirm({
        title: '处理订单',
        content: `确定将订单「${record.id}」标记为已完成吗?`,
        okText: '标记完成',
        cancelText: '取消',
        onOk: () => {
          setOrders((prev) =>
            prev.map((order) =>
              order.key === record.key ? { ...order, status: 'completed' } : order,
            ),
          );
          message.success('订单状态已更新');
        },
      });
    },
    [message, modal],
  );
  const columns = useMemo(
    () => createOrderColumns({ onProcess: handleProcess }),
    [handleProcess],
  );
  const handleExport = () => {
    message.success(`已导出 ${filteredOrders.length} 条订单记录(演示)`);
    message.success(`已导出 ${summary.count} 条订单记录(演示)`);
  };
  const columns: TableProps<OrderRecord>['columns'] = [
    {
      title: '订单号',
      dataIndex: 'id',
      width: 160,
      render: (value: string) => <code className="order-id">{value}</code>,
    },
    {
      title: '客户',
      dataIndex: 'customer',
      width: 240,
      render: (_value, record) => (
        <div className="order-customer">
          <strong>{record.customer}</strong>
          <span>{record.contact}</span>
        </div>
      ),
    },
    {
      title: '订单金额',
      dataIndex: 'amount',
      width: 120,
      align: 'right',
      render: (value: number) => <strong className="order-amount">¥{value.toLocaleString()}</strong>,
    },
    {
      title: '来源渠道',
      dataIndex: 'channel',
      width: 110,
      render: (value: string) => (
        <Tag bordered={false} color="geekblue">
          {value}
        </Tag>
      ),
    },
    {
      title: '状态',
      dataIndex: 'status',
      width: 100,
      render: (value: OrderStatus) => (
        <Tag color={statusMeta[value].color} bordered={false}>
          {statusMeta[value].label}
        </Tag>
      ),
    },
    { title: '创建时间', dataIndex: 'createdAt', width: 160 },
    {
      title: '操作',
      key: 'action',
      width: 100,
      render: (_value, record) =>
        record.status === 'completed' ? (
          <span className="text-muted">已完成</span>
        ) : (
          <Button type="link" size="small" onClick={() => handleProcess(record)}>
            处理
          </Button>
        ),
    },
  ];
  return (
    <div className="orders-page">
@@ -244,61 +164,59 @@
            跟踪销售订单的处理进度,支持筛选与导出。
          </p>
        </div>
        <div className="page-header__actions">
          <Button
            icon={<ReloadOutlined />}
            onClick={() => message.success('订单数据已刷新')}
          >
            刷新
          </Button>
          <Button type="primary" icon={<DownloadOutlined />} onClick={handleExport}>
            导出订单
          </Button>
        </div>
      </div>
      <Card className="filter-card" variant="borderless">
        <div className="page-toolbar">
          <Input
            allowClear
            prefix={<SearchOutlined />}
            placeholder="搜索订单号 / 客户名称"
            value={keyword}
            onChange={(event) => setKeyword(event.target.value)}
            style={{ width: 280 }}
          />
          <Select<OrderStatus | 'all'>
            value={status}
            onChange={setStatus}
            style={{ width: 150 }}
            options={[
              { value: 'all', label: '全部状态' },
              { value: 'pending', label: '待付款' },
              { value: 'processing', label: '处理中' },
              { value: 'completed', label: '已完成' },
              { value: 'cancelled', label: '已取消' },
            ]}
          />
          <span className="text-muted">
            共 {filteredOrders.length} 条订单 · 合计 ¥
            {filteredOrders.reduce((sum, order) => sum + order.amount, 0).toLocaleString()}
          </span>
        </div>
      </Card>
      <ProTable<OrderRecord, OrderQuery>
        rowKey="key"
        columns={columns}
        params={tableParams}
        cardProps={{ className: 'orders-table-card' }}
        search={{ labelWidth: 'auto', defaultCollapsed: false }}
        scroll={{ x: 1000 }}
        options={false}
        pagination={{
          defaultPageSize: 8,
          showSizeChanger: true,
          hideOnSinglePage: false,
          showTotal: (total) => `共 ${total} 条`,
        }}
        toolBarRender={() => [
          <span key="summary" className="text-muted">
            共 {summary.count} 条订单 · 合计 ¥{summary.amount.toLocaleString()}
          </span>,
          <Button key="export" type="primary" icon={<DownloadOutlined />} onClick={handleExport}>
            导出订单
          </Button>,
        ]}
        request={async (params) => {
          const { current = 1, pageSize = 8, id, customer, status, orders: dataSource } =
            params;
          const normalizedId = id?.trim().toLowerCase();
          const normalizedCustomer = customer?.trim().toLowerCase();
      <Card className="table-card" variant="borderless">
        <Table<OrderRecord>
          rowKey="key"
          columns={columns}
          dataSource={filteredOrders}
          scroll={{ x: 1000 }}
          pagination={{
            pageSize: 8,
            showSizeChanger: false,
            showTotal: (total) => `共 ${total} 条`,
          }}
        />
      </Card>
          const filteredOrders = dataSource.filter((order) => {
            const matchesId =
              !normalizedId || order.id.toLowerCase().includes(normalizedId);
            const matchesCustomer =
              !normalizedCustomer ||
              order.customer.toLowerCase().includes(normalizedCustomer);
            const matchesStatus = !status || order.status === status;
            return matchesId && matchesCustomer && matchesStatus;
          });
          setSummary({
            count: filteredOrders.length,
            amount: filteredOrders.reduce((sum, order) => sum + order.amount, 0),
          });
          const start = (current - 1) * pageSize;
          return {
            data: filteredOrders.slice(start, start + pageSize),
            total: filteredOrders.length,
            success: true,
          };
        }}
      />
    </div>
  );
};