import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { Button, Drawer, Empty, Spin, Tag, Timeline } from 'antd';
|
import type { TimelineProps } from 'antd';
|
import { followMethodMeta, followRecordTypeMeta, followStatusMeta, stageMeta } from '../columns';
|
import { getCustomerFollowUps } from '../server';
|
import type { FollowStatus, FollowUpVO } from '../types';
|
import { formatDateTime } from '../utils';
|
import './FollowUpRecordsDrawer.scss';
|
|
interface FollowUpRecordsDrawerProps {
|
open: boolean;
|
/** 待查看跟进记录的客户,关闭时为 null。 */
|
customer: { id: number; name: string } | null;
|
onClose: () => void;
|
}
|
|
/** 每次加载的跟进记录条数。 */
|
const PAGE_SIZE = 100;
|
|
const statusDotColors: Record<FollowStatus, string> = {
|
DONE: 'green',
|
PENDING: 'blue',
|
OVERDUE: 'red',
|
};
|
|
const renderStageChange = (record: FollowUpVO) => {
|
if (record.recordType !== 'STAGE_CHANGE') return null;
|
|
const from = record.stageFrom ? stageMeta[record.stageFrom]?.text ?? record.stageFrom : '-';
|
const to = record.stageTo ? stageMeta[record.stageTo]?.text ?? record.stageTo : '-';
|
|
return (
|
<div className="follow-up-item__meta">
|
阶段变化:{from} → {to}
|
</div>
|
);
|
};
|
|
const renderFollowUpItem = (record: FollowUpVO) => {
|
const method = record.followMethod
|
? followMethodMeta[record.followMethod] ?? record.followMethod
|
: null;
|
const recordType = record.recordType ? followRecordTypeMeta[record.recordType] : null;
|
const status = record.followStatus ? followStatusMeta[record.followStatus] : null;
|
|
return (
|
<div className="follow-up-item">
|
<div className="follow-up-item__header">
|
<span className="follow-up-item__time">
|
{formatDateTime(record.createdAt ?? record.followAt)}
|
</span>
|
{method && <Tag bordered={false}>{method}</Tag>}
|
{recordType && (
|
<Tag color={recordType.color} bordered={false}>
|
{recordType.text}
|
</Tag>
|
)}
|
{status && (
|
<Tag color={status.color} bordered={false}>
|
{status.text}
|
</Tag>
|
)}
|
</div>
|
|
{record.content && <div className="follow-up-item__content">{record.content}</div>}
|
|
{renderStageChange(record)}
|
|
{(record.nextFollowAt || record.nextFollowPlan) && (
|
<div className="follow-up-item__meta">
|
下次跟进:{formatDateTime(record.nextFollowAt)}
|
{record.nextFollowPlan ? ` · ${record.nextFollowPlan}` : ''}
|
</div>
|
)}
|
|
{record.followerId && (
|
<div className="follow-up-item__meta">跟进人:#{record.followerId}</div>
|
)}
|
</div>
|
);
|
};
|
|
const FollowUpRecordsDrawer = ({ open, customer, onClose }: FollowUpRecordsDrawerProps) => {
|
const [records, setRecords] = useState<FollowUpVO[]>([]);
|
const [total, setTotal] = useState(0);
|
const [page, setPage] = useState(1);
|
const [loading, setLoading] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
useEffect(() => {
|
if (!open || !customer) {
|
setRecords([]);
|
setTotal(0);
|
setPage(1);
|
return;
|
}
|
|
let active = true;
|
setRecords([]);
|
setTotal(0);
|
setPage(1);
|
setLoading(true);
|
|
getCustomerFollowUps({ id: customer.id, page: 1, size: PAGE_SIZE })
|
.then((result) => {
|
if (!active) return;
|
setRecords(result.records ?? []);
|
setTotal(result.total ?? 0);
|
})
|
.catch(() => {
|
// 错误已由 request 统一提示
|
if (active) setRecords([]);
|
})
|
.finally(() => {
|
if (active) setLoading(false);
|
});
|
|
return () => {
|
active = false;
|
};
|
}, [customer, open]);
|
|
const handleLoadMore = useCallback(async () => {
|
if (!customer) return;
|
|
const nextPage = page + 1;
|
setLoadingMore(true);
|
try {
|
const result = await getCustomerFollowUps({
|
id: customer.id,
|
page: nextPage,
|
size: PAGE_SIZE,
|
});
|
setRecords((prev) => [...prev, ...(result.records ?? [])]);
|
setTotal(result.total ?? 0);
|
setPage(nextPage);
|
} catch {
|
// 错误已由 request 统一提示
|
} finally {
|
setLoadingMore(false);
|
}
|
}, [customer, page]);
|
|
const timelineItems = useMemo<TimelineProps['items']>(
|
() =>
|
records.map((record) => ({
|
key: record.id,
|
color: record.followStatus ? statusDotColors[record.followStatus] : 'gray',
|
children: renderFollowUpItem(record),
|
})),
|
[records],
|
);
|
|
const hasMore = records.length < total;
|
|
return (
|
<Drawer
|
title={customer ? `跟进记录 · ${customer.name}` : '跟进记录'}
|
width={560}
|
open={open}
|
onClose={onClose}
|
>
|
<Spin spinning={loading}>
|
{records.length ? (
|
<div className="follow-up-records">
|
<div className="follow-up-records__summary">共 {total} 条跟进记录</div>
|
|
<Timeline items={timelineItems} />
|
|
{hasMore && (
|
<div className="follow-up-records__footer">
|
<Button loading={loadingMore} onClick={handleLoadMore}>
|
加载更多
|
</Button>
|
</div>
|
)}
|
</div>
|
) : (
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无跟进记录" />
|
)}
|
</Spin>
|
</Drawer>
|
);
|
};
|
|
export default FollowUpRecordsDrawer;
|