diff --git a/.umirc.ts b/.umirc.ts index 9073e10..9e23975 100644 --- a/.umirc.ts +++ b/.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', diff --git a/package.json b/package.json index 913c450..3b39c6f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/pages/Customer/List/index.tsx b/src/pages/Customer/List/index.tsx new file mode 100644 index 0000000..ab0b772 --- /dev/null +++ b/src/pages/Customer/List/index.tsx @@ -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(); + 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 ( + + {(record.tags || []).map((tag) => { + return ( + { + const { success, message: msg } = + await customercontrollerDeltag({ + email: record.email, + tag, + }); + return false; + }} + > + {tag} + + ); + })} + + ); + }, + }, + { + title: '操作', + dataIndex: 'option', + valueType: 'option', + render: (_, record) => { + return ( + + + + + ); + }, + }, + ]; + return ( + + { + 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} + /> + + ); +}; + +export const AddTag: React.FC<{ + email: string; + tags?: string[]; + tableRef: React.MutableRefObject; +}> = ({ email, tags, tableRef }) => { + const { message } = App.useApp(); + const [tagList, setTagList] = useState([]); + + return ( + 修改标签} + width={800} + modalProps={{ + destroyOnHidden: true, + }} + submitter={false} + > + { + 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); + }, + }} + > + + ); +}; + +export default ListPage; diff --git a/src/pages/Logistics/Address/index.tsx b/src/pages/Logistics/Address/index.tsx index 375703b..0df5cae 100644 --- a/src/pages/Logistics/Address/index.tsx +++ b/src/pages/Logistics/Address/index.tsx @@ -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; diff --git a/src/pages/Order/List/index.tsx b/src/pages/Order/List/index.tsx index 45d85df..7f751c4 100644 --- a/src/pages/Order/List/index.tsx +++ b/src/pages/Order/List/index.tsx @@ -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<{ 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<{ { - 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<{ { - 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<{ 选择地址} - modalProps={{ destroyOnClose: true }} + modalProps={{ destroyOnHidden: true }} onFinish={async () => { if (!selectedRow) { message.error('请选择地址'); diff --git a/src/pages/Order/PendingItems/index.tsx b/src/pages/Order/PendingItems/index.tsx new file mode 100644 index 0000000..fbb68b8 --- /dev/null +++ b/src/pages/Order/PendingItems/index.tsx @@ -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(); + const [points, setPoints] = useState([]); + useEffect(() => { + stockcontrollerGetallstockpoints().then(({ data }) => { + setPoints(data as API.StockPoint[]); + }); + }, []); + const columns: ProColumns[] = [ + { + title: '产品名称', + dataIndex: 'name', + hideInSearch: true, + }, + { + title: '数量', + dataIndex: 'quantity', + hideInSearch: true, + }, + { + title: '订单号', + dataIndex: 'numbers', + hideInSearch: true, + width: 800, + render: (_, record) => { + return record?.numbers?.join?.('、'); + }, + }, + ]; + + return ( + + + 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={() => [ + , + ]} + /> + + ); +}; + +export default ListPage; diff --git a/src/pages/Organiza/User/index.tsx b/src/pages/Organiza/User/index.tsx index 8a13c3d..16c3684 100644 --- a/src/pages/Organiza/User/index.tsx +++ b/src/pages/Organiza/User/index.tsx @@ -84,7 +84,7 @@ const CreateForm: React.FC<{ } autoFocusFirstInput drawerProps={{ - destroyOnClose: true, + destroyOnHidden: true, }} onFinish={async (values) => { try { diff --git a/src/pages/Product/Category/index.tsx b/src/pages/Product/Category/index.tsx index 38f04b7..c350a82 100644 --- a/src/pages/Product/Category/index.tsx +++ b/src/pages/Product/Category/index.tsx @@ -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 { diff --git a/src/pages/Product/Flavors/index.tsx b/src/pages/Product/Flavors/index.tsx index 40c8f7e..e2688f7 100644 --- a/src/pages/Product/Flavors/index.tsx +++ b/src/pages/Product/Flavors/index.tsx @@ -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 { diff --git a/src/pages/Product/List/index.tsx b/src/pages/Product/List/index.tsx index 6399b03..b7cf731 100644 --- a/src/pages/Product/List/index.tsx +++ b/src/pages/Product/List/index.tsx @@ -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; +}> = ({value,tableRef, id}) => { + const { message } = App.useApp(); + const [editable, setEditable] = React.useState(false); + if (!editable) return
setEditable(true)}>{value||'-'}
; + return { + 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(); @@ -30,6 +53,15 @@ const List: React.FC = () => { title: '名称', dataIndex: 'name', }, + { + title: '中文名', + dataIndex: 'nameCn', + render: (_, record) => { + return ( + + ) + }, + }, { title: '产品描述', dataIndex: 'description', @@ -116,7 +148,13 @@ const List: React.FC = () => { success, }; }} - columns={columns} + 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 { diff --git a/src/pages/Product/Strength/index.tsx b/src/pages/Product/Strength/index.tsx index d7eb539..6fbfd8e 100644 --- a/src/pages/Product/Strength/index.tsx +++ b/src/pages/Product/Strength/index.tsx @@ -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 { diff --git a/src/pages/Product/WpList/index.tsx b/src/pages/Product/WpList/index.tsx index c57bb13..a23dc3a 100644 --- a/src/pages/Product/WpList/index.tsx +++ b/src/pages/Product/WpList/index.tsx @@ -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 { diff --git a/src/pages/Statistics/Order/index.tsx b/src/pages/Statistics/Order/index.tsx index fed66cc..1e86906 100644 --- a/src/pages/Statistics/Order/index.tsx +++ b/src/pages/Statistics/Order/index.tsx @@ -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 `${item.marker} ${item.seriesName}: ${item.value}`; }); @@ -53,7 +57,13 @@ const ListPage: React.FC = () => { rows.push(items.slice(i, i + 4).join('')); } - return rows.join('
'); + return ( + `
${xValue} ${ + ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][ + dayjs(xValue).day() + ] + }
` + rows.join('
') + ); }, }, 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(); 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 ( + + {(record.tags || []).map((tag) => { + return ( + { + const { success, message: msg } = + await customercontrollerDeltag({ + email: record.email, + tag, + }); + return false; + }} + > + {tag} + + ); + })} + + ); + }, + }, { title: '操作', dataIndex: 'action', valueType: 'option', render: (_, record) => { - return ; + return ( + + + + + ); }, }, ]} @@ -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; +}> = ({ email, tags, tableRef }) => { + const actionRef = useRef(); return ( 历史订单} - modalProps={{ destroyOnClose: true, footer: null }} + modalProps={{ destroyOnHidden: true, footer: null }} width="80vw" + submitter={false} > { + return ( + + {(record.tags || []).map((tag) => { + return ( + { + const { success, message: msg } = + await customercontrollerDeltag({ + email, + tag, + }); + return false; + }} + > + {tag} + + ); + })} + + ); + }, + }, ]} request={async () => { const { data, success } = await statisticscontrollerGetorderbyemail({ @@ -879,6 +991,9 @@ const HistoryOrder: React.FC<{ success, }; }} + toolBarRender={() => [ + , + ]} /> ); diff --git a/src/pages/Statistics/OrderSource/index.tsx b/src/pages/Statistics/OrderSource/index.tsx new file mode 100644 index 0000000..c1eade0 --- /dev/null +++ b/src/pages/Statistics/OrderSource/index.tsx @@ -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([]) + const actionRef = useRef(); + 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 ( + + {(record.tags || []).map((tag) => { + return ( + { + const { success, message: msg } = + await customercontrollerDeltag({ + email: record.email, + tag, + }); + return false; + }} + > + {tag} + + ); + })} + + ); + }, + }, + { + title: '操作', + dataIndex: 'option', + valueType: 'option', + render: (_, record) => { + return ( + + ); + }, + }, + ]; + return( + + { + if (params.componentType === 'series') { + setTableData([]) + const {success, data} = await statisticscontrollerGetinativeusersbymonth({ + month: params.name + }) + if(success) setTableData(data) + } + }, + }} + /> + { + tableData?.length ? + + :<> + + } + + ) +} + +export default ListPage; \ No newline at end of file diff --git a/src/pages/Statistics/Sales/index.tsx b/src/pages/Statistics/Sales/index.tsx index 64ad8cb..acfcd1b 100644 --- a/src/pages/Statistics/Sales/index.tsx +++ b/src/pages/Statistics/Sales/index.tsx @@ -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(); + 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 = () => { { dateFormatter="number" footer={() => `总计: ${total}`} toolBarRender={() => [ + , { 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={() => []} + toolBarRender={() => [ + , + ]} /> ); diff --git a/src/pages/Stock/PurchaseOrder/index.tsx b/src/pages/Stock/PurchaseOrder/index.tsx index 79b54ef..08deb46 100644 --- a/src/pages/Stock/PurchaseOrder/index.tsx +++ b/src/pages/Stock/PurchaseOrder/index.tsx @@ -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<{
{ - 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<{
{ - 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={} autoFocusFirstInput drawerProps={{ - destroyOnClose: true, + destroyOnHidden: true, }} readonly={true} layout="vertical" @@ -614,7 +615,7 @@ const DetailForm: React.FC<{ { - 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, }; }) || [] diff --git a/src/pages/Stock/Transfer/index.tsx b/src/pages/Stock/Transfer/index.tsx index 5b19845..d7f9cf4 100644 --- a/src/pages/Stock/Transfer/index.tsx +++ b/src/pages/Stock/Transfer/index.tsx @@ -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: '请选择源目标仓库' }]} /> + { + 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, + }, + }), + ), + }) + }}>引用} /> {({ items }) => { - return '数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0); + return '数量:' + (items?.reduce?.((acc, cur) => acc + cur.quantity, 0)||0); }} { - 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<{ {({ items }) => { - return '数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0); + return '数量:' + items?.reduce?.((acc, cur) => acc + cur.quantity, 0); }} { - 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={} autoFocusFirstInput drawerProps={{ - destroyOnClose: true, + destroyOnHidden: true, }} readonly={true} layout="vertical" @@ -630,7 +645,7 @@ const DetailForm: React.FC<{ { - 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, }; }) || [] diff --git a/src/pages/Stock/Warehouse/index.tsx b/src/pages/Stock/Warehouse/index.tsx index 544334d..7bd4d2e 100644 --- a/src/pages/Stock/Warehouse/index.tsx +++ b/src/pages/Stock/Warehouse/index.tsx @@ -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 { diff --git a/src/pages/Track/index.tsx b/src/pages/Track/index.tsx index be6b401..aad3e4a 100644 --- a/src/pages/Track/index.tsx +++ b/src/pages/Track/index.tsx @@ -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 { - label: v.tracking_provider + ' ' + v.primary_tracking_number, - value: v.id, - }; - }, - ); + return trackList?.map((v) => { + return { + label: v.siteName + ' ' + v.externalOrderId, + value: v.id, + }; + }); }} fieldProps={{ - prefix: '追踪号', + prefix: '订单号', onChange(value: string) { setId(value); }, @@ -53,28 +46,22 @@ const TrackPage: React.FC = () => { ), }} /> - - 原订单 - 产品 - 数量 - {data?.orderItem?.map((v: any) => ( - <> - {v.name} - {v.quantity} - +
+ {data.map((item) => ( +
+

+ {item.name} * {item.quantity} +

+
+ {item.constitution.map((v) => ( +
+ {v.name} * {v.quantity * item.quantity} +
+ ))} +
+
))} - - - - 产品 - 数量 - {data?.shipmentItem?.map((v: any) => ( - <> - {v.name} - {v.quantity} - - ))} - +
); }; diff --git a/src/servers/api/customer.ts b/src/servers/api/customer.ts new file mode 100644 index 0000000..bc68dbe --- /dev/null +++ b/src/servers/api/customer.ts @@ -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('/customer/list', { + method: 'GET', + params: { + ...params, + }, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /customer/tag/add */ +export async function customercontrollerAddtag( + body: API.CustomerTagDTO, + options?: { [key: string]: any }, +) { + return request('/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('/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('/customer/tags', { + method: 'GET', + ...(options || {}), + }); +} diff --git a/src/servers/api/index.ts b/src/servers/api/index.ts index 800eb60..4737f7c 100644 --- a/src/servers/api/index.ts +++ b/src/servers/api/index.ts @@ -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, diff --git a/src/servers/api/order.ts b/src/servers/api/order.ts index 5f9efb5..2a393e3 100644 --- a/src/servers/api/order.ts +++ b/src/servers/api/order.ts @@ -133,6 +133,21 @@ export async function ordercontrollerCreateorder( }); } +/** 此处后端没有提供注释 POST /order/order/pengding/items */ +export async function ordercontrollerPengdingitems( + body: Record, + options?: { [key: string]: any }, +) { + return request('/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默认没有生成对象) diff --git a/src/servers/api/product.ts b/src/servers/api/product.ts index 1ca3032..706559b 100644 --- a/src/servers/api/product.ts +++ b/src/servers/api/product.ts @@ -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(`/productupdateNameCn/${param1}/${param0}`, { + method: 'PUT', + params: { ...queryParams }, + ...(options || {}), + }); +} diff --git a/src/servers/api/statistics.ts b/src/servers/api/statistics.ts index 17884ae..5e3990a 100644 --- a/src/servers/api/statistics.ts +++ b/src/servers/api/statistics.ts @@ -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('/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('/statistics/orderSource', { + method: 'GET', + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /statistics/restocking */ export async function statisticscontrollerRestocking( body: Record, diff --git a/src/servers/api/stock.ts b/src/servers/api/stock.ts index dcee54e..8f7d5eb 100644 --- a/src/servers/api/stock.ts +++ b/src/servers/api/stock.ts @@ -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(`/stock/purchase-order/${param0}`, { + method: 'GET', + params: { ...queryParams }, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 PUT /stock/receiveTransfer/${param0} */ export async function stockcontrollerUpdatetransfer( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) diff --git a/src/servers/api/typings.d.ts b/src/servers/api/typings.d.ts index 2b78cad..00f3bb1 100644 --- a/src/servers/api/typings.d.ts +++ b/src/servers/api/typings.d.ts @@ -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 = {