forked from yoone/WEB
1
0
Fork 0
WEB/src/pages/Customer/List/index.tsx

701 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 [sites, setSites] = useState<any[]>([]); // 添加站点数据状态
// 获取站点数据
const fetchSites = async () => {
try {
const { data, success } = await sitecontrollerAll();
if (success) {
setSites(data || []);
}
} catch (error) {
console.error('获取站点数据失败:', error);
}
};
// 根据站点ID获取站点名称
const getSiteName = (siteId: number | undefined | null) => {
if (!siteId) return '-';
if (typeof siteId === 'string') {
return siteId;
}
const site = sites.find((s) => s.id === siteId);
return site ? site.name : String(siteId);
};
// 组件加载时获取站点数据
useEffect(() => {
fetchSites();
}, []);
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',
hideInSearch: 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',
width: 120,
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}
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,
};
}}
columns={columns}
search={{
labelWidth: 'auto',
span: 6,
}}
pagination={{
pageSize: 20,
showSizeChanger: true,
showTotal: (total, range) =>
`${range[0]}-${range[1]} 条/总共 ${total}`,
}}
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;