diff --git a/package-lock.json b/package-lock.json index 6e6b453..05a3e59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "haina-dashboard", - "version": "2.13.2", + "version": "2.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "haina-dashboard", - "version": "2.13.2", + "version": "2.14.0", "dependencies": { "@ant-design/charts": "^1.4.2", "@ant-design/pro-components": "^2.6.16", diff --git a/package.json b/package.json index 77b2e4d..e4d31f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "haina-dashboard", - "version": "2.13.2", + "version": "2.14.0", "private": true, "dependencies": { "@ant-design/charts": "^1.4.2", diff --git a/src/App.jsx b/src/App.jsx index bd561f8..7e0c7ad 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -58,6 +58,8 @@ import Hotel from './views/Hotel'; import HostCaseCount from './views/HostCaseCount'; import TrainsUpsell from './views/biz/reports/TrainsUpsell'; import HostCaseReport from './views/HostCaseReport'; +import ToBOrder from './views/toB/ToBOrder'; +import ToBOrderSub from './views/toB/ToBOrderSub'; const App = () => { const { Content, Footer, Sider, } = Layout; @@ -93,6 +95,11 @@ const App = () => { label: 订单数据, // icon: , }, + { + key: 'tob_orders', + label: 分销订单, + // icon: , + }, { key: 22, label: 仪表盘, @@ -105,11 +112,13 @@ const App = () => { ], }, { - key: 20, label: '商务市场', icon: , + key: 20, + label: '商务市场', + icon: , children: [ { key: 201, label: 订单数据 }, { key: 202, label: 火车票Upsell }, - ] + ], }, { key: 5, @@ -147,11 +156,8 @@ const App = () => { label: '财务', icon: , children: [ - { - key: 41, - label: 信用卡账单, - }, - { key: 42, label: 汇率 }, + // { key: 41, label: 信用卡账单 }, + // { key: 42, label: 汇率 }, { key: 'service_person_num', label: 服务人数 }, ], }, @@ -261,6 +267,8 @@ const App = () => { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/charts/Customer_care_regular.jsx b/src/charts/Customer_care_regular.jsx index 44fbdeb..1facca9 100644 --- a/src/charts/Customer_care_regular.jsx +++ b/src/charts/Customer_care_regular.jsx @@ -6,13 +6,24 @@ import { stores_Context } from '../config'; import { observer } from 'mobx-react'; import SearchForm from './../components/search/SearchForm'; import LineWithAvg from '../components/LineWithAvg'; -import { flow } from 'mobx'; -import { TableExportBtn } from '../components/Data'; +import { toJS } from 'mobx'; +import { TableExportBtn, RenderVSDataCell } from '../components/Data'; + +import useCustomerRelationsStore from '../zustand/CustomerRelations'; +import { useShallow } from 'zustand/shallow'; +import { fixTo2Decimals, isEmpty } from '@haina/utils-commons'; const Customer_care_regular = () => { - const { orders_store, date_picker_store, customer_store } = useContext(stores_Context); + const { date_picker_store, customer_store } = useContext(stores_Context); const regular_data = customer_store.regular_data; + const [loading, loading2, searchValues, searchValuesToSub] = useCustomerRelationsStore(useShallow((state) => [state.loading, state.loading2, state.searchValues, state.searchValuesToSub])); + const [setSearchValues] = useCustomerRelationsStore(useShallow((state) => [state.setSearchValues])); + + const [regular] = useCustomerRelationsStore(useShallow((state) => [state.regular])); + + const getRegularCustomer = useCustomerRelationsStore((state) => state.getRegularCustomer); + const columns = [ { title: '订单号', @@ -205,13 +216,14 @@ const Customer_care_regular = () => { return (
- + { }} onSubmit={async (_err, obj, form, str) => { customer_store.setSearchValues(obj, form, 'regular_data'); - regular_data.data_compare=[]; - if (obj.DateDiff1 && obj.DateDiff2){ - regular_data.isCompareLine=true; - regular_data.showCompareSum=true; - await customer_store.regular_customer_order(); - customer_store.regular_customer_order(false,true); - // customer_store.regular_customer_order(true,false,true); - // customer_store.regular_customer_order(true,true,true); - } - else{ - regular_data.isCompareLine=false; - regular_data.showCompareSum=false; - customer_store.regular_customer_order(); - // customer_store.regular_customer_order(true); - } + setSearchValues(obj, form); + getRegularCustomer({ ...obj, IsDetail: 0 }); + getRegularCustomer({ ...obj, IsDetail: 1 }); + + // regular_data.data_compare=[]; + // if (obj.DateDiff1 && obj.DateDiff2){ + // regular_data.isCompareLine=true; + // regular_data.showCompareSum=true; + // await customer_store.regular_customer_order(); + // customer_store.regular_customer_order(false,true); + // // customer_store.regular_customer_order(true,false,true); + // customer_store.regular_customer_order(true,true,true); + // } + // else{ + // regular_data.isCompareLine=false; + // regular_data.showCompareSum=false; + // customer_store.regular_customer_order(); + // customer_store.regular_customer_order(true); + // } }} /> @@ -247,8 +263,8 @@ const Customer_care_regular = () => { { title: () => ( <> 订单数{' '} - + @@ -268,10 +284,15 @@ const Customer_care_regular = () => { key: 'OrderNum', render: (text, record, index) => ( <> - {text}   - { - {index === 0 && regular_data.total_data_tips!=='' && } - } + + {index === 0 && regular.total_data_tips !== '' && ( + <> +    + + + + + )} ), }, @@ -279,46 +300,84 @@ const Customer_care_regular = () => { title: '订单数占比', dataIndex: 'OrderRate', key: 'OrderRate', - render: (text) => typeof text === 'number'?{parseFloat((text * 100).toFixed(2))}%:text, + render: (text, record) => ( + + ), }, { title: '订单数占比(市场)', dataIndex: 'OrderRate2', key: 'OrderRate2', - render: (text) => typeof text === 'number'?{parseFloat((text * 100).toFixed(2))}%:text, + render: (text, record) => ( + + ), }, { title: '成行数', dataIndex: 'SUCOrderNum', key: 'SUCOrderNum', + render: (text, record) => , }, { title: '成行率', dataIndex: 'SUCRate', key: 'SUCRate', - render: (text) => typeof text === 'number'?{Math.round(text * 100)}%:text, + render: (text, record) => ( + + ), }, { title: '毛利', dataIndex: 'ML', key: 'ML', + render: (text, record) => , }, { title: '毛利占比', dataIndex: 'OrderMLRate', key: 'OrderMLRate', - render: (text) => typeof text === 'number'?{parseFloat((text * 100).toFixed(2))}%:text, + render: (text, record) => ( + + ), }, { title: '毛利占比(市场)', dataIndex: 'OrderMLRate2', key: 'OrderMLRate2', - render: (text) => typeof text === 'number'?{parseFloat((text * 100).toFixed(2))}%:text, + render: (text, record) => ( + + ), }, { title: '人数(含成人+儿童)', dataIndex: 'PersonNum', key: 'PersonNum', + render: (text, record) => , }, ]} size="small" @@ -327,65 +386,38 @@ const Customer_care_regular = () => { /> - -
- <> - -
- 维护中, 暂不可用, 敬请期待 -
-
- - +
+
+ + - - - { - const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]); - writeFileXLSX(wb, '老客户.xlsx'); - }} - > - 导出下表 - - - -
record.COLI_ID} - /> - - + + + { + const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]); + writeFileXLSX(wb, '老客户.xlsx'); + }} + > + 导出下表 + + + +
record.COLI_ID} /> + + diff --git a/src/components/Data.jsx b/src/components/Data.jsx index bfd2d52..00c446c 100644 --- a/src/components/Data.jsx +++ b/src/components/Data.jsx @@ -36,8 +36,9 @@ export const VSTag = (props) => { * @property {string} dataSuffix */ export const VSDataTag = ({ diffPercent=0, diffData=0, data1=0, data2=0, dataSuffix='' }) => { - const _diffPercent = diffPercent || (data2) ? fixTo2Decimals((data1-data2)/data2*100) : 100; - const _diffData = diffData || isNaN(data2) ? fixTo2Decimals(data1-0) : (fixTo2Decimals(data1-data2)); + const _diffPercent = diffPercent || ((data2) ? fixTo2Decimals((data1-data2)/data2*100) : 100); + const _diffPercentSuffix = String(_diffPercent).includes('%') ? '' : '%'; + const _diffData = diffData || (isNaN(data2) ? fixTo2Decimals(data1-0) : (fixTo2Decimals(data1-data2))); const CaretIcon = parseInt(_diffPercent) < 0 ? CaretDownOutlined : CaretUpOutlined; const tagColor = parseInt(_diffPercent) < 0 ? 'gold' : 'lime'; // parseInt(_diffPercent) === 0 ? ( @@ -50,14 +51,30 @@ export const VSDataTag = ({ diffPercent=0, diffData=0, data1=0, data2=0, dataSuf {_diffData !== 0 && ( } color={tagColor}> - {_diffPercent} - %  {_diffData}{dataSuffix} + {_diffPercent}{_diffPercentSuffix}  {_diffData}{dataSuffix} )} ); }; +/** + * 表格中显示数据对比 + * + * @property {boolean | undefined} [showDiffData=false] + * @property {number} [diffPercent=0] + * @property {number} diffData + * @property {number} data1 + * @property {number} data2 + * @property {string} dataSuffix + */ +export const RenderVSDataCell = ({ showDiffData=false, data1, data2, dataSuffix = '', ...props }) => { + if (showDiffData) { + return ; + } + return
{data1}{dataSuffix}
; +}; + /** * 导出表格数据存为xlsx * @property label 文件名字 diff --git a/src/components/Pareto.jsx b/src/components/Pareto.jsx index febd65b..628d03d 100644 --- a/src/components/Pareto.jsx +++ b/src/components/Pareto.jsx @@ -58,7 +58,7 @@ const ParetoChart = ({ data, xField, yField, thresholds = { A: 80, B: 90 }, titl Cumulative: { // min: 0, label: { - formatter: (val) => `${val}%`, + formatter: (val) => `${val}`, }, }, }, diff --git a/src/utils/commons.js b/src/utils/commons.js index 7c5fffa..cb48e64 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -1,6 +1,11 @@ import { Tag } from "antd"; import { CaretUpOutlined, CaretDownOutlined } from "@ant-design/icons"; +/** + * 显示对比标签 + * @deprecated + * 使用组件: VSTag VSDataTag RenderVSDataCell + */ export function show_vs_tag(vs, vs_diff, data1, data2) { let tag = "-"; if (parseInt(vs) < 0) { diff --git a/src/views/AgentGroupCount.jsx b/src/views/AgentGroupCount.jsx index 999e297..2074f94 100644 --- a/src/views/AgentGroupCount.jsx +++ b/src/views/AgentGroupCount.jsx @@ -6,7 +6,7 @@ import { observer } from 'mobx-react'; import { toJS } from 'mobx'; import 'moment/locale/zh-cn'; import SearchForm from './../components/search/SearchForm'; -import { TableExportBtn, VSDataTag } from './../components/Data'; +import { TableExportBtn, RenderVSDataCell } from './../components/Data'; import useCustomerServicesStore from '../zustand/CustomerServices'; import { useShallow } from 'zustand/shallow'; import { fixTo2Decimals } from "@haina/utils-commons"; @@ -19,13 +19,6 @@ const TdCell = (tdprops) => { return
+ { + setSearchValues(obj, form); + getToBOrderCount(obj); + onTabChange(activeTab); + }} + /> + + + + + + + + + + + + + + onTabChange(active_key)} + items={[ + { + key: 'customer_types', + label: ( + + + 分销客户 + + ), + }, + ].map((ele) => { + return { + ...ele, + children: ( + <> +
; }; -const DataRenderCell = ({ data1, data2, dataSuffix = '', showDiffData }) => { - if (showDiffData) { - return ; - } - return
{data1}{dataSuffix}
; -}; - const AgentGroupCount = () => { const { customerServicesStore, date_picker_store } = useContext(stores_Context); const [loading, searchValues, setSearchValues, searchValuesToSub] = useCustomerServicesStore(useShallow((state) => [state.loading, state.searchValues, state.setSearchValues, state.searchValuesToSub])); @@ -53,9 +46,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'GroupCount'), children: [ { - title: , // total1.GroupCount, + title: , // total1.GroupCount, dataIndex: 'GroupCount', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -65,9 +58,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'PersonNum'), children: [ { - title: , // total1.PersonNum, + title: , // total1.PersonNum, dataIndex: 'PersonNum', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -77,9 +70,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'GroupDays'), children: [ { - title: , // total1.GroupDays, + title: , // total1.GroupDays, dataIndex: 'GroupDays', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -89,9 +82,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'totalcost'), children: [ { - title: , // total1.totalcost, + title: , // total1.totalcost, dataIndex: 'totalcost', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -101,9 +94,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'FKBTS'), children: [ { - title: , // total1.FKBTS, + title: , // total1.FKBTS, dataIndex: 'FKBTS', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -113,9 +106,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'DDZTS'), children: [ { - title: , // total1.DDZTS, + title: , // total1.DDZTS, dataIndex: 'DDZTS', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -125,9 +118,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'DDZCases'), children: [ { - title: , // total1.DDZCases, + title: , // total1.DDZCases, dataIndex: 'DDZCases', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -137,9 +130,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'DDZRate'), children: [ { - title: , // total1.DDZRate, + title: , // total1.DDZRate, dataIndex: 'DDZRate', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -149,9 +142,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'ZWHP'), children: [ { - title: , // total1.ZWHP, + title: , // total1.ZWHP, dataIndex: 'ZWHP', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -161,9 +154,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'ZWHPCases'), children: [ { - title: , // total1.ZWHPCases, + title: , // total1.ZWHPCases, dataIndex: 'ZWHPCases', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -173,9 +166,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'ZWHPRate'), // parseInt(a.ZWHPRate) - parseInt(b.ZWHPRate), children: [ { - title: , // total1.ZWHPRate, + title: , // total1.ZWHPRate, dataIndex: 'ZWHPRate', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -185,9 +178,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'BMS'), children: [ { - title: , // total1.BMS, + title: , // total1.BMS, dataIndex: 'BMS', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -197,9 +190,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'BMRate'), // parseInt(a.BMRate) - parseInt(b.BMRate), children: [ { - title: , // total1.BMRate, + title: , // total1.BMRate, dataIndex: 'BMRate', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -209,9 +202,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'TS'), children: [ { - title: , // total1.TS, + title: , // total1.TS, dataIndex: 'TS', - render: (text, r) => , + render: (text, r) => , }, ], }, @@ -221,9 +214,9 @@ const AgentGroupCount = () => { sorter: (a, b) => sorter(a, b, 'TSRate'), // parseInt(a.TSRate) - parseInt(b.TSRate), children: [ { - title: , // total1.TSRate, + title: , // total1.TSRate, dataIndex: 'TSRate', - render: (text, r) => , + render: (text, r) => , }, ], }, diff --git a/src/views/Orders.jsx b/src/views/Orders.jsx index 9224ce0..ed84861 100644 --- a/src/views/Orders.jsx +++ b/src/views/Orders.jsx @@ -7,12 +7,11 @@ import { observer } from "mobx-react"; import * as config from "../config"; import { NavLink } from "react-router-dom"; import * as comm from '@haina/utils-commons'; -import { show_vs_tag } from './../utils/commons'; -import { utils, writeFileXLSX } from "xlsx"; import DateGroupRadio from '../components/DateGroupRadio'; import SearchForm from './../components/search/SearchForm'; -import { TableExportBtn } from './../components/Data'; +import { TableExportBtn, RenderVSDataCell } from './../components/Data'; import ParetoChart from "../components/Pareto"; +import { toJS } from 'mobx'; class Orders extends Component { static contextType = stores_Context; @@ -24,229 +23,181 @@ class Orders extends Component { format_data(data) { const { date_picker_store, orders_store } = this.context; const result = { dataSource: [], columns: [] }; - if (!comm.emptyValue(data)) { - const ordercountTotal1 = data.ordercountTotal1; - const ordercountTotal2 = data.ordercountTotal2; - if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) { - // 有比较的数据 - result.columns = [ - { - title: '#', - fixed: 'left', - children: [ - { - title: ( - -
- {date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)} -
-
- {date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~{date_picker_store.end_date_cp.format(config.DATE_FORMAT)} -
-
- ), - titleX: `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)} vs ${date_picker_store.start_date_cp.format( - config.DATE_FORMAT - )}~${date_picker_store.end_date_cp.format(config.DATE_FORMAT)}`, - dataIndex: 'OrderType', - fixed: 'left', - render: (text, record) => {text}, - }, - ], - }, - { - title: '数量', - children: [ - { - title: show_vs_tag(ordercountTotal1.OrderCount_vs, ordercountTotal1.OrderCount_diff, ordercountTotal1.OrderCount, ordercountTotal2.OrderCount), - titleX: [ordercountTotal1.OrderCount, ordercountTotal2.OrderCount].join(' vs '), - dataIndex: 'OrderCount', - }, - ], - }, - { - title: '成交数', - children: [ - { - title: show_vs_tag(ordercountTotal1.CJCount_vs, ordercountTotal1.CJCount_diff, ordercountTotal1.CJCount, ordercountTotal2.CJCount), - titleX: [ordercountTotal1.CJCount, ordercountTotal2.CJCount].join(' vs '), - dataIndex: 'CJCount', - }, - ], - }, - { - title: '成交人数', - children: [ - { - title: show_vs_tag(ordercountTotal1.CJPersonNum_vs, ordercountTotal1.CJPersonNum_diff, ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum), - titleX: [ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum].join(' vs '), - dataIndex: 'CJPersonNum', - }, - ], - }, - { - title: '成交率', - children: [ - { - title: show_vs_tag(ordercountTotal1.CJrate_vs, ordercountTotal1.CJrate_diff, ordercountTotal1.CJrate, ordercountTotal2.CJrate), - titleX: [ordercountTotal1.CJrate, ordercountTotal2.CJrate].join(' vs '), - dataIndex: 'CJrate', - }, - ], - }, - { - title: '成交毛利(预计)', - children: [ - { - title: show_vs_tag(ordercountTotal1.YJLY_vs, ordercountTotal1.YJLY_diff, ordercountTotal1.YJLY, ordercountTotal2.YJLY), - titleX: [ordercountTotal1.YJLY, ordercountTotal2.YJLY].join(' vs '), - dataIndex: 'YJLY', - }, - ], - }, + if (!comm.isEmpty(data)) { + const rows1Map = data.ordercount1.reduce((a, row1) => ({ ...a, [row1.OrderTypeSN]: row1 }), {}); + const rows2Map = data.ordercount2.reduce((a, row2) => ({ ...a, [row2.OrderTypeSN]: row2 }), {}); + // Diff: elements in rows2 but not in rows1 + const diffKey = [...new Set(Object.keys(rows2Map).filter((x) => !new Set(Object.keys(rows1Map)).has(x)))]; + const withDiff1 = (structuredClone(toJS(data.ordercount1)) || []).map((r1) => ({ ...r1, diff: rows2Map[r1.OrderTypeSN] })); + withDiff1.push( + ...diffKey.map((key) => ({ + diff: rows2Map[key], + EOI_ObjSN: rows2Map[key].EOI_ObjSN, + OrderType: rows2Map[key].OrderType, + CJrate: 0, + CJCount_vs: -100, + CJPersonNum_vs: -100, + CJrate_vs: -100, + YJLY_vs: -100, + OrderCount_vs: -100, + CJCount_diff: -rows2Map[key].CJCount, + CJPersonNum_diff: -rows2Map[key].CJPersonNum, + CJrate_diff: -rows2Map[key].CJrate, + YJLY_diff: -rows2Map[key].YJLY, + OrderCount_diff: -rows2Map[key].OrderCount, + Ordervalue: 0, + Ordervalue_diff: -rows2Map[key].Ordervalue, + Ordervalue_vs: -100, + })) + ); + const showDiff = date_picker_store.start_date_cp && date_picker_store.end_date_cp; + result.dataSource = withDiff1; - { - title: '单个订单价值', - children: [ - { - title: show_vs_tag(ordercountTotal1.Ordervalue_vs, ordercountTotal1.Ordervalue_diff, ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue), - titleX: [ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue].join(' vs '), - dataIndex: 'Ordervalue', - }, - ], - }, - ]; - // 1.找出两个数组OrderType相同的数据做比较显示 2.找出两组数据OrderType都不相同的数据做显示 - let has_same_type = false; // 数组1在数组2中是否找到相同的类型 - for (const item of data.ordercount1) { - has_same_type = false; - // 数组1在数组2中相同的类型 - for (const item2 of data.ordercount2) { - if (item.OrderType === item2.OrderType) { - has_same_type = true; - result.dataSource.push({ - key: item.key, - OrderType: item.OrderType, - OrderTypeSN: item.OrderTypeSN, - OrderCount: show_vs_tag(item.OrderCount_vs, item.OrderCount_diff, item.OrderCount, item2.OrderCount), - OrderCount_X: ([item.OrderCount, item2.OrderCount].join(' vs ')), - CJCount: show_vs_tag(item.CJCount_vs, item.CJCount_diff, item.CJCount, item2.CJCount), - CJCount_X: ([item.CJCount, item2.CJCount].join(' vs ')), - CJPersonNum: show_vs_tag(item.CJPersonNum_vs, item.CJPersonNum_diff, item.CJPersonNum, item2.CJPersonNum), - CJPersonNum_X: ([item.CJPersonNum, item2.CJPersonNum].join(' vs ')), - CJrate: show_vs_tag(item.CJrate_vs, item.CJrate_diff, item.CJrate, item2.CJrate), - CJrate_X: ([item.CJrate, item2.CJrate].join(' vs ')), - YJLY: show_vs_tag(item.YJLY_vs, item.YJLY_diff, item.YJLY, item2.YJLY), - YJLY_X: ([item.YJLY, item2.YJLY].join(' vs ')), - Ordervalue: show_vs_tag(item.Ordervalue_vs, item.Ordervalue_diff, item.Ordervalue, item2.Ordervalue), - Ordervalue_X: ([item.Ordervalue, item2.Ordervalue].join(' vs ')), - }); - } - } - // 数组1中不在数组2的类型 - if (has_same_type === false) { - result.dataSource.push({ - key: item.key, - OrderType: item.OrderType, - OrderTypeSN: item.OrderTypeSN, - OrderCount: show_vs_tag(comm.formatPercent(item.OrderCount), item.OrderCount, item.OrderCount, 0), - OrderCount_X: ([item.OrderCount, 0].join(' vs ')), - CJCount: show_vs_tag(comm.formatPercent(item.CJCount), item.CJCount, item.CJCount, 0), - CJCount_X: ([item.CJCount, 0].join(' vs ')), - CJPersonNum: show_vs_tag(comm.formatPercent(item.CJPersonNum), item.CJPersonNum, item.CJPersonNum, 0), - CJPersonNum_X: ([item.CJPersonNum, 0].join(' vs ')), - CJrate: show_vs_tag(item.CJrate, item.CJrate, item.CJrate, 0), - CJrate_X: ([item.CJrate, 0].join(' vs ')), - YJLY: show_vs_tag(comm.formatPercent(item.YJLY), item.YJLY, item.YJLY, 0), - YJLY_X: ([item.YJLY, 0].join(' vs ')), - Ordervalue: show_vs_tag(comm.formatPercent(item.Ordervalue), item.Ordervalue, item.Ordervalue, 0), - Ordervalue_X: ([item.Ordervalue, 0].join(' vs ')), - }); - } - } - // 数组2中不在数组1的类型 - for (const item2 of data.ordercount2) { - has_same_type = false; - for (const item of data.ordercount1) { - if (item.OrderType === item2.OrderType) { - has_same_type = true; - } - } - if (has_same_type === false) { - result.dataSource.push({ - key: item2.key, - OrderType: item2.OrderType, - OrderTypeSN: item2.OrderTypeSN, - OrderCount: show_vs_tag(comm.formatPercent(-item2.OrderCount), -item2.OrderCount, 0, item2.OrderCount), - OrderCount_X: ([ 0, item2.OrderCount].join(' vs ')), - CJCount: show_vs_tag(comm.formatPercent(-item2.CJCount), -item2.CJCount, 0, item2.CJCount), - CJCount_X: ([ 0, item2.CJCount].join(' vs ')), - CJPersonNum: show_vs_tag(comm.formatPercent(-item2.CJPersonNum), -item2.CJPersonNum, 0, item2.CJPersonNum), - CJPersonNum_X: ([0, item2.CJPersonNum].join(' vs ')), - CJrate: show_vs_tag(-item2.CJrate, -item2.CJrate, 0, item2.CJrate), - CJrate_X: ([ 0, item2.CJrate].join(' vs ')), - YJLY: show_vs_tag(comm.formatPercent(-item2.YJLY), -item2.YJLY, 0, item2.YJLY), - YJLY_X: ([0, item2.YJLY].join(' vs ')), - Ordervalue: show_vs_tag(comm.formatPercent(-item2.Ordervalue), -item2.Ordervalue, 0, item2.Ordervalue), - Ordervalue_X: ([ 0, item2.Ordervalue].join(' vs ')), - }); - } - } - } else { - result.columns = [ - { - title: "#", - fixed: 'left', - children: [ - { - title: ( - -
- {date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)} -
-
- ), - titleX: `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)}`, - fixed: 'left', - dataIndex: "OrderType", - render: (text, record) => {text}, - }, - ], - }, - { - title: "数量", - children: [{ title: ordercountTotal1.OrderCount, dataIndex: "OrderCount" }], - sorter: (a, b) => b.OrderCount - a.OrderCount, - }, - { - title: "成交数", - children: [{ title: ordercountTotal1.CJCount, dataIndex: "CJCount" }], - sorter: (a, b) => b.CJCount - a.CJCount, - }, - { - title: "成交人数", - children: [{ title: ordercountTotal1.CJPersonNum, dataIndex: "CJPersonNum" }], - sorter: (a, b) => b.CJPersonNum - a.CJPersonNum, - }, - { - title: "成交率", - children: [{ title: ordercountTotal1.CJrate, dataIndex: "CJrate" }], - sorter: (a, b) => parseInt(b.CJrate) - parseInt(a.CJrate), - }, - { - title: "成交毛利(预计)", - children: [{ title: ordercountTotal1.YJLY, dataIndex: "YJLY" }], - sorter: (a, b) => parseFloat(b.YJLY.replace(/,/g, "")) - parseFloat(a.YJLY.replace(/,/g, "")), - }, + const ordercountTotal1 = data.ordercountTotal1; + const ordercountTotal2 = data.ordercountTotal2; + result.columns = [ + { + title: '#', + fixed: 'left', + children: [ + { + title: ( + +
+ {date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)} +
+ {showDiff ?
+ {date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~{date_picker_store.end_date_cp.format(config.DATE_FORMAT)} +
: null} +
+ ), + titleX: showDiff ? `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)} vs ${date_picker_store.start_date_cp.format( + config.DATE_FORMAT + )}~${date_picker_store.end_date_cp.format(config.DATE_FORMAT)}` : `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)}`, + dataIndex: 'OrderType', + fixed: 'left', + render: (text, record) => {text}, + }, + ], + }, + { + title: '数量', + children: [ + { + title: ( + + ), + titleX: [ordercountTotal1.OrderCount, ordercountTotal2.OrderCount].join(' vs '), + dataIndex: 'OrderCount', + render: (text, r) => , + }, + ], + }, + { + title: '成交数', + children: [ + { + title: ( + + ), + titleX: [ordercountTotal1.CJCount, ordercountTotal2.CJCount].join(' vs '), + dataIndex: 'CJCount', + render: (text, r) => , + }, + ], + }, + { + title: '成交人数', + children: [ + { + title: ( + + ), + titleX: [ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum].join(' vs '), + dataIndex: 'CJPersonNum', + render: (text, r) => , + }, + ], + }, + { + title: '成交率', + children: [ + { + title: ( + + ), + titleX: [ordercountTotal1.CJrate, ordercountTotal2.CJrate].join(' vs '), + dataIndex: 'CJrate', + render: (text, r) => , + }, + ], + }, + { + title: '成交毛利(预计)', + children: [ + { + title: ( + + ), + titleX: [ordercountTotal1.YJLY, ordercountTotal2.YJLY].join(' vs '), + dataIndex: 'YJLY', + render: (text, r) => , + }, + ], + }, + + { + title: '单个订单价值', + children: [ + { + title: ( + + ), + titleX: [ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue].join(' vs '), + dataIndex: 'Ordervalue', + render: (text, r) => , + }, + ], + }, + ]; + return result; - { - title: "单个订单价值", - children: [{ title: ordercountTotal1.Ordervalue, dataIndex: "Ordervalue" }], - sorter: (a, b) => parseFloat(b.Ordervalue.replace(/,/g, "")) - parseFloat(a.Ordervalue.replace(/,/g, "")), - }, - ]; - result.dataSource = data.ordercount1; - } } return result; } @@ -256,10 +207,10 @@ class Orders extends Component { const table_data = orders_store.orderCountData_Form ? this.format_data(orders_store.orderCountData_Form) : []; const data_source = orders_store.orderCountData ? orders_store.orderCountData : []; const avg_line_y = Math.round(orders_store.avgLine1); - const pie_data = comm.emptyValue(orders_store.orderCountData_Form) + const pie_data = comm.isEmpty(orders_store.orderCountData_Form) ? [] : orders_store.orderCountData_Form.ordercount1.map((ele) => ({ ...ele, YJLYx: comm.price_to_number(ele.YJLY) })); // 饼图的显示 - const pie_data2 = comm.emptyValue(orders_store.orderCountData_Form) + const pie_data2 = comm.isEmpty(orders_store.orderCountData_Form) ? [] : orders_store.orderCountData_Form.ordercount2.map((ele) => ({ ...ele, YJLYx: comm.price_to_number(ele.YJLY) })); diff --git a/src/views/biz/BizOrder.jsx b/src/views/biz/BizOrder.jsx index 4edaaee..adfcf95 100644 --- a/src/views/biz/BizOrder.jsx +++ b/src/views/biz/BizOrder.jsx @@ -4,10 +4,10 @@ import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined, Custom import { Line, Pie } from '@ant-design/charts'; import { NavLink } from 'react-router-dom'; import * as comm from '@haina/utils-commons'; -import { show_vs_tag } from './../../utils/commons'; import DateGroupRadio from '../../components/DateGroupRadio'; import SearchForm from '../../components/search/SearchForm'; import { TableExportBtn } from '../../components/Data'; +import { RenderVSDataCell } from './../../components/Data'; import { observer } from 'mobx-react'; import { toJS } from 'mobx'; @@ -143,12 +143,18 @@ const BizOrder = observer(() => { title: '数量', children: [ { - title: !showDiff - ? result.ordercountTotal1?.OrderCount - : show_vs_tag(result.ordercountTotal1?.OrderCount_vs, result.ordercountTotal1?.OrderCount_diff, result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount), + title: ( + + ), titleX: [result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount].join(' vs '), dataIndex: 'OrderCount', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.OrderCount_vs, r.OrderCount_diff, r.OrderCount, r.diff?.OrderCount)), + render: (text, r) => , }, ], }, @@ -156,12 +162,18 @@ const BizOrder = observer(() => { title: '成交数', children: [ { - title: !showDiff - ? result.ordercountTotal1?.CJCount - : show_vs_tag(result.ordercountTotal1?.CJCount_vs, result.ordercountTotal1?.CJCount_diff, result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount), + title: ( + + ), titleX: [result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount].join(' vs '), dataIndex: 'CJCount', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.CJCount_vs, r.CJCount_diff, r.CJCount, r.diff?.CJCount)), + render: (text, r) => , }, ], }, @@ -169,12 +181,18 @@ const BizOrder = observer(() => { title: '成交人数', children: [ { - title: !showDiff - ? result.ordercountTotal1?.CJPersonNum - : show_vs_tag(result.ordercountTotal1?.CJPersonNum_vs, result.ordercountTotal1?.CJPersonNum_diff, result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum), + title: ( + + ), titleX: [result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum].join(' vs '), dataIndex: 'CJPersonNum', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.CJPersonNum_vs, r.CJPersonNum_diff, r.CJPersonNum, r.diff?.CJPersonNum)), + render: (text, r) => , }, ], }, @@ -182,12 +200,18 @@ const BizOrder = observer(() => { title: '成交率', children: [ { - title: !showDiff - ? result.ordercountTotal1?.CJrate - : show_vs_tag(result.ordercountTotal1?.CJrate_vs, result.ordercountTotal1?.CJrate_diff, result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate), + title: ( + + ), titleX: [result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate].join(' vs '), dataIndex: 'CJrate', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.CJrate_vs, r.CJrate_diff, r.CJrate, r.diff?.CJrate)), + render: (text, r) => , }, ], }, @@ -195,12 +219,18 @@ const BizOrder = observer(() => { title: '成交毛利(预计)', children: [ { - title: !showDiff - ? result.ordercountTotal1?.YJLY - : show_vs_tag(result.ordercountTotal1?.YJLY_vs, result.ordercountTotal1?.YJLY_diff, result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY), + title: ( + + ), titleX: [result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY].join(' vs '), dataIndex: 'YJLY', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.YJLY_vs, r.YJLY_diff, r.YJLY, r.diff?.YJLY)), + render: (text, r) => , }, ], }, @@ -209,12 +239,18 @@ const BizOrder = observer(() => { title: '单个订单价值', children: [ { - title: !showDiff - ? result.ordercountTotal1?.Ordervalue - : show_vs_tag(result.ordercountTotal1?.Ordervalue_vs, result.ordercountTotal1?.Ordervalue_diff, result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue), + title: ( + + ), titleX: [result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue].join(' vs '), dataIndex: 'Ordervalue', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.Ordervalue_vs, r.Ordervalue_diff, r.Ordervalue, r.diff?.Ordervalue)), + render: (text, r) => , }, ], }, diff --git a/src/views/biz/reports/TrainsUpsell.jsx b/src/views/biz/reports/TrainsUpsell.jsx index 13e7ac4..235a2a9 100644 --- a/src/views/biz/reports/TrainsUpsell.jsx +++ b/src/views/biz/reports/TrainsUpsell.jsx @@ -3,8 +3,8 @@ import { Row, Col, Table, Spin, Space, Divider } from 'antd'; import { Funnel, Pie, Sunburst } from '@ant-design/charts'; import { isEmpty } from '@haina/utils-commons'; -import { show_vs_tag } from '../../../utils/commons'; import SearchForm from '../../../components/search/SearchForm'; +import { RenderVSDataCell } from './../../../components/Data'; import { observer } from 'mobx-react'; import { toJS } from 'mobx'; @@ -168,33 +168,33 @@ const TrainsUpsell = observer(({ ...props }) => { { title: '数量', dataIndex: 'OrderCount', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.OrderCount_vs, r.OrderCount_diff, r.OrderCount, r.diff?.OrderCount)), + render: (text, r) => , }, { title: '成交数', dataIndex: 'CJCount', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.CJCount_vs, r.CJCount_diff, r.CJCount, r.diff?.CJCount)), + render: (text, r) => , }, { title: '成交人数', dataIndex: 'CJPersonNum', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.CJPersonNum_vs, r.CJPersonNum_diff, r.CJPersonNum, r.diff?.CJPersonNum)), + render: (text, r) => , }, { title: '成交率', dataIndex: 'CJrate', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.CJrate_vs, r.CJrate_diff, r.CJrate, r.diff?.CJrate)), + render: (text, r) => , }, { title: '成交毛利(预计)', dataIndex: 'YJLY', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.YJLY_vs, r.YJLY_diff, r.YJLY, r.diff?.YJLY)), + render: (text, r) => , }, { title: '单个订单价值', dataIndex: 'Ordervalue', - render: (text, r) => (!showDiff ? text : show_vs_tag(r.Ordervalue_vs, r.Ordervalue_diff, r.Ordervalue, r.diff?.Ordervalue)), + render: (text, r) => , }, ]; return ( diff --git a/src/views/toB/ToBOrder.jsx b/src/views/toB/ToBOrder.jsx new file mode 100644 index 0000000..27fcaf4 --- /dev/null +++ b/src/views/toB/ToBOrder.jsx @@ -0,0 +1,366 @@ +import { useContext } from 'react'; +import { Row, Col, Tabs, Table, Divider, Spin, Checkbox, Space } from 'antd'; +import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined, CustomerServiceOutlined, IeOutlined } from '@ant-design/icons'; +import { Line, Pie } from '@ant-design/charts'; +import { NavLink } from 'react-router-dom'; +import * as comm from '@haina/utils-commons'; +import DateGroupRadio from '../../components/DateGroupRadio'; +import SearchForm from '../../components/search/SearchForm'; +import { TableExportBtn } from '../../components/Data'; +import { RenderVSDataCell } from './../../components/Data'; + +import { observer } from 'mobx-react'; +import { toJS } from 'mobx'; +import { stores_Context } from '../../config'; +import { useShallow } from 'zustand/shallow'; +import useToBOrderStore, { orderCountDataMapper, orderCountDataFieldMapper } from '../../zustand/ToBOrder'; + + +const ToBOrder = observer(() => { + const { date_picker_store: searchFormStore } = useContext(stores_Context); + + const [searchValues, setSearchValues] = useToBOrderStore(useShallow((state) => [state.searchValues, state.setSearchValues])); + const [activeTab, setActiveTab] = useToBOrderStore(useShallow((state) => [state.activeTab, state.setActiveTab])); + const [loading, typeLoading, onTabChange] = useToBOrderStore(useShallow((state) => [state.loading, state.typeLoading,state.onTabChange])); + + const orderCountDataRaw = useToBOrderStore((state) => state.orderCountDataRaw); + const [orderCountDataLines, avgLineValue] = useToBOrderStore(useShallow((state) => [state.orderCountDataLines, state.avgLineValue])); + const [onChangeDateGroup, activeDateGroupRadio] = useToBOrderStore(useShallow((state) => [state.onChangeDateGroup, state.activeDateGroupRadio])); + + const orderCountDataByType = useToBOrderStore((state) => state.orderCountDataByType); + const result = orderCountDataByType[activeTab] || {}; + + const getToBOrderCount = useToBOrderStore((state) => state.getToBOrderCount); + + const showDiff = !comm.isEmpty(searchFormStore.start_date_cp); + + const avg_line_y = Math.round(avgLineValue); + const lineConfig = { + data: orderCountDataLines, + padding: 'auto', + xField: 'xField', + yField: 'yField', + seriesField: 'seriesField', + // xAxis: { + // type: "timeCat", + // }, + point: { + size: 4, + shape: 'cicle', + }, + annotations: [ + { + type: 'text', + position: ['start', avg_line_y], + content: avg_line_y, + offsetX: -15, + style: { + fill: '#F4664A', + textBaseline: 'bottom', + }, + }, + { + type: 'line', + start: [-10, avg_line_y], + end: ['max', avg_line_y], + style: { + stroke: '#F4664A', + lineDash: [2, 2], + }, + }, + ], + label: {}, // 显示标签 + legend: { + itemValue: { + formatter: (text, item) => { + const items = orderCountDataLines.filter((d) => d.seriesField === item.value); // 按站点筛选 + return items.length ? items.reduce((a, b) => a + b.yField, 0) : ''; // 计算总数 + }, + }, + }, + tooltip: { + customItems: (originalItems) => { + // process originalItems, + return originalItems.map((ele) => ({ ...ele, name: ele.data?.seriesField || ele.data?.xField })); + }, + title: (title, datum) => { + let ret = title; + switch (activeDateGroupRadio) { + case 'day': + ret = `${title} ${comm.getWeek(datum.xField)}`; // 显示周几 + break; + + default: + break; + } + return ret; + }, + }, + // smooth: true, + }; + const pieConfig = { + appendPadding: 10, + data: [], + angleField: 'OrderCount', + colorField: 'OrderType', + radius: 0.8, + label: { + type: 'outer', + content: '{name} {value} \t {percentage}', + }, + legend: false, // 不显示图例 + interactions: [ + { + type: 'element-selected', + }, + { + type: 'element-active', + }, + ], + }; + + const tableProps = { + dataSource: result?.ordercount1 || [], // table_data.dataSource, + columns: [ + { + title: '#', + fixed: 'left', + children: [ + { + title: ( + +
{result.ordercountTotal1?.groups}
+ {showDiff ?
{result.ordercountTotal2?.groups}
: null} +
+ ), + titleX: `${result.ordercountTotal1?.groups}` + (showDiff ? ` vs ${result.ordercountTotal2?.groups}` : ''), + dataIndex: 'OrderType', + fixed: 'left', + render: (text, record) => {text}, + }, + ], + }, + { + title: '数量', + children: [ + { + title: ( + + ), + titleX: [result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount].join(' vs '), + dataIndex: 'OrderCount', + render: (text, r) => , + }, + ], + }, + { + title: '成交数', + children: [ + { + title: ( + + ), + titleX: [result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount].join(' vs '), + dataIndex: 'CJCount', + render: (text, r) => , + }, + ], + }, + { + title: '成交人数', + children: [ + { + title: ( + + ), + titleX: [result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum].join(' vs '), + dataIndex: 'CJPersonNum', + render: (text, r) => , + }, + ], + }, + { + title: '成交率', + children: [ + { + title: ( + + ), + titleX: [result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate].join(' vs '), + dataIndex: 'CJrate', + render: (text, r) => , + }, + ], + }, + { + title: '成交毛利(预计)', + children: [ + { + title: ( + + ), + titleX: [result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY].join(' vs '), + dataIndex: 'YJLY', + render: (text, r) => , + }, + ], + }, + + { + title: '单个订单价值', + children: [ + { + title: ( + + ), + titleX: [result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue].join(' vs '), + dataIndex: 'Ordervalue', + render: (text, r) => , + }, + ], + }, + ], + size: 'small', + pagination: false, + scroll: { x: 100 * 7 }, + loading, + }; + + return ( + <> +
+ +
+ + + + + ), + }; + })} + /> + + + +
+

各项占比

+ {/* setIsShowEmpty(e.target.checked)} + > + 包含空值 + */} +
+ + + + + + + + {showDiff && + + + } + + + + + ); +}); +export default ToBOrder; diff --git a/src/views/toB/ToBOrderSub.jsx b/src/views/toB/ToBOrderSub.jsx new file mode 100644 index 0000000..5a53590 --- /dev/null +++ b/src/views/toB/ToBOrderSub.jsx @@ -0,0 +1,280 @@ +import { useContext, useEffect } from 'react'; +import { Row, Col, Tabs, Table, Divider, Spin, Space } from 'antd'; +import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined } from '@ant-design/icons'; +import { Line } from '@ant-design/charts'; +import { NavLink, useParams } from 'react-router-dom'; +import { getWeek } from '@haina/utils-commons'; +import DateGroupRadio from '../../components/DateGroupRadio'; +import SearchForm from '../../components/search/SearchForm'; +import { TableExportBtn } from '../../components/Data'; + +import { observer } from 'mobx-react'; +import { stores_Context } from '../../config'; +import { useShallow } from 'zustand/shallow'; +import useToBOrderStore, { orderCountDataMapper, orderCountDataFieldMapper } from '../../zustand/ToBOrder'; + +// 在逗号和分号处自动换行的函数 +const addLineBreaksAtCommas = (text) => { + if (!text) return ''; + return text.replace(/&/g, '&').replace(/,/g, ',\n').replace(/,/g, ',\n').replace(/;/g, ';\n').replace(/;/g, ';\n'); +}; + +const OrderDetailTable = ({ caption, dataSource, loading, ...props }) => { + const columns = [ + { + title: '订单号', + dataIndex: 'COLI_ID', + key: 'COLI_ID', + }, + { + title: '网站', + dataIndex: 'COLI_WebCode', + key: 'COLI_WebCode', + }, + { + title: '成行', + dataIndex: 'COLI_Success', + key: 'COLI_Success', + render: (text, record) => {text == 1 ? '是' : '否'}, + sorter: (a, b) => b.COLI_Success - a.COLI_Success, + }, + // { + // title: "人数(成/童/婴)", + // dataIndex: "COLI_PersonNum", + // key: "COLI_PersonNum", + // render: (text, record) => ( + // + // {record.COLI_PersonNum}/{record.COLI_ChildNum}/{record.COLI_BabyNum} + // + // ), + // }, + { + title: '预计利润', + dataIndex: 'CGI_YJLY', + key: 'CGI_YJLY', + }, + { + title: '预定时间', + dataIndex: 'COLI_ApplyDate', + key: 'COLI_ApplyDate', + }, + { + title: '出发日期', + dataIndex: 'CGI_ArriveDate', + key: 'CGI_ArriveDate', + }, + // { + // title: '客人需求', + // dataIndex: 'COLI_CustomerRequest', + // key: 'COLI_CustomerRequest', + // ellipsis: true, + // }, + { + title: '订单内容', + dataIndex: 'COLI_OrderDetailText', + key: 'COLI_OrderDetailText', + ellipsis: true, + }, + Table.EXPAND_COLUMN, + ]; + return ( +
+ +
+
{caption}
+ +
+
+
record.key} + expandable={{ + expandedRowRender: (record) => ( +
+              {/* 
+                客户需求
+              
+              {record.COLI_CustomerRequest} */}
+              
+                订单内容
+              
+              {record.COLI_OrderDetailText}
+            
+ ), + }} + /> + + ); +}; + +const ToBOrderSub = observer(({ ...props }) => { + const { ordertype, ordertype_sub, ordertype_title } = useParams(); + const { date_picker_store: searchFormStore } = useContext(stores_Context); + + const [searchValues, setSearchValues] = useToBOrderStore(useShallow((state) => [state.searchValues, state.setSearchValues])); + const [searchValuesToSub] = useToBOrderStore(useShallow((state) => [state.searchValuesToSub])); + + const [loading, typeLoading] = useToBOrderStore(useShallow((state) => [state.loading, state.typeLoading])); + const orderCountDataRawSub = useToBOrderStore((state) => state.orderCountDataRawSub); + const [orderCountDataLinesSub, avgLineValueSub] = useToBOrderStore(useShallow((state) => [state.orderCountDataLinesSub, state.avgLineValueSub])); + const [onChangeDateGroupSub, activeDateGroupRadioSub] = useToBOrderStore(useShallow((state) => [state.onChangeDateGroupSub, state.activeDateGroupRadioSub])); + + const orderDetails = useToBOrderStore((state) => state.orderDetails); + + const getToBOrderCount = useToBOrderStore((state) => state.getToBOrderCount); + const getToBOrderDetailByType = useToBOrderStore((state) => state.getToBOrderDetailByType); + + useEffect(() => { + getToBOrderCount(searchValuesToSub, ordertype, ordertype_sub); + getToBOrderDetailByType(searchValuesToSub, ordertype, ordertype_sub); + }, []); + + const avg_line_y = Math.round(avgLineValueSub); + const lineConfig = { + data: orderCountDataLinesSub, + padding: 'auto', + xField: 'xField', + yField: 'yField', + seriesField: 'seriesField', + // xAxis: { + // type: "timeCat", + // }, + point: { + size: 4, + shape: 'cicle', + }, + annotations: [ + { + type: 'text', + position: ['start', avg_line_y], + content: avg_line_y, + offsetX: -15, + style: { + fill: '#F4664A', + textBaseline: 'bottom', + }, + }, + { + type: 'line', + start: [-10, avg_line_y], + end: ['max', avg_line_y], + style: { + stroke: '#F4664A', + lineDash: [2, 2], + }, + }, + ], + label: {}, // 显示标签 + legend: { + itemValue: { + formatter: (text, item) => { + const items = orderCountDataLinesSub.filter((d) => d.seriesField === item.value); // 按站点筛选 + return items.length ? items.reduce((a, b) => a + b.yField, 0) : ''; // 计算总数 + }, + }, + }, + tooltip: { + customItems: (originalItems) => { + // process originalItems, + return originalItems.map((ele) => ({ ...ele, name: ele.data?.seriesField || ele.data?.xField })); + }, + title: (title, datum) => { + let ret = title; + switch (activeDateGroupRadioSub) { + case 'day': + ret = `${title} ${getWeek(datum.xField)}`; // 显示周几 + break; + + default: + break; + } + return ret; + }, + }, + smooth: true, + }; + + const tab_items = [ + { + key: 'detail', + label: ( + + + 订单内容 + + ), + title: '订单内容', + children: ( + <> + + + + + + ), + }, + ]; + return ( +
+ +
+ 返回 + + + { + setSearchValues(obj, form); + getToBOrderCount(obj, ordertype, ordertype_sub); + getToBOrderDetailByType(obj, ordertype, ordertype_sub); + }} + /> + + + + + + + + + + + + + + + + + + + ); +}); +export default ToBOrderSub; diff --git a/src/zustand/CustomerRelations.js b/src/zustand/CustomerRelations.js new file mode 100644 index 0000000..5cc99ce --- /dev/null +++ b/src/zustand/CustomerRelations.js @@ -0,0 +1,172 @@ +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; +import { fetchJSON } from '@haina/utils-request'; +import { HT_HOST } from '../config'; +import { formatDate, isEmpty, sortBy } from '@haina/utils-commons'; +import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht'; + +const defaultParams = {}; + +export const fetchRegularCustomer = async (params) => { + const _params = { + Website: params.WebCode || '', // CHT,AH,JH,GH,ZWQD,GH_ZWQD_HW,GHKYZG,GHKYHW,HTravel + DEI_SNList: params.DepartmentList || '', // 1,2,28,7 + ApplydateCheck: params.DateType === 'applyDate' ? 1 : 0, // + EntrancedateCheck: params.DateType === 'startDate' ? 1 : 0, // + ConfirmDateCheck: params.DateType === 'confirmDate' ? 1 : 0, // + ApplydateStart: params.Date1 || '', + ApplydateEnd: params.Date2 || '', + EntrancedateStart: params.Date1 || '', + EntrancedateEnd: params.Date2 || '', + ConfirmdateStart: params.Date1 || '', + ConfirmdateEnd: params.Date2 || '', + IsDetail: '', // + IncludeTickets: '', // + ...params, + }; + const { WebCode, DepartmentList, DateType, Date1, Date2, ...readyParams } = _params; + const [result1=[], result2=[]] = await Promise.all([ + fetchJSON(HT_HOST + '/service-tourdesign/RegularCusOrder', { ...defaultParams, ...readyParams }), + ...(params.DateDiff1 && params.IsDetail === 0 + ? [ + fetchJSON(HT_HOST + '/service-tourdesign/RegularCusOrder', { + ...defaultParams, + ...readyParams, + ApplydateStart: params.DateDiff1 || '', + ApplydateEnd: params.DateDiff2 || '', + EntrancedateStart: params.DateDiff1 || '', + EntrancedateEnd: params.DateDiff2 || '', + ConfirmdateStart: params.DateDiff1 || '', + ConfirmdateEnd: params.DateDiff2 || '', + }), + ] + : []), + ]); + if (params.IsDetail === 1) { + return { result1, result2 }; + } + const ret = {}; + const result1Mapped = result1.reduce((r, v) => ({ ...r, [v.ItemName]: v }), {}); + const allKeys = [...new Set([...result1.map((e) => e.ItemName), ...result2.map((e) => e.ItemName)])]; + const result2Mapped = result2.reduce((r, v) => ({ ...r, [v.ItemName]: v }), {}); + const x = {}; + allKeys.forEach((key) => { + x[key] = { ...(result1Mapped?.[key] || { ItemName: key }), diff: result2Mapped[key] || {} }; + }); + ret.result1 = Object.values(x); + return { result1: ret.result1 }; // { result1, result2 }; +}; + +// 老客户: 日期对应的数据字段 +const dateTypeDataHelper = { + applyDate: 'SumOrder', + startDate: 'ConfirmOrder', + confirmDate: 'ConfirmOrder', +}; + +/** + * 构建 老客户系列数据 + * * 用明细数据计算 + * @param {[]} details + * @param {string} pivotByOrder + * @param {string} pivotByDate + * @returns + */ +const buildSeriesDataFromDetails = (details, pivotByOrder, pivotByDate) => { + const dataDetail = (details || []).map((ele) => ({ + ...ele, + key: ele.COLI_ID, + orderState: ele.OrderState, + applyDate: formatDate(new Date(ele.COLI_ApplyDate)), + startDate: ele.COLI_OrderStartDate, + confirmDate: formatDate(new Date(ele.COLI_ConfirmDate)), + })); + const { data: IsOldData } = pivotBy( + dataDetail.filter((ele) => ele.COLI_IsOld === '是'), + [['COLI_IsOld'], [], pivotByDate] + ); + const { data: isCusCommendData } = pivotBy( + dataDetail.filter((ele) => ele.COLI_IsCusCommend === '是'), + [['COLI_IsCusCommend'], [], pivotByDate] + ); + // console.log('IsOldData====', IsOldData, '\nisCusCommend', isCusCommendData); + // 合并成两个系列 + const seriesData = [] + .concat( + IsOldData.map((ele) => ({ ...ele, _ylabel: '老客户' })), + isCusCommendData.map((ele) => ({ ...ele, _ylabel: '老客户推荐' })) + ) + .sort(sortBy(pivotByDate)); + return seriesData; +}; + +/** + * -------------------------------------------------------------------------------------------------------- + */ +const initialState = { + loading: false, + loading2: false, + searchValues: { + DepartmentList: ['1', '2', '28', '7', '33'].map((kk) => groupsMappedByKey[kk]), + WebCode: ['CHT', 'AH', 'JH', 'GH', 'ZWQD', 'GH_ZWQD_HW', 'GHKYZG', 'GHKYHW', 'HTravel'].map((kk) => sitesMappedByCode[kk]), + DateType: { key: 'applyDate', label: '提交日期' }, + IncludeTickets: { key: '0', label: '不含门票' }, + }, + searchValuesToSub: {}, + + regular: { data: [], details: [], total_data_tips: '', pivotData: [], pivotY: 'SumOrder', pivotX: '' }, +}; + +const useCustomerRelationsStore = create( + devtools( + immer((set, get) => ({ + ...initialState, + reset: () => set(initialState), + + setLoading: (loading) => set({ loading }), + setLoading2: (loading2) => set({ loading2 }), + setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })), + setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })), + + // 获取数据 --------------------------------------------------------------------------------------------------- + + // 老客户 + getRegularCustomer: async (params) => { + const { setLoading, setLoading2 } = get(); + const { IsDetail } = params; + setLoading(true); + setLoading2(IsDetail === 1); + const pivotByOrder = dateTypeDataHelper[params.DateType]; + const pivotByDate = params.DateType; + try { + const {result1, result2} = await fetchRegularCustomer(params); + set((state) => { + if (IsDetail === 1) { + state.regular.details = result1; + + const dump_l = (result1 || []).filter((ele) => ele.COLI_IsOld !== '' && ele.COLI_IsCusCommend !== '').length; + state.regular.total_data_tips = dump_l > 0 ? `包含 ${dump_l} 条同时勾选的数据` : ''; + /** 使用明细数据画图 */ + const seriesData = buildSeriesDataFromDetails(result1, pivotByOrder, pivotByDate); + state.regular.pivotData = seriesData; + state.regular.pivotX = pivotByDate; + state.regular.pivotY = pivotByOrder; + } else { + console.log('0000'); + state.regular.data = result1; + } + }); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + IsDetail === 1 && setLoading2(false); + } + }, + })), + { name: 'CustomerRelations' } + ) +); + +export default useCustomerRelationsStore; diff --git a/src/zustand/ToBOrder.js b/src/zustand/ToBOrder.js new file mode 100644 index 0000000..0d8230b --- /dev/null +++ b/src/zustand/ToBOrder.js @@ -0,0 +1,241 @@ +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; +import { groupsMappedByCode } from '../libs/ht'; +import { fetchJSON } from '@haina/utils-request'; +import { HT_HOST } from '../config'; +import { resultDataCb } from '../components/DateGroupRadio/date'; +import { isEmpty, price_to_number } from '@haina/utils-commons'; + +/** + * 分销(ToB)订单 + */ + +const defaultParams = {}; + +export const fetchToBOrderCount = async (params, type = '', typeVal = '') => { + const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetOrderCount_FX', { + ...defaultParams, + ...params, + COLI_ApplyDate1: params.Date1, + COLI_ApplyDate2: params.Date2, + COLI_ApplyDateOld1: params.DateDiff1 || '', + COLI_ApplyDateOld2: params.DateDiff2 || '', + OrderType: type, + OrderType_val: typeVal, + }); + return errcode !== 0 ? {} : (result || {}); +}; + +const _typeRes = { + 'ordercount1': [], + 'ordercount2': [], + 'ordercountTotal1': { + 'OrderType': '合计', + 'OrderCount': 0, + 'CJCount': 0, + 'CJPersonNum': 0, + 'YJLY': '', + 'CJrate': '0%', + 'Ordervalue': '', + 'groups': '', + 'key': 1, + }, + 'ordercountTotal2': {}, +}; +export const fetchToBOrderCountByType = async (type, params) => { + const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetOrderCountByType_FX', { + ...defaultParams, + ...params, + COLI_ApplyDate1: params.Date1, + COLI_ApplyDate2: params.Date2, + COLI_ApplyDateOld1: params.DateDiff1 || '', + COLI_ApplyDateOld2: params.DateDiff2 || '', + OrderType: type, + }); + const res = errcode !== 0 ? _typeRes : (result || _typeRes); + const rows1Map = res.ordercount1.reduce((a, row1) => ({ ...a, [row1.OrderTypeSN]: {...row1, YJLYx: price_to_number(row1.YJLY)} }), {}); + const rows2Map = res.ordercount2.reduce((a, row2) => ({ ...a, [row2.OrderTypeSN]: {...row2, YJLYx: price_to_number(row2.YJLY)} }), {}); + + const mixRow1 = res.ordercount1.map((row1) => ({ ...row1, YJLYx: price_to_number(row1.YJLY), diff: rows2Map[row1.OrderTypeSN] || {} })); + // Diff: elements in rows2 but not in rows1 + const diff = [...new Set(Object.keys(rows2Map).filter((x) => !new Set(Object.keys(rows1Map)).has(x)))]; + mixRow1.push(...diff.map((sn) => ({ diff: rows2Map[sn], OrderType: rows2Map[sn].OrderType, OrderTypeSN: rows2Map[sn].OrderTypeSN }))); + + return { ...res, ordercount1: mixRow1, ordercount2: res.ordercount2.map((row1) => ({ ...row1, YJLYx: price_to_number(row1.YJLY), })) }; +}; + +const _detailRes = { ordercount1: [], ordercount2: [] }; +export const fetchToBOrderDetailByType = async (params, type = '', typeVal = '', orderContent = 'detail') => { + const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetOrderCountByType_Sub_FX', { + ...defaultParams, + SubOrderType: orderContent, + ...params, + COLI_ApplyDate1: params.Date1, + COLI_ApplyDate2: params.Date2, + COLI_ApplyDateOld1: params.DateDiff1 || '', + COLI_ApplyDateOld2: params.DateDiff2 || '', + OrderType: type, + OrderType_val: typeVal, + }); + const res = errcode !== 0 ? _detailRes : (result || _detailRes); + const dateStr = [params.Date1, params.Date2].map((d) => d.substring(0, 10)).join('~'); + const dateDiffStr = isEmpty(params.DateDiff1) ? '' : [params.DateDiff1, params.DateDiff2].map((d) => d.substring(0, 10)).join('~'); + const ret = [ + { dateRangeStr: dateStr, data: res.ordercount1 }, + { dateRangeStr: dateDiffStr, data: res.ordercount2 }, + ]; + return ret; +}; + +const calculateLineData = (value, data, avg1) => { + const groupByDate = data.reduce((r, v) => { + (r[v.ApplyDate] || (r[v.ApplyDate] = [])).push(v); + return r; + }, {}); + const _data = Object.keys(groupByDate) + .reduce((r, _d) => { + const xAxisGroup = groupByDate[_d].reduce((a, v) => { + (a[v.groups] || (a[v.groups] = [])).push(v); + return a; + }, {}); + Object.keys(xAxisGroup).map((_group) => { + const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row.orderCount, 0); + r.push({ ...xAxisGroup[_group][0], orderCount: summaryVal }); + return _group; + }); + return r; + }, []) + .map((row) => ({ xField: row.ApplyDate, yField: row.orderCount, seriesField: row.groups })); + return { lines: _data, dateRadioValue: value, avgLineValue: avg1 }; +}; + +export const orderCountDataMapper = { 'data1': 'ordercount1', data2: 'ordercount2' }; +export const orderCountDataFieldMapper = { 'dateKey': 'ApplyDate', 'valueKey': 'orderCount', 'seriesKey': 'id', _f: 'sum' }; + +/** + * -------------------------------------------------------------------------------------------------------- + */ +const initialState = { + loading: false, + typeLoading: false, + searchValues: { + DateType: { key: 'applyDate', label: '提交日期' }, + WebCode: { key: 'all', label: '所有来源' }, + IncludeTickets: { key: '1', label: '含门票' }, + DepartmentList: groupsMappedByCode.GH, // { key: 'All', label: '所有来源' }, // + }, + searchValuesToSub: { + DateType: 'applyDate', + WebCode: 'all', + IncludeTickets: '1', + DepartmentList: -1, // -1: All + }, + + activeTab: 'customer_types', + activeDateGroupRadio: 'day', + + orderCountDataRaw: {}, + orderCountDataLines: [], + avgLineValue: 0, + + orderCountDataByType: {}, + + // 二级页面 + orderCountDataRawSub: {}, + orderCountDataLinesSub: [], + avgLineValueSub: 0, + activeDateGroupRadioSub: 'day', + orderDetails: [], +}; + +const useToBOrderStore = create( + devtools( + immer((set, get) => ({ + ...initialState, + reset: () => set(initialState), + + setLoading: (loading) => set({ loading }), + setTypeLoading: (typeLoading) => set({ typeLoading }), + + setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })), + setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })), + setActiveTab: (tab) => set({ activeTab: tab }), + + setOrderCountDataLines: (data) => set({ orderCountDataLines: data }), + setOrderCountDataByType: (type, data) => + set((state) => { + state.orderCountDataByType[type] = data; + }), + + // data ---- + onChangeDateGroup: (value, data, avg1) => { + const { lines, dateRadioValue, avgLineValue } = calculateLineData(value, data, avg1); + set({ orderCountDataLines: lines, avgLineValue, activeDateGroupRadio: dateRadioValue }); + }, + onChangeDateGroupSub: (value, data, avg1) => { + const { lines, dateRadioValue, avgLineValue } = calculateLineData(value, data, avg1); + set({ orderCountDataLinesSub: lines, avgLineValueSub: avgLineValue, activeDateGroupRadioSub: dateRadioValue }); + }, + + // site effects + getToBOrderCount: async (params, type, typeVal) => { + const { setLoading } = get(); + setLoading(true); + try { + const res = await fetchToBOrderCount(params, type, typeVal); + // 第一次得到数据 + const { lines, dateRadioValue, avgLineValue } = resultDataCb(res, 'day', orderCountDataMapper, orderCountDataFieldMapper, calculateLineData); + if (isEmpty(type)) { + // index page + set({ orderCountDataRaw: res }); + set({ orderCountDataLines: lines, avgLineValue, activeDateGroupRadio: dateRadioValue }); + } else { + // sub page + set({ orderCountDataRawSub: res }); + set({ orderCountDataLinesSub: lines, avgLineValueSub: avgLineValue, activeDateGroupRadioSub: dateRadioValue }); + } + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }, + + getToBOrderCount_type: async (type) => { + const { setTypeLoading, searchValuesToSub, setOrderCountDataByType } = get(); + setTypeLoading(true); + try { + const res = await fetchToBOrderCountByType(type, searchValuesToSub); + setOrderCountDataByType(type, res); + } catch (error) { + console.error(error); + } finally { + setTypeLoading(false); + } + }, + + onTabChange: async (tab) => { + const { setActiveTab, getToBOrderCount_type } = get(); + setActiveTab(tab); + await getToBOrderCount_type(tab); + }, + + // sub + getToBOrderDetailByType: async (params, type, typeVal) => { + const { setTypeLoading } = get(); + try { + setTypeLoading(true); + const res = await fetchToBOrderDetailByType(params, type, typeVal, 'detail'); + set({ orderDetails: res }); + } catch (error) { + console.error(error); + } finally { + setTypeLoading(false); + } + }, + })), + { name: 'ToBOrder' } + ) +); +export default useToBOrderStore;