forked from yoone/WEB
客户列表
This commit is contained in:
parent
354812c7c4
commit
b5d05bfc65
27
.umirc.ts
27
.umirc.ts
|
|
@ -80,12 +80,12 @@ export default defineConfig({
|
|||
component: './Stock/Warehouse',
|
||||
},
|
||||
{
|
||||
name: '入库管理',
|
||||
name: '采购管理',
|
||||
path: '/stock/purchaseOrder',
|
||||
component: './Stock/PurchaseOrder',
|
||||
},
|
||||
{
|
||||
name: '调拨管理',
|
||||
name: '发货管理',
|
||||
path: '/stock/transfer',
|
||||
component: './Stock/Transfer',
|
||||
},
|
||||
|
|
@ -106,6 +106,23 @@ export default defineConfig({
|
|||
path: '/order/list',
|
||||
component: './Order/List',
|
||||
},
|
||||
{
|
||||
name: '待发货产品',
|
||||
path: '/order/item',
|
||||
component: './Order/PendingItems',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '客户管理',
|
||||
path: '/customer',
|
||||
access: 'canSeeAdmin',
|
||||
routes: [
|
||||
{
|
||||
name: '客户列表',
|
||||
path: '/customer/list',
|
||||
component: './Customer/List',
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
|
|
@ -145,6 +162,12 @@ export default defineConfig({
|
|||
component: './Statistics/Order',
|
||||
access: 'canSeeSuper',
|
||||
},
|
||||
{
|
||||
name: '订单来源',
|
||||
path: '/statistics/orderSource',
|
||||
component: './Statistics/OrderSource',
|
||||
access: 'canSeeSuper',
|
||||
},
|
||||
{
|
||||
name: '客户统计',
|
||||
path: '/statistics/customer',
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@
|
|||
"dayjs": "^1.11.9",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"print-js": "^1.6.0",
|
||||
"react-phone-input-2": "^2.15.1"
|
||||
"react-phone-input-2": "^2.15.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.33",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,247 @@
|
|||
import { HistoryOrder } from '@/pages/Statistics/Order';
|
||||
import {
|
||||
customercontrollerAddtag,
|
||||
customercontrollerDeltag,
|
||||
customercontrollerGetcustomerlist,
|
||||
customercontrollerGettags,
|
||||
} from '@/servers/api/customer';
|
||||
import {
|
||||
ActionType,
|
||||
ModalForm,
|
||||
PageContainer,
|
||||
ProColumns,
|
||||
ProFormSelect,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { App, Button, Space, Tag } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
const ListPage: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const columns: ProColumns[] = [
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
if (record.billing.first_name || record.billing.last_name)
|
||||
return record.billing.first_name + ' ' + record.billing.last_name;
|
||||
return record.shipping.first_name + ' ' + record.shipping.last_name;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
{
|
||||
title: '客户编号',
|
||||
dataIndex: 'customerId',
|
||||
render: (_, record) => {
|
||||
if(!record.customerId) return '-';
|
||||
return String(record.customerId).padStart(6,0)
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '首单时间',
|
||||
dataIndex: 'first_purchase_date',
|
||||
valueType: 'dateMonth',
|
||||
sorter: true,
|
||||
render: (_, record) =>
|
||||
record.first_purchase_date
|
||||
? dayjs(record.first_purchase_date).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-',
|
||||
// search: {
|
||||
// transform: (value: string) => {
|
||||
// return { month: value };
|
||||
// },
|
||||
// },
|
||||
},
|
||||
{
|
||||
title: '尾单时间',
|
||||
hideInSearch: true,
|
||||
dataIndex: 'last_purchase_date',
|
||||
valueType: 'dateTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '订单数',
|
||||
dataIndex: 'orders',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
dataIndex: 'total',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'YOONE订单数',
|
||||
dataIndex: 'yoone_orders',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'YOONE金额',
|
||||
dataIndex: 'yoone_total',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'state',
|
||||
dataIndex: 'state',
|
||||
render: (_, record) => record?.billing.state || record?.shipping.state,
|
||||
},
|
||||
{
|
||||
title: 'city',
|
||||
dataIndex: 'city',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record?.billing.city || record?.shipping.city,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space>
|
||||
{(record.tags || []).map((tag) => {
|
||||
return (
|
||||
<Tag
|
||||
key={tag}
|
||||
closable
|
||||
onClose={async () => {
|
||||
const { success, message: msg } =
|
||||
await customercontrollerDeltag({
|
||||
email: record.email,
|
||||
tag,
|
||||
});
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'option',
|
||||
valueType: 'option',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space>
|
||||
<AddTag
|
||||
email={record.email}
|
||||
tags={record.tags}
|
||||
tableRef={actionRef}
|
||||
/>
|
||||
<HistoryOrder
|
||||
email={record.email}
|
||||
tags={record.tags}
|
||||
tableRef={actionRef}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer ghost>
|
||||
<ProTable
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
request={async (params, sorter) => {
|
||||
const key = Object.keys(sorter)[0];
|
||||
const { data, success } = await customercontrollerGetcustomerlist({
|
||||
...params,
|
||||
...(key ? { sorterKey: key, sorterValue: sorter[key] } : {}),
|
||||
});
|
||||
|
||||
return {
|
||||
total: data?.total || 0,
|
||||
data: data?.items || [],
|
||||
success,
|
||||
};
|
||||
}}
|
||||
columns={columns}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export 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>修改标签</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) => {
|
||||
return !(tags || []).includes(tag);
|
||||
})
|
||||
.map((tag) => ({ label: tag, value: tag }));
|
||||
}}
|
||||
fieldProps={{
|
||||
value: tagList, // 当前值
|
||||
onChange: async (newValue) => {
|
||||
const added = newValue.filter((x) => !tagList.includes(x));
|
||||
const removed = tagList.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>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListPage;
|
||||
|
|
@ -154,7 +154,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
const { contact, ...params } = values;
|
||||
|
|
@ -284,7 +284,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
const { contact, ...params } = values;
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ const SyncForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -501,7 +501,7 @@ const Detail: React.FC<{
|
|||
<Drawer
|
||||
title="订单详情"
|
||||
open={visiable}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
size="large"
|
||||
onClose={() => setVisiable(false)}
|
||||
footer={[
|
||||
|
|
@ -1036,7 +1036,7 @@ const Shipping: React.FC<{
|
|||
size="large"
|
||||
width="80vw"
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
styles: {
|
||||
body: { maxHeight: '65vh', overflowY: 'auto', overflowX: 'hidden' },
|
||||
},
|
||||
|
|
@ -1265,7 +1265,7 @@ const Shipping: React.FC<{
|
|||
<ProFormSelect
|
||||
params={{ options }}
|
||||
request={async ({ keyWords, options }) => {
|
||||
if (!keyWords || keyWords.length < 3) return options;
|
||||
if (!keyWords || keyWords.length < 2) return options;
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -1273,7 +1273,7 @@ const Shipping: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item?.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item?.sku,
|
||||
};
|
||||
}) || options
|
||||
|
|
@ -1802,7 +1802,7 @@ const CreateOrder: React.FC<{
|
|||
size="large"
|
||||
width="80vw"
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
styles: {
|
||||
body: { maxHeight: '65vh', overflowY: 'auto', overflowX: 'hidden' },
|
||||
},
|
||||
|
|
@ -1844,7 +1844,7 @@ const CreateOrder: React.FC<{
|
|||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords, options }) => {
|
||||
if (!keyWords || keyWords.length < 3) return options;
|
||||
if (!keyWords || keyWords.length < 2) return options;
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -1852,7 +1852,7 @@ const CreateOrder: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item?.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item?.sku,
|
||||
};
|
||||
}) || options
|
||||
|
|
@ -2011,7 +2011,7 @@ const AddressPicker: React.FC<{
|
|||
<ModalForm
|
||||
title="选择地址"
|
||||
trigger={<Button type="primary">选择地址</Button>}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
onFinish={async () => {
|
||||
if (!selectedRow) {
|
||||
message.error('请选择地址');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
import { ordercontrollerPengdingitems } from '@/servers/api/order';
|
||||
import { stockcontrollerGetallstockpoints } from '@/servers/api/stock';
|
||||
import {
|
||||
ActionType,
|
||||
PageContainer,
|
||||
ProColumns,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { App, Button } from 'antd';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
const ListPage: React.FC = () => {
|
||||
const { message } = App.useApp();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [points, setPoints] = useState<API.StockPoint[]>([]);
|
||||
useEffect(() => {
|
||||
stockcontrollerGetallstockpoints().then(({ data }) => {
|
||||
setPoints(data as API.StockPoint[]);
|
||||
});
|
||||
}, []);
|
||||
const columns: ProColumns<API.StockDTO>[] = [
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'name',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'quantity',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '订单号',
|
||||
dataIndex: 'numbers',
|
||||
hideInSearch: true,
|
||||
width: 800,
|
||||
render: (_, record) => {
|
||||
return record?.numbers?.join?.('、');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer ghost>
|
||||
<ProTable<API.StockDTO>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
request={async (params) => {
|
||||
const { data, success } = await ordercontrollerPengdingitems(params);
|
||||
|
||||
return {
|
||||
total: data?.total || 0,
|
||||
data: data?.items || [],
|
||||
success,
|
||||
};
|
||||
}}
|
||||
columns={columns}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
const { data, success } = await ordercontrollerPengdingitems({
|
||||
current: 1,
|
||||
pageSize: 20000,
|
||||
});
|
||||
if (!success) return;
|
||||
// 表头
|
||||
const headers = ['产品名', '数量', '订单号'];
|
||||
|
||||
// 数据行
|
||||
const rows = (data?.items || []).map((item) => {
|
||||
return [item.name, item.quantity, item.numbers?.join('、')];
|
||||
});
|
||||
|
||||
// 导出
|
||||
const sheet = XLSX.utils.aoa_to_sheet([headers, ...rows]);
|
||||
const book = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(book, sheet, '待发产品');
|
||||
const buffer = XLSX.write(book, {
|
||||
bookType: 'xlsx',
|
||||
type: 'array',
|
||||
});
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/octet-stream',
|
||||
});
|
||||
saveAs(blob, '待发产品.xlsx');
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListPage;
|
||||
|
|
@ -84,7 +84,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -174,7 +174,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -172,7 +172,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
productcontrollerGetflavorsall,
|
||||
productcontrollerGetproductlist,
|
||||
productcontrollerGetstrengthall,
|
||||
productcontrollerUpdateproductnamecn,
|
||||
} from '@/servers/api/product';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
|
|
@ -21,6 +22,28 @@ import {
|
|||
import { App, Button, Popconfirm } from 'antd';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
const NameCn: React.FC<{
|
||||
id: number;
|
||||
value: string;
|
||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||
}> = ({value,tableRef, id}) => {
|
||||
const { message } = App.useApp();
|
||||
const [editable, setEditable] = React.useState<boolean>(false);
|
||||
if (!editable) return <div onClick={() => setEditable(true)}>{value||'-'}</div>;
|
||||
return <ProFormText fieldProps={{autoFocus:true}} initialValue={value} onBlur={async(e) => {
|
||||
if(!e.target.value) return setEditable(false)
|
||||
const { success, message: errMsg } =
|
||||
await productcontrollerUpdateproductnamecn({
|
||||
id,
|
||||
nameCn: e.target.value,
|
||||
})
|
||||
setEditable(false)
|
||||
if (!success) {
|
||||
return message.error(errMsg)
|
||||
}
|
||||
tableRef?.current?.reload()
|
||||
}} />
|
||||
}
|
||||
const List: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
|
|
@ -30,6 +53,15 @@ const List: React.FC = () => {
|
|||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '中文名',
|
||||
dataIndex: 'nameCn',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<NameCn value={record.nameCn} id={record.id} tableRef={actionRef} />
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品描述',
|
||||
dataIndex: 'description',
|
||||
|
|
@ -117,6 +149,12 @@ const List: React.FC = () => {
|
|||
};
|
||||
}}
|
||||
columns={columns}
|
||||
editable={{
|
||||
type: 'single',
|
||||
onSave: async (key, record, originRow) => {
|
||||
console.log('保存数据:', record);
|
||||
},
|
||||
}}
|
||||
rowSelection={{
|
||||
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
|
||||
}}
|
||||
|
|
@ -140,7 +178,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -170,7 +170,7 @@ const CreateForm: React.FC<{
|
|||
// }
|
||||
// autoFocusFirstInput
|
||||
// drawerProps={{
|
||||
// destroyOnClose: true,
|
||||
// destroyOnHidden: true,
|
||||
// }}
|
||||
// onFinish={async (values) => {
|
||||
// try {
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ const SyncForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -249,7 +249,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
const { siteId, ...params } = values;
|
||||
|
|
@ -339,7 +339,7 @@ const UpdateVaritation: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
const { ...params } = values;
|
||||
|
|
@ -434,7 +434,7 @@ const SetComponent: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async ({ constitution }) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { ORDER_STATUS_ENUM } from '@/constants';
|
||||
import { AddTag } from '@/pages/Customer/List';
|
||||
import { customercontrollerDeltag } from '@/servers/api/customer';
|
||||
import { sitecontrollerAll } from '@/servers/api/site';
|
||||
import {
|
||||
statisticscontrollerGetorderbydate,
|
||||
|
|
@ -7,6 +9,7 @@ import {
|
|||
} from '@/servers/api/statistics';
|
||||
import { formatSource } from '@/utils/format';
|
||||
import {
|
||||
ActionType,
|
||||
ModalForm,
|
||||
PageContainer,
|
||||
ProForm,
|
||||
|
|
@ -14,10 +17,10 @@ import {
|
|||
ProFormSelect,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Space, Tag } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
const highlightText = (text: string, keyword: string) => {
|
||||
if (!keyword) return text;
|
||||
|
|
@ -43,6 +46,7 @@ const ListPage: React.FC = () => {
|
|||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function (params) {
|
||||
const xValue = params[0].axisValue;
|
||||
const items = params.map((item) => {
|
||||
return `<span style="display: inline-block;width: 250px">${item.marker} ${item.seriesName}: ${item.value}</span>`;
|
||||
});
|
||||
|
|
@ -53,7 +57,13 @@ const ListPage: React.FC = () => {
|
|||
rows.push(items.slice(i, i + 4).join(''));
|
||||
}
|
||||
|
||||
return rows.join('<br/>');
|
||||
return (
|
||||
`<div>${xValue} ${
|
||||
['周日', '周一', '周二', '周三', '周四', '周五', '周六'][
|
||||
dayjs(xValue).day()
|
||||
]
|
||||
}</div>` + rows.join('<br/>')
|
||||
);
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
|
|
@ -120,28 +130,6 @@ const ListPage: React.FC = () => {
|
|||
const res = data?.sort(() => -1);
|
||||
setXAxis(res?.map((v) => dayjs(v.order_date).format('YYYY-MM-DD')));
|
||||
setSeries([
|
||||
{
|
||||
name: '首购订单数',
|
||||
type: 'line',
|
||||
data: res?.map((v) => v.first_purchase_orders),
|
||||
},
|
||||
{
|
||||
name: '首购金额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: res?.map((v) => v.first_purchase_total),
|
||||
},
|
||||
{
|
||||
name: '复购订单数',
|
||||
type: 'line',
|
||||
data: res?.map((v) => v.repeat_purchase_orders),
|
||||
},
|
||||
{
|
||||
name: '复购金额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: res?.map((v) => v.repeat_purchase_total),
|
||||
},
|
||||
{
|
||||
name: 'TOGO CPC订单数',
|
||||
type: 'line',
|
||||
|
|
@ -162,6 +150,50 @@ const ListPage: React.FC = () => {
|
|||
type: 'line',
|
||||
data: res?.map((v) => v.non_can_cpc_orders),
|
||||
},
|
||||
{
|
||||
name: '直达订单数',
|
||||
type: 'line',
|
||||
data: res?.map((v) => v.direct_orders),
|
||||
},
|
||||
{
|
||||
name: '直达金额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: res?.map((v) => v.direct_total),
|
||||
},
|
||||
{
|
||||
name: '首购订单数',
|
||||
type: 'line',
|
||||
data: res?.map((v) => v.first_purchase_orders),
|
||||
},
|
||||
{
|
||||
name: '首购金额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: res?.map((v) => v.first_purchase_total),
|
||||
},
|
||||
{
|
||||
name: 'organic订单数',
|
||||
type: 'line',
|
||||
data: res?.map((v) => v.organic_orders),
|
||||
},
|
||||
{
|
||||
name: 'organic金额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: res?.map((v) => v.organic_total),
|
||||
},
|
||||
{
|
||||
name: '复购订单数',
|
||||
type: 'line',
|
||||
data: res?.map((v) => v.repeat_purchase_orders),
|
||||
},
|
||||
{
|
||||
name: '复购金额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: res?.map((v) => v.repeat_purchase_total),
|
||||
},
|
||||
{
|
||||
name: 'CPC订单数',
|
||||
type: 'line',
|
||||
|
|
@ -612,6 +644,7 @@ const DailyOrders: React.FC<{
|
|||
selectedDate;
|
||||
}> = ({ selectedDate }) => {
|
||||
const [orders, setOrders] = useState([]);
|
||||
const actionRef = useRef<ActionType>();
|
||||
useEffect(() => {
|
||||
if (!selectedDate) {
|
||||
setOrders([]);
|
||||
|
|
@ -683,6 +716,12 @@ const DailyOrders: React.FC<{
|
|||
valueType: 'dateTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '尾单时间',
|
||||
dataIndex: 'last_purchase_date',
|
||||
valueType: 'dateTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '支付时间',
|
||||
dataIndex: 'date_paid',
|
||||
|
|
@ -784,12 +823,53 @@ const DailyOrders: React.FC<{
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space>
|
||||
{(record.tags || []).map((tag) => {
|
||||
return (
|
||||
<Tag
|
||||
key={tag}
|
||||
closable
|
||||
onClose={async () => {
|
||||
const { success, message: msg } =
|
||||
await customercontrollerDeltag({
|
||||
email: record.email,
|
||||
tag,
|
||||
});
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
valueType: 'option',
|
||||
render: (_, record) => {
|
||||
return <HistoryOrder email={record.customer_email} />;
|
||||
return (
|
||||
<Space>
|
||||
<AddTag
|
||||
email={record.customer_email}
|
||||
tags={record.tags}
|
||||
tableRef={actionRef}
|
||||
/>
|
||||
<HistoryOrder
|
||||
email={record.customer_email}
|
||||
tags={record.tags}
|
||||
tableRef={actionRef}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
|
@ -799,15 +879,19 @@ const DailyOrders: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
const HistoryOrder: React.FC<{
|
||||
export const HistoryOrder: React.FC<{
|
||||
email: string;
|
||||
}> = ({ email }) => {
|
||||
tags?: string[];
|
||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||
}> = ({ email, tags, tableRef }) => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
return (
|
||||
<ModalForm
|
||||
title={`历史订单${email}`}
|
||||
trigger={<Button type="primary">历史订单</Button>}
|
||||
modalProps={{ destroyOnClose: true, footer: null }}
|
||||
modalProps={{ destroyOnHidden: true, footer: null }}
|
||||
width="80vw"
|
||||
submitter={false}
|
||||
>
|
||||
<ProTable
|
||||
search={false}
|
||||
|
|
@ -869,6 +953,34 @@ const HistoryOrder: React.FC<{
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space>
|
||||
{(record.tags || []).map((tag) => {
|
||||
return (
|
||||
<Tag
|
||||
key={tag}
|
||||
// closable
|
||||
onClose={async () => {
|
||||
const { success, message: msg } =
|
||||
await customercontrollerDeltag({
|
||||
email,
|
||||
tag,
|
||||
});
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
request={async () => {
|
||||
const { data, success } = await statisticscontrollerGetorderbyemail({
|
||||
|
|
@ -879,6 +991,9 @@ const HistoryOrder: React.FC<{
|
|||
success,
|
||||
};
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<AddTag email={email} tags={tags} tableRef={actionRef} />,
|
||||
]}
|
||||
/>
|
||||
</ModalForm>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,257 @@
|
|||
import React, { useEffect, useState, useMemo, useRef } from "react"
|
||||
|
||||
import {
|
||||
ActionType,
|
||||
PageContainer, ProColumns, ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { statisticscontrollerGetordersorce, statisticscontrollerGetinativeusersbymonth } from "@/servers/api/statistics";
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { App, Button, Space, Tag } from 'antd';
|
||||
import { HistoryOrder } from "../Order";
|
||||
import dayjs from 'dayjs';
|
||||
const ListPage: React.FC = () => {
|
||||
|
||||
const [data, setData] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
statisticscontrollerGetordersorce().then(({ data, success }) => {
|
||||
if(success) setData(data)
|
||||
});
|
||||
}, []);
|
||||
|
||||
const option = useMemo(() => {
|
||||
if(!data.inactiveRes) return {}
|
||||
const xAxisData = data?.inactiveRes?.map(v=> v.order_month)?.sort(_=>-1)
|
||||
const arr = data?.res?.map(v=>v.first_order_month_group)
|
||||
const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index)
|
||||
const series = [
|
||||
{
|
||||
name: '新客户',
|
||||
type: 'bar',
|
||||
data: data?.inactiveRes?.map(v=> v.new_user_count)?.sort(_=>-1),
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
},
|
||||
{
|
||||
name: '老客户',
|
||||
type: 'bar',
|
||||
data: data?.inactiveRes?.map(v=> v.old_user_count)?.sort(_=>-1),
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
},
|
||||
...uniqueArr?.map(v => {
|
||||
data?.res?.filter(item => item.order_month === v)
|
||||
return {
|
||||
name: v,
|
||||
type: "bar",
|
||||
stack: "total",
|
||||
label: {
|
||||
"show": true,
|
||||
formatter: function(params) {
|
||||
if(!params.value) return ''
|
||||
return Math.abs(params.value)
|
||||
},
|
||||
color: '#fff'
|
||||
},
|
||||
"data": xAxisData.map(month => {
|
||||
return (data?.res?.find(item => item.order_month === month && item.first_order_month_group === v)?.order_count || 0)
|
||||
}),
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: '未复购客户',
|
||||
type: 'bar',
|
||||
data: data?.inactiveRes?.map(v=> -v.inactive_user_count)?.sort(_=>-1),
|
||||
stack: "total",
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
xAxisIndex: 1,
|
||||
yAxisIndex: 1,
|
||||
barWidth: "60%",
|
||||
|
||||
itemStyle: {
|
||||
color: '#f44336'
|
||||
}
|
||||
},
|
||||
]
|
||||
return {
|
||||
grid: [
|
||||
{ top: '10%', height: '70%' },
|
||||
{ bottom: '10%', height: '10%' }
|
||||
],
|
||||
legend: {
|
||||
selectedMode: false
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
data: xAxisData,
|
||||
gridIndex: 0,
|
||||
},{
|
||||
type: 'category',
|
||||
data: xAxisData,
|
||||
gridIndex: 1,
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
gridIndex: 0,
|
||||
},{
|
||||
type: 'value',
|
||||
gridIndex: 1,
|
||||
}],
|
||||
series,
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const [tableData, setTableData] = useState<any[]>([])
|
||||
const actionRef = useRef<ActionType>();
|
||||
const columns: ProColumns[] = [
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
if (record.billing.first_name || record.billing.last_name)
|
||||
return record.billing.first_name + ' ' + record.billing.last_name;
|
||||
return record.shipping.first_name + ' ' + record.shipping.last_name;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
{
|
||||
title: '首单时间',
|
||||
dataIndex: 'first_purchase_date',
|
||||
valueType: 'dateMonth',
|
||||
sorter: true,
|
||||
render: (_, record) =>
|
||||
record.first_purchase_date
|
||||
? dayjs(record.first_purchase_date).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
title: '尾单时间',
|
||||
hideInSearch: true,
|
||||
dataIndex: 'last_purchase_date',
|
||||
valueType: 'dateTime',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '订单数',
|
||||
dataIndex: 'orders',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
dataIndex: 'total',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'state',
|
||||
dataIndex: 'state',
|
||||
render: (_, record) => record?.billing.state || record?.shipping.state,
|
||||
},
|
||||
{
|
||||
title: 'city',
|
||||
dataIndex: 'city',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record?.billing.city || record?.shipping.city,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space>
|
||||
{(record.tags || []).map((tag) => {
|
||||
return (
|
||||
<Tag
|
||||
key={tag}
|
||||
closable
|
||||
onClose={async () => {
|
||||
const { success, message: msg } =
|
||||
await customercontrollerDeltag({
|
||||
email: record.email,
|
||||
tag,
|
||||
});
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'option',
|
||||
valueType: 'option',
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<HistoryOrder
|
||||
email={record.email}
|
||||
tags={record.tags}
|
||||
tableRef={actionRef}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return(
|
||||
<PageContainer ghost>
|
||||
<ReactECharts
|
||||
option={option}
|
||||
style={{ height: 1050 }}
|
||||
onEvents={{
|
||||
click: async (params) => {
|
||||
if (params.componentType === 'series') {
|
||||
setTableData([])
|
||||
const {success, data} = await statisticscontrollerGetinativeusersbymonth({
|
||||
month: params.name
|
||||
})
|
||||
if(success) setTableData(data)
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{
|
||||
tableData?.length ?
|
||||
<ProTable
|
||||
search={false}
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
dataSource={tableData}
|
||||
columns={columns}
|
||||
/>
|
||||
:<></>
|
||||
|
||||
}
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListPage;
|
||||
|
|
@ -7,11 +7,15 @@ import {
|
|||
ProFormSwitch,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Button } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useRef, useState } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
const ListPage: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef();
|
||||
const [total, setTotal] = useState(0);
|
||||
const [isSource, setIsSource] = useState(false);
|
||||
const [yooneTotal, setYooneTotal] = useState({});
|
||||
|
|
@ -106,6 +110,7 @@ const ListPage: React.FC = () => {
|
|||
<ProTable
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
formRef={formRef}
|
||||
rowKey="id"
|
||||
params={{ isSource }}
|
||||
form={{
|
||||
|
|
@ -145,6 +150,43 @@ const ListPage: React.FC = () => {
|
|||
dateFormatter="number"
|
||||
footer={() => `总计: ${total}`}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
const { dateRange, param } = formRef.current?.getFieldsValue();
|
||||
const [startDate, endDate] = dateRange.values();
|
||||
const { data, success } = await ordercontrollerGetordersales({
|
||||
startDate: dayjs(startDate).valueOf(),
|
||||
endDate: dayjs(endDate).valueOf(),
|
||||
...param,
|
||||
current: 1,
|
||||
pageSize: 20000,
|
||||
});
|
||||
if (!success) return;
|
||||
// 表头
|
||||
const headers = ['产品名', '数量'];
|
||||
|
||||
// 数据行
|
||||
const rows = (data?.items || []).map((item) => {
|
||||
return [item.name, item.totalQuantity];
|
||||
});
|
||||
|
||||
// 导出
|
||||
const sheet = XLSX.utils.aoa_to_sheet([headers, ...rows]);
|
||||
const book = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(book, sheet, '销售');
|
||||
const buffer = XLSX.write(book, {
|
||||
bookType: 'xlsx',
|
||||
type: 'array',
|
||||
});
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/octet-stream',
|
||||
});
|
||||
saveAs(blob, '销售.xlsx');
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>,
|
||||
<ProFormSwitch
|
||||
label="原产品"
|
||||
fieldProps={{
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import {
|
|||
ProColumns,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { App } from 'antd';
|
||||
import { App, Button } from 'antd';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
const ListPage: React.FC = () => {
|
||||
const { message } = App.useApp();
|
||||
|
|
@ -25,6 +27,11 @@ const ListPage: React.FC = () => {
|
|||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
},
|
||||
{
|
||||
title: '中文名',
|
||||
dataIndex: 'productNameCn',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: 'SKU',
|
||||
dataIndex: 'productSku',
|
||||
|
|
@ -76,7 +83,44 @@ const ListPage: React.FC = () => {
|
|||
};
|
||||
}}
|
||||
columns={columns}
|
||||
// toolBarRender={() => [<CreateForm tableRef={actionRef} />]}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
const { data, success } = await stockcontrollerGetstocks({
|
||||
current: 1,
|
||||
pageSize: 20000,
|
||||
});
|
||||
if (!success) return;
|
||||
// 表头
|
||||
const headers = ['产品名', 'SKU', ...points.map((p) => p.name)];
|
||||
|
||||
// 数据行
|
||||
const rows = (data?.items || []).map((item) => {
|
||||
const stockMap = new Map(
|
||||
item.stockPoint.map((sp) => [sp.id, sp.quantity]),
|
||||
);
|
||||
const stockRow = points.map((p) => stockMap.get(p.id) || 0);
|
||||
return [item.productName, item.productSku, ...stockRow];
|
||||
});
|
||||
|
||||
// 导出
|
||||
const sheet = XLSX.utils.aoa_to_sheet([headers, ...rows]);
|
||||
const book = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(book, sheet, '库存');
|
||||
const buffer = XLSX.write(book, {
|
||||
bookType: 'xlsx',
|
||||
type: 'array',
|
||||
});
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/octet-stream',
|
||||
});
|
||||
saveAs(blob, '库存.xlsx');
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const PurchaseOrderPage: React.FC = () => {
|
|||
title: '数量',
|
||||
hideInSearch: true,
|
||||
render(_, record) {
|
||||
if (!record.items) return 0;
|
||||
return record.items.reduce((cur, next) => {
|
||||
return cur + next.quantity;
|
||||
}, 0);
|
||||
|
|
@ -187,7 +188,7 @@ const CreateForm: React.FC<{
|
|||
autoFocusFirstInput
|
||||
layout="vertical"
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -267,7 +268,7 @@ const CreateForm: React.FC<{
|
|||
<div key={idx}>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
if (keyWords.length < 2) return [];
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -275,7 +276,7 @@ const CreateForm: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item.sku,
|
||||
};
|
||||
}) || []
|
||||
|
|
@ -365,7 +366,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -450,7 +451,7 @@ const UpdateForm: React.FC<{
|
|||
<div key={idx}>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
if (keyWords.length < 2) return [];
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -458,7 +459,7 @@ const UpdateForm: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item.sku,
|
||||
};
|
||||
}) || []
|
||||
|
|
@ -544,7 +545,7 @@ const DetailForm: React.FC<{
|
|||
trigger={<Button type="primary">详情</Button>}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
readonly={true}
|
||||
layout="vertical"
|
||||
|
|
@ -614,7 +615,7 @@ const DetailForm: React.FC<{
|
|||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
if (keyWords.length < 2) return [];
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -622,7 +623,7 @@ const DetailForm: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item.sku,
|
||||
};
|
||||
}) || []
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
stockcontrollerCanceltransfer,
|
||||
stockcontrollerCreatetransfer,
|
||||
stockcontrollerGetallstockpoints,
|
||||
stockcontrollerGetpurchaseorder,
|
||||
stockcontrollerGettransfers,
|
||||
stockcontrollerLosttransfer,
|
||||
stockcontrollerReceivetransfer,
|
||||
|
|
@ -60,7 +61,7 @@ const TransferPage: React.FC = () => {
|
|||
title: '数量',
|
||||
hideInSearch: true,
|
||||
render(_, record) {
|
||||
return record.items.reduce((cur, next) => {
|
||||
return record?.items?.reduce?.((cur, next) => {
|
||||
return cur + next.quantity;
|
||||
}, 0);
|
||||
},
|
||||
|
|
@ -204,11 +205,10 @@ const CreateForm: React.FC<{
|
|||
autoFocusFirstInput
|
||||
layout="vertical"
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
onFinish={async ({orderNumber,...values}) => {
|
||||
try {
|
||||
console.log(values);
|
||||
const { success, message: errMsg } =
|
||||
await stockcontrollerCreatetransfer(values);
|
||||
if (!success) {
|
||||
|
|
@ -272,9 +272,24 @@ const CreateForm: React.FC<{
|
|||
rules={[{ required: true, message: '请选择源目标仓库' }]}
|
||||
/>
|
||||
<ProFormTextArea name="note" label="备注" />
|
||||
<ProFormText name={'orderNumber'} addonAfter={<Button onClick={async () => {
|
||||
const orderNumber = await form.getFieldValue('orderNumber')
|
||||
const { data } = await stockcontrollerGetpurchaseorder({orderNumber})
|
||||
form.setFieldsValue({
|
||||
items: data?.map(
|
||||
(item: { productName: string; productSku: string }) => ({
|
||||
...item,
|
||||
productSku: {
|
||||
label: item.productName,
|
||||
value: item.productSku,
|
||||
},
|
||||
}),
|
||||
),
|
||||
})
|
||||
}}>引用</Button>} />
|
||||
<ProFormDependency name={['items']}>
|
||||
{({ items }) => {
|
||||
return '数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0);
|
||||
return '数量:' + (items?.reduce?.((acc, cur) => acc + cur.quantity, 0)||0);
|
||||
}}
|
||||
</ProFormDependency>
|
||||
<ProFormList
|
||||
|
|
@ -297,7 +312,7 @@ const CreateForm: React.FC<{
|
|||
<div key={idx}>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
if (keyWords.length < 2) return [];
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -305,7 +320,7 @@ const CreateForm: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item.sku,
|
||||
};
|
||||
}) || []
|
||||
|
|
@ -382,7 +397,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -450,7 +465,7 @@ const UpdateForm: React.FC<{
|
|||
<ProFormTextArea name="note" label="备注" />
|
||||
<ProFormDependency name={['items']}>
|
||||
{({ items }) => {
|
||||
return '数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0);
|
||||
return '数量:' + items?.reduce?.((acc, cur) => acc + cur.quantity, 0);
|
||||
}}
|
||||
</ProFormDependency>
|
||||
<ProFormList
|
||||
|
|
@ -473,7 +488,7 @@ const UpdateForm: React.FC<{
|
|||
<div key={idx}>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
if (keyWords.length < 2) return [];
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -481,7 +496,7 @@ const UpdateForm: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item.sku,
|
||||
};
|
||||
}) || []
|
||||
|
|
@ -553,7 +568,7 @@ const DetailForm: React.FC<{
|
|||
trigger={<Button type="primary">详情</Button>}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
readonly={true}
|
||||
layout="vertical"
|
||||
|
|
@ -630,7 +645,7 @@ const DetailForm: React.FC<{
|
|||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
if (keyWords.length < 2) return [];
|
||||
try {
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
|
|
@ -638,7 +653,7 @@ const DetailForm: React.FC<{
|
|||
return (
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item.sku,
|
||||
};
|
||||
}) || []
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ const CreateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
@ -182,7 +182,7 @@ const UpdateForm: React.FC<{
|
|||
}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnClose: true,
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
} from '@/servers/api/logistics';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { PageContainer, ProFormSelect } from '@ant-design/pro-components';
|
||||
import { Col, Row } from 'antd';
|
||||
import { useState } from 'react';
|
||||
|
||||
const TrackPage: React.FC = () => {
|
||||
|
|
@ -19,21 +18,15 @@ const TrackPage: React.FC = () => {
|
|||
if (!keyWords || keyWords.length < 3) return [];
|
||||
const { data: trackList } =
|
||||
await logisticscontrollerGettrackingnumber({ number: keyWords });
|
||||
return trackList?.map(
|
||||
(v: {
|
||||
tracking_provider: string;
|
||||
primary_tracking_number: string;
|
||||
id: string;
|
||||
}) => {
|
||||
return trackList?.map((v) => {
|
||||
return {
|
||||
label: v.tracking_provider + ' ' + v.primary_tracking_number,
|
||||
label: v.siteName + ' ' + v.externalOrderId,
|
||||
value: v.id,
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
}}
|
||||
fieldProps={{
|
||||
prefix: '追踪号',
|
||||
prefix: '订单号',
|
||||
onChange(value: string) {
|
||||
setId(value);
|
||||
},
|
||||
|
|
@ -53,28 +46,22 @@ const TrackPage: React.FC = () => {
|
|||
),
|
||||
}}
|
||||
/>
|
||||
<Row>
|
||||
<Col span={24}>原订单</Col>
|
||||
<Col span={18}>产品</Col>
|
||||
<Col span={6}>数量</Col>
|
||||
{data?.orderItem?.map((v: any) => (
|
||||
<>
|
||||
<Col span={18}>{v.name}</Col>
|
||||
<Col span={6}>{v.quantity}</Col>
|
||||
</>
|
||||
<div>
|
||||
{data.map((item) => (
|
||||
<div>
|
||||
<h4>
|
||||
{item.name} * {item.quantity}
|
||||
</h4>
|
||||
<div style={{ paddingLeft: 20, color: 'blue' }}>
|
||||
{item.constitution.map((v) => (
|
||||
<div>
|
||||
{v.name} * {v.quantity * item.quantity}
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<Row style={{ marginTop: '30px' }}>
|
||||
<Col span={18}>产品</Col>
|
||||
<Col span={6}>数量</Col>
|
||||
{data?.shipmentItem?.map((v: any) => (
|
||||
<>
|
||||
<Col span={18}>{v.name}</Col>
|
||||
<Col span={6}>{v.quantity}</Col>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** 此处后端没有提供注释 GET /customer/list */
|
||||
export async function customercontrollerGetcustomerlist(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.customercontrollerGetcustomerlistParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/customer/list', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /customer/tag/add */
|
||||
export async function customercontrollerAddtag(
|
||||
body: API.CustomerTagDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.BooleanRes>('/customer/tag/add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 DELETE /customer/tag/del */
|
||||
export async function customercontrollerDeltag(
|
||||
body: API.CustomerTagDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.BooleanRes>('/customer/tag/del', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /customer/tags */
|
||||
export async function customercontrollerGettags(options?: {
|
||||
[key: string]: any;
|
||||
}) {
|
||||
return request<any>('/customer/tags', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as customer from './customer';
|
||||
import * as logistics from './logistics';
|
||||
import * as order from './order';
|
||||
import * as product from './product';
|
||||
|
|
@ -12,6 +13,7 @@ import * as user from './user';
|
|||
import * as webhook from './webhook';
|
||||
import * as wpProduct from './wpProduct';
|
||||
export default {
|
||||
customer,
|
||||
logistics,
|
||||
order,
|
||||
product,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,21 @@ export async function ordercontrollerCreateorder(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /order/order/pengding/items */
|
||||
export async function ordercontrollerPengdingitems(
|
||||
body: Record<string, any>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/order/order/pengding/items', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /order/order/refund/${param0} */
|
||||
export async function ordercontrollerRefundorder(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -327,3 +327,17 @@ export async function productcontrollerGetstrengthall(options?: {
|
|||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
|
||||
export async function productcontrollerUpdateproductnamecn(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.productcontrollerUpdateproductnamecnParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { nameCn: param0, id: param1, ...queryParams } = params;
|
||||
return request<API.ProductRes>(`/productupdateNameCn/${param1}/${param0}`, {
|
||||
method: 'PUT',
|
||||
params: { ...queryParams },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,21 @@ export async function statisticscontrollerGetcustomerorders(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /statistics/inactiveUsersByMonth */
|
||||
export async function statisticscontrollerGetinativeusersbymonth(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.statisticscontrollerGetinativeusersbymonthParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/statistics/inactiveUsersByMonth', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /statistics/order */
|
||||
export async function statisticscontrollerGetorderstatistics(
|
||||
body: API.OrderStatisticsParams,
|
||||
|
|
@ -62,6 +77,16 @@ export async function statisticscontrollerGetorderbyemail(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /statistics/orderSource */
|
||||
export async function statisticscontrollerGetordersorce(options?: {
|
||||
[key: string]: any;
|
||||
}) {
|
||||
return request<any>('/statistics/orderSource', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /statistics/restocking */
|
||||
export async function statisticscontrollerRestocking(
|
||||
body: Record<string, any>,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,20 @@ export async function stockcontrollerDelpurchaseorder(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /stock/purchase-order/${param0} */
|
||||
export async function stockcontrollerGetpurchaseorder(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.stockcontrollerGetpurchaseorderParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { orderNumber: param0, ...queryParams } = params;
|
||||
return request<API.BooleanRes>(`/stock/purchase-order/${param0}`, {
|
||||
method: 'GET',
|
||||
params: { ...queryParams },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 PUT /stock/receiveTransfer/${param0} */
|
||||
export async function stockcontrollerUpdatetransfer(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -67,10 +67,11 @@ declare namespace API {
|
|||
name: string;
|
||||
/** 产品描述 */
|
||||
description?: string;
|
||||
/** sku */
|
||||
sku?: string;
|
||||
/** 分类 ID */
|
||||
categoryId?: number;
|
||||
strengthId?: number;
|
||||
flavorsId?: number;
|
||||
humidity?: string;
|
||||
};
|
||||
|
||||
type CreatePurchaseOrderDTO = {
|
||||
|
|
@ -100,6 +101,22 @@ declare namespace API {
|
|||
unit?: string;
|
||||
};
|
||||
|
||||
type customercontrollerGetcustomerlistParams = {
|
||||
current?: string;
|
||||
pageSize?: string;
|
||||
email?: string;
|
||||
tags?: string;
|
||||
sorterKey?: string;
|
||||
sorterValue?: string;
|
||||
state?: string;
|
||||
first_purchase_date?: string;
|
||||
};
|
||||
|
||||
type CustomerTagDTO = {
|
||||
email?: string;
|
||||
tag?: string;
|
||||
};
|
||||
|
||||
type Date = {
|
||||
year?: string;
|
||||
month?: string;
|
||||
|
|
@ -552,12 +569,14 @@ declare namespace API {
|
|||
id: number;
|
||||
/** 产品名称 */
|
||||
name: string;
|
||||
nameCn?: string;
|
||||
/** 产品描述 */
|
||||
description?: string;
|
||||
/** 分类 ID */
|
||||
categoryId?: number;
|
||||
flavorsId?: number;
|
||||
strengthId?: number;
|
||||
humidity?: string;
|
||||
/** sku */
|
||||
sku?: string;
|
||||
/** 创建时间 */
|
||||
|
|
@ -658,6 +677,11 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type productcontrollerUpdateproductnamecnParams = {
|
||||
nameCn: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
type productcontrollerUpdateproductParams = {
|
||||
id: number;
|
||||
};
|
||||
|
|
@ -765,6 +789,17 @@ declare namespace API {
|
|||
name?: string;
|
||||
};
|
||||
|
||||
type QueryCustomerListDTO = {
|
||||
current?: string;
|
||||
pageSize?: string;
|
||||
email?: string;
|
||||
tags?: string;
|
||||
sorterKey?: string;
|
||||
sorterValue?: string;
|
||||
state?: string;
|
||||
first_purchase_date?: string;
|
||||
};
|
||||
|
||||
type QueryFlavorsDTO = {
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
|
|
@ -1027,6 +1062,10 @@ declare namespace API {
|
|||
sku?: string;
|
||||
};
|
||||
|
||||
type statisticscontrollerGetinativeusersbymonthParams = {
|
||||
month?: string;
|
||||
};
|
||||
|
||||
type stockcontrollerCanceltransferParams = {
|
||||
id: number;
|
||||
};
|
||||
|
|
@ -1039,6 +1078,10 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type stockcontrollerGetpurchaseorderParams = {
|
||||
orderNumber: string;
|
||||
};
|
||||
|
||||
type stockcontrollerGetpurchaseordersParams = {
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
|
|
@ -1261,10 +1304,11 @@ declare namespace API {
|
|||
name?: string;
|
||||
/** 产品描述 */
|
||||
description?: string;
|
||||
/** sku */
|
||||
sku?: string;
|
||||
/** 分类 ID */
|
||||
categoryId?: number;
|
||||
strengthId?: number;
|
||||
flavorsId?: number;
|
||||
humidity?: string;
|
||||
};
|
||||
|
||||
type UpdatePurchaseOrderDTO = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue