WEB/src/pages/Customer/List/index.tsx

674 lines
17 KiB
TypeScript

import {
customercontrollerAddtag,
customercontrollerDeltag,
customercontrollerGetcustomerlist,
customercontrollerGettags,
customercontrollerSetrate,
customercontrollerSynccustomers,
} from '@/servers/api/customer';
import { sitecontrollerAll } from '@/servers/api/site';
import {
ActionType,
ModalForm,
PageContainer,
ProColumns,
ProFormDateTimeRangePicker,
ProFormSelect,
ProFormText,
ProTable,
} from '@ant-design/pro-components';
import { App, Avatar, Button, Form, Rate, Space, Tag, Tooltip } from 'antd';
import { useEffect, useRef, useState } from 'react';
import HistoryOrders from './HistoryOrders';
// 地址格式化函数
const formatAddress = (address: any) => {
if (!address) return '-';
if (typeof address === 'string') {
try {
address = JSON.parse(address);
} catch (e) {
return address;
}
}
const {
first_name,
last_name,
company,
address_1,
address_2,
city,
state,
postcode,
country,
phone: addressPhone,
email: addressEmail,
} = address;
const parts = [];
// 姓名
const fullName = [first_name, last_name].filter(Boolean).join(' ');
if (fullName) parts.push(fullName);
// 公司
if (company) parts.push(company);
// 地址行
if (address_1) parts.push(address_1);
if (address_2) parts.push(address_2);
// 城市、州、邮编
const locationParts = [city, state, postcode].filter(Boolean).join(', ');
if (locationParts) parts.push(locationParts);
// 国家
if (country) parts.push(country);
// 联系方式
if (addressPhone) parts.push(`电话: ${addressPhone}`);
if (addressEmail) parts.push(`邮箱: ${addressEmail}`);
return parts.join(', ');
};
// 地址卡片组件
const AddressCell: React.FC<{ address: any; title: string }> = ({
address,
title,
}) => {
const formattedAddress = formatAddress(address);
if (formattedAddress === '-') {
return <span>-</span>;
}
return (
<Tooltip
title={
<div style={{ maxWidth: 300, whiteSpace: 'pre-line' }}>
<strong>{title}:</strong>
<br />
{formattedAddress}
</div>
}
placement="topLeft"
>
<div
style={{
maxWidth: 200,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
cursor: 'pointer',
}}
>
{formattedAddress}
</div>
</Tooltip>
);
};
const CustomerList: React.FC = () => {
const actionRef = useRef<ActionType>();
const { message } = App.useApp();
const [syncModalVisible, setSyncModalVisible] = useState(false);
const columns: ProColumns<API.GetCustomerDTO>[] = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '原始 ID',
dataIndex: 'origin_id',
sorter: true,
},
{
title: '站点',
dataIndex: 'site_id',
valueType: 'select',
request: async () => {
try {
const { data, success } = await sitecontrollerAll();
if (success && data) {
return data.map((site: any) => ({
label: site.name,
value: site.id,
}));
}
return [];
} catch (error) {
console.error('获取站点列表失败:', error);
return [];
}
},
},
{
title: '头像',
dataIndex: 'avatar',
hideInSearch: true,
width: 60,
render: (_, record) => (
<Avatar
src={record.avatar}
size="small"
style={{ backgroundColor: '#1890ff' }}
>
{!record.avatar && record.fullname?.charAt(0)?.toUpperCase()}
</Avatar>
),
},
{
title: '姓名',
dataIndex: 'fullname',
sorter: true,
render: (_, record) => {
return (
record.fullname ||
`${record.first_name || ''} ${record.last_name || ''}`.trim() ||
record.username ||
'-'
);
},
},
{
title: '用户名',
dataIndex: 'username',
copyable: true,
sorter: true,
},
{
title: '邮箱',
dataIndex: 'email',
copyable: true,
sorter: true,
},
{
title: '电话',
dataIndex: 'phone',
copyable: true,
},
{
title: '账单地址',
dataIndex: 'billing',
hideInSearch: true,
width: 200,
render: (billing) => <AddressCell address={billing} title="账单地址" />,
},
{
title: '物流地址',
dataIndex: 'shipping',
hideInSearch: true,
width: 200,
render: (shipping) => <AddressCell address={shipping} title="物流地址" />,
},
{
title: '评分',
dataIndex: 'rate',
hideInSearch: true,
sorter: true,
render: (_, record) => {
return (
<Rate
onChange={async (val) => {
try {
const { success, message: msg } =
await customercontrollerSetrate({
id: record.id,
rate: val,
});
if (success) {
message.success(msg);
actionRef.current?.reload();
}
} catch (e: any) {
message.error(e?.message || '设置评分失败');
}
}}
value={record.rate || 0}
allowHalf
/>
);
},
},
{
title: '标签',
dataIndex: 'tags',
hideInSearch: true,
render: (_, record) => {
const tags = record?.tags || [];
return (
<Space size={[0, 8]} wrap>
{tags.map((tag: string) => {
return (
<Tag
key={tag}
closable
onClose={async () => {
const { success, message: msg } =
await customercontrollerDeltag({
email: record.email,
tag,
});
if (!success) {
message.error(msg);
return false;
}
actionRef.current?.reload();
return true;
}}
style={{ marginBottom: 4 }}
>
{tag}
</Tag>
);
})}
</Space>
);
},
},
{
title: '创建时间',
dataIndex: 'site_created_at',
valueType: 'dateTime',
hideInSearch: true,
sorter: true,
width: 140,
},
{
title: '更新时间',
dataIndex: 'site_created_at',
valueType: 'dateTime',
hideInSearch: true,
sorter: true,
width: 140,
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
width: 120,
render: (_, record) => {
return (
<Space direction="vertical" size="small">
<AddTag
email={record.email || ''}
tags={record.raw?.tags || []}
tableRef={actionRef}
/>
{/* 订单 */}
<HistoryOrders customer={record} siteId={record.site_id} />
</Space>
);
},
},
];
return (
<PageContainer header={{ title: '客户列表' }}>
<ProTable
scroll={{ x: 'max-content' }}
headerTitle="查询表格"
actionRef={actionRef}
columns={columns}
rowKey="id"
request={async (params, sorter, filter) => {
console.log('custoemr request', params, sorter, filter);
const { current, pageSize, ...restParams } = params;
const orderBy: any = {};
Object.entries(sorter).forEach(([key, value]) => {
orderBy[key] = value === 'ascend' ? 'asc' : 'desc';
});
// 构建查询参数
const queryParams: any = {
page: current || 1,
per_page: pageSize || 20,
where: {
...filter,
...restParams,
},
orderBy,
};
const result = await customercontrollerGetcustomerlist({
params: queryParams,
});
console.log(queryParams, result);
return {
total: result?.data?.total || 0,
data: result?.data?.items || [],
success: true,
};
}}
search={{
labelWidth: 'auto',
span: 6,
}}
pagination={{
pageSize: 20,
showSizeChanger: true,
showQuickJumper: true,
}}
toolBarRender={() => [
<Button
key="sync"
type="primary"
onClick={() => setSyncModalVisible(true)}
>
</Button>,
// 这里可以添加导出、导入等功能按钮
]}
/>
<SyncCustomersModal
visible={syncModalVisible}
onClose={() => setSyncModalVisible(false)}
tableRef={actionRef}
/>
</PageContainer>
);
};
const AddTag: React.FC<{
email: string;
tags?: string[];
tableRef: React.MutableRefObject<ActionType | undefined>;
}> = ({ email, tags, tableRef }) => {
const { message } = App.useApp();
const [tagList, setTagList] = useState<string[]>([]);
return (
<ModalForm
title={`修改标签 - ${email}`}
trigger={
<Button type="link" size="small">
</Button>
}
width={800}
modalProps={{
destroyOnHidden: true,
}}
submitter={false}
>
<ProFormSelect
mode="tags"
allowClear
name="tag"
label="标签"
request={async () => {
const { data, success } = await customercontrollerGettags();
if (!success) return [];
setTagList(tags || []);
return data
.filter((tag: string) => {
return !(tags || []).includes(tag);
})
.map((tag: string) => ({ label: tag, value: tag }));
}}
fieldProps={{
value: tagList, // 当前值
onChange: async (newValue) => {
const added = newValue.filter((x) => !(tags || []).includes(x));
const removed = (tags || []).filter((x) => !newValue.includes(x));
if (added.length) {
const { success, message: msg } = await customercontrollerAddtag({
email,
tag: added[0],
});
if (!success) {
message.error(msg);
return;
}
}
if (removed.length) {
const { success, message: msg } = await customercontrollerDeltag({
email,
tag: removed[0],
});
if (!success) {
message.error(msg);
return;
}
}
tableRef?.current?.reload();
setTagList(newValue);
},
}}
></ProFormSelect>
</ModalForm>
);
};
const SyncCustomersModal: React.FC<{
visible: boolean;
onClose: () => void;
tableRef: React.MutableRefObject<ActionType | undefined>;
}> = ({ visible, onClose, tableRef }) => {
const { message } = App.useApp();
const [sites, setSites] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm(); // 添加表单实例
// 获取站点列表
useEffect(() => {
if (visible) {
setLoading(true);
sitecontrollerAll()
.then((res: any) => {
setSites(res?.data || []);
})
.catch((error: any) => {
message.error('获取站点列表失败: ' + (error.message || '未知错误'));
})
.finally(() => {
setLoading(false);
});
}
}, [visible]);
// 定义同步参数类型
type SyncParams = {
siteId: number;
search?: string;
role?: string;
dateRange?: [string, string];
orderBy?: string;
};
const handleSync = async (values: SyncParams) => {
try {
setLoading(true);
// 构建过滤参数
const params: any = {};
// 添加搜索关键词
if (values.search) {
params.search = values.search;
}
// 添加角色过滤
if (values.role) {
params.where = {
...params.where,
role: values.role,
};
}
// 添加日期范围过滤(使用 after 和 before 参数)
if (values.dateRange && values.dateRange[0] && values.dateRange[1]) {
params.where = {
...params.where,
after: values.dateRange[0],
before: values.dateRange[1],
};
}
// 添加排序
if (values.orderBy) {
params.orderBy = values.orderBy;
}
const {
success,
message: msg,
data,
} = await customercontrollerSynccustomers({
siteId: values.siteId,
params: Object.keys(params).length > 0 ? params : undefined,
});
if (success) {
// 显示详细的同步结果
const result = data || {};
const {
total = 0,
synced = 0,
created = 0,
updated = 0,
errors = [],
} = result;
let resultMessage = `同步完成!共处理 ${total} 个客户:`;
if (created > 0) resultMessage += ` 新建 ${created}`;
if (updated > 0) resultMessage += ` 更新 ${updated}`;
if (synced > 0) resultMessage += ` 同步成功 ${synced}`;
if (errors.length > 0) resultMessage += ` 失败 ${errors.length}`;
if (errors.length > 0) {
// 如果有错误,显示警告消息
message.warning({
content: (
<div>
<div>{resultMessage}</div>
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
:
{errors
.slice(0, 3)
.map((err: any) => err.email || err.error)
.join(', ')}
{errors.length > 3 && `${errors.length - 3} 个错误...`}
</div>
</div>
),
duration: 8,
key: 'sync-result',
});
} else {
// 完全成功
message.success({
content: resultMessage,
duration: 4,
key: 'sync-result',
});
}
onClose();
// 刷新表格数据
tableRef.current?.reload();
return true;
} else {
message.error(msg || '同步失败');
return false;
}
} catch (error: any) {
message.error('同步失败: ' + (error.message || '未知错误'));
return false;
} finally {
setLoading(false);
}
};
return (
<ModalForm
title="同步客户数据"
open={visible}
onOpenChange={(open) => !open && onClose()}
modalProps={{
destroyOnClose: true,
confirmLoading: loading,
}}
onFinish={handleSync}
form={form}
>
<ProFormSelect
name="siteId"
label="选择站点"
placeholder="请选择要同步的站点"
options={sites.map((site) => ({
label: site.name,
value: site.id,
}))}
rules={[{ required: true, message: '请选择站点' }]}
fieldProps={{
loading: loading,
}}
/>
<ProFormText
name="search"
label="搜索关键词"
placeholder="输入邮箱、姓名或用户名进行搜索"
tooltip="支持搜索邮箱、姓名、用户名等字段"
/>
<ProFormSelect
name="role"
label="客户角色"
placeholder="选择客户角色进行过滤"
options={[
{ label: '所有角色', value: '' },
{ label: '管理员', value: 'administrator' },
{ label: '编辑', value: 'editor' },
{ label: '作者', value: 'author' },
{ label: '订阅者', value: 'subscriber' },
{ label: '客户', value: 'customer' },
]}
fieldProps={{
allowClear: true,
}}
/>
<ProFormDateTimeRangePicker
name="dateRange"
label="注册日期范围"
placeholder={['开始日期', '结束日期']}
transform={(value) => {
return {
dateRange: value,
};
}}
fieldProps={{
showTime: false,
style: { width: '100%' },
}}
/>
<ProFormSelect
name="orderBy"
label="排序方式"
placeholder="选择排序方式"
options={[
{ label: '默认排序', value: '' },
{ label: '注册时间(升序)', value: 'date_created:asc' },
{ label: '注册时间(降序)', value: 'date_created:desc' },
{ label: '邮箱(升序)', value: 'email:asc' },
{ label: '邮箱(降序)', value: 'email:desc' },
{ label: '姓名(升序)', value: 'first_name:asc' },
{ label: '姓名(降序)', value: 'first_name:desc' },
]}
fieldProps={{
allowClear: true,
}}
/>
</ModalForm>
);
};
export { AddTag };
export default CustomerList;