wsj
3 days ago d26274d89d377067c1e2648192cffbf89464b361
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { useEffect, useMemo, useState } from 'react';
import { Button, Descriptions, Drawer, Empty, Spin, Tag } from 'antd';
import type { DescriptionsProps } from 'antd';
import { customerTypeMeta, levelMeta, renderTags, sourceMeta, stageMeta } from '../columns';
import { getCustomerDetail } from '../service';
import type { CustomerVO } from '../types';
import { formatDateTime } from '../utils';
 
interface CustomerDetailDrawerProps {
  open: boolean;
  /** 待展示的客户 ID,关闭时为 null。 */
  customerId: number | null;
  onClose: () => void;
}
 
const renderTag = (meta?: { text: string; color: string }) =>
  meta ? (
    <Tag color={meta.color} bordered={false}>
      {meta.text}
    </Tag>
  ) : (
    '-'
  );
 
const CustomerDetailDrawer = ({ open, customerId, onClose }: CustomerDetailDrawerProps) => {
  const [customer, setCustomer] = useState<CustomerVO | null>(null);
  const [loading, setLoading] = useState(false);
 
  useEffect(() => {
    if (!open || !customerId) return;
 
    let active = true;
    setCustomer(null);
    setLoading(true);
 
    getCustomerDetail({ id: customerId })
      .then((data) => {
        if (active) setCustomer(data);
      })
      .catch(() => {
        // 错误已由 request 统一提示
        if (active) setCustomer(null);
      })
      .finally(() => {
        if (active) setLoading(false);
      });
 
    return () => {
      active = false;
    };
  }, [customerId, open]);
 
  const items = useMemo<DescriptionsProps['items']>(() => {
    if (!customer) return [];
 
    return [
      { key: 'name', label: '客户名称', children: customer.name || '-' },
      {
        key: 'customerType',
        label: '客户类型',
        children: renderTag(customer.customerType ? customerTypeMeta[customer.customerType] : undefined),
      },
      {
        key: 'level',
        label: '客户等级',
        children: renderTag(customer.level ? levelMeta[customer.level] : undefined),
      },
      {
        key: 'stage',
        label: '客户阶段',
        children: renderTag(customer.stage ? stageMeta[customer.stage] : undefined),
      },
      { key: 'industry', label: '所属行业', children: customer.industry || '-' },
      { key: 'region', label: '所在地区', children: customer.region || '-' },
      {
        key: 'source',
        label: '客户来源',
        children: customer.source ? sourceMeta[customer.source] ?? customer.source : '-',
      },
      { key: 'contactName', label: '联系人', children: customer.contactName || '-' },
      { key: 'contactPhone', label: '联系电话', children: customer.contactPhone || '-' },
      { key: 'contactPosition', label: '职位', children: customer.contactPosition || '-' },
      { key: 'contactTel', label: '备用电话', children: customer.contactTel || '-' },
      { key: 'email', label: '邮箱', children: customer.email || '-' },
      { key: 'address', label: '地址', children: customer.address || '-' },
      { key: 'demand', label: '需求描述', children: customer.demand || '-' },
      { key: 'tags', label: '标签', children: renderTags(customer.tags) },
      { key: 'owner', label: '归属销售员', children: customer.ownerName || '-' },
      { key: 'lastFollowAt', label: '最近跟进', children: formatDateTime(customer.lastFollowAt) },
      { key: 'nextFollowAt', label: '下次跟进', children: formatDateTime(customer.nextFollowAt) },
      { key: 'dealAt', label: '成交时间', children: formatDateTime(customer.dealAt) },
      { key: 'lostAt', label: '流失时间', children: formatDateTime(customer.lostAt) },
      { key: 'lostReason', label: '流失原因', children: customer.lostReason || '-' },
    ];
  }, [customer]);
 
  return (
    <Drawer
      title="客户详情"
      width={520}
      open={open}
      onClose={onClose}
      footer={
        <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
          <Button onClick={onClose}>关闭</Button>
        </div>
      }
    >
      <Spin spinning={loading}>
        {customer ? (
          <Descriptions column={1} bordered size="small" items={items} />
        ) : (
          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无客户信息" />
        )}
      </Spin>
    </Drawer>
  );
};
 
export default CustomerDetailDrawer;