Compare commits
34 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9edfa0c5b8 | 7 days ago |
|
|
195d0facaa | 2 weeks ago |
|
|
0f2041cdc8 | 2 weeks ago |
|
|
ce4e23fe6c | 2 weeks ago |
|
|
62ba5b5973 | 1 month ago |
|
|
986f27eb28 | 1 month ago |
|
|
9a59a5ac0e | 1 month ago |
|
|
11be41a446 | 2 months ago |
|
|
da3f4458ff | 2 months ago |
|
|
e2d91d9df8 | 2 months ago |
|
|
594426048c | 2 months ago |
|
|
d6c34f145e | 2 months ago |
|
|
90714b6f82 | 2 months ago |
|
|
ae07f7593b | 3 months ago |
|
|
460dd47f1c | 3 months ago |
|
|
46f3a52784 | 3 months ago |
|
|
93fa13ac9d | 3 months ago |
|
|
5351239f5f | 3 months ago |
|
|
a44a05de26 | 3 months ago |
|
|
c023dd5b68 | 3 months ago |
|
|
16e6ec574a | 3 months ago |
|
|
ead5b37ded | 3 months ago |
|
|
5f7a942842 | 3 months ago |
|
|
1f5d6a9047 | 3 months ago |
|
|
7cb91abea2 | 3 months ago |
|
|
8581038aeb | 3 months ago |
|
|
3b24aa1373 | 3 months ago |
|
|
cb994aff29 | 3 months ago |
|
|
a48ed8ff03 | 3 months ago |
|
|
8cd446d8f8 | 3 months ago |
|
|
559ba14e35 | 3 months ago |
|
|
a2b920b313 | 3 months ago |
|
|
9793f62b34 | 3 months ago |
|
|
a66ed759f8 | 3 months ago |
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Select } from 'antd';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
// 导游语种列表
|
||||||
|
export const languageOptions = [
|
||||||
|
{ value: '', label: '所有语种' },
|
||||||
|
{ value: '102001', label: '英语' },
|
||||||
|
{ value: '102002', label: '普通话' },
|
||||||
|
{ value: '102003', label: '日语' },
|
||||||
|
{ value: '102004', label: '韩语' },
|
||||||
|
{ value: '102005', label: '德语' },
|
||||||
|
{ value: '102006', label: '法语' },
|
||||||
|
{ value: '102007', label: '意大利语' },
|
||||||
|
{ value: '102008', label: '西班牙语' },
|
||||||
|
{ value: '102009', label: '俄语' },
|
||||||
|
{ value: '102010', label: '粤语' },
|
||||||
|
{ value: '102011', label: '印尼语' },
|
||||||
|
{ value: '102012', label: '泰国语' },
|
||||||
|
{ value: '102013', label: '葡萄牙语' }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GuideLanguageSelect = ({ value, onChange, ...props }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
defaultValue={['']}
|
||||||
|
placeholder="选择导游语种"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
allowClear={true}
|
||||||
|
options={languageOptions}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(GuideLanguageSelect);
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Select } from 'antd';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import { lineClass } from './../../libs/ht';
|
||||||
|
|
||||||
|
export const LineClassSelector = ({ value, onChange, ...props }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="选择来源类型"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
allowClear
|
||||||
|
labelInValue
|
||||||
|
{...props}
|
||||||
|
options={lineClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(LineClassSelector);
|
||||||
@ -0,0 +1,367 @@
|
|||||||
|
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: (
|
||||||
|
<span>
|
||||||
|
<div>{result.ordercountTotal1?.groups}</div>
|
||||||
|
{showDiff ? <div>{result.ordercountTotal2?.groups}</div> : null}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
titleX: `${result.ordercountTotal1?.groups}` + (showDiff ? ` vs ${result.ordercountTotal2?.groups}` : ''),
|
||||||
|
dataIndex: 'OrderType',
|
||||||
|
fixed: 'left',
|
||||||
|
render: (text, record) => <NavLink to={`/tob_orders_sub/${activeTab}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '数量',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<RenderVSDataCell
|
||||||
|
showDiffData={showDiff}
|
||||||
|
data1={result.ordercountTotal1?.OrderCount}
|
||||||
|
data2={result.ordercountTotal2?.OrderCount}
|
||||||
|
diffPercent={result.ordercountTotal1?.OrderCount_vs}
|
||||||
|
diffData={result.ordercountTotal1?.OrderCount_diff}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
titleX: [result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount].join(' vs '),
|
||||||
|
dataIndex: 'OrderCount',
|
||||||
|
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.OrderCount} diffPercent={r.OrderCount_vs} diffData={r.OrderCount_diff} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '成交数',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<RenderVSDataCell
|
||||||
|
showDiffData={showDiff}
|
||||||
|
data1={result.ordercountTotal1?.CJCount}
|
||||||
|
data2={result.ordercountTotal2?.CJCount}
|
||||||
|
diffPercent={result.ordercountTotal1?.CJCount_vs}
|
||||||
|
diffData={result.ordercountTotal1?.CJCount_diff}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
titleX: [result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount].join(' vs '),
|
||||||
|
dataIndex: 'CJCount',
|
||||||
|
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJCount} diffPercent={r.CJCount_vs} diffData={r.CJCount_diff} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '成交人数',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<RenderVSDataCell
|
||||||
|
showDiffData={showDiff}
|
||||||
|
data1={result.ordercountTotal1?.CJPersonNum}
|
||||||
|
data2={result.ordercountTotal2?.CJPersonNum}
|
||||||
|
diffPercent={result.ordercountTotal1?.CJPersonNum_vs}
|
||||||
|
diffData={result.ordercountTotal1?.CJPersonNum_diff}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
titleX: [result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum].join(' vs '),
|
||||||
|
dataIndex: 'CJPersonNum',
|
||||||
|
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJPersonNum} diffPercent={r.CJPersonNum_vs} diffData={r.CJPersonNum_diff} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '成交率',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<RenderVSDataCell
|
||||||
|
showDiffData={showDiff}
|
||||||
|
data1={result.ordercountTotal1?.CJrate}
|
||||||
|
data2={result.ordercountTotal2?.CJrate}
|
||||||
|
diffPercent={result.ordercountTotal1?.CJrate_vs}
|
||||||
|
diffData={result.ordercountTotal1?.CJrate_diff}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
titleX: [result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate].join(' vs '),
|
||||||
|
dataIndex: 'CJrate',
|
||||||
|
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJrate} diffPercent={r.CJrate_vs} diffData={r.CJrate_diff} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '成交毛利(预计)',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<RenderVSDataCell
|
||||||
|
showDiffData={showDiff}
|
||||||
|
data1={result.ordercountTotal1?.YJLY}
|
||||||
|
data2={result.ordercountTotal2?.YJLY}
|
||||||
|
diffPercent={result.ordercountTotal1?.YJLY_vs}
|
||||||
|
diffData={result.ordercountTotal1?.YJLY_diff}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
titleX: [result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY].join(' vs '),
|
||||||
|
dataIndex: 'YJLY',
|
||||||
|
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.YJLY} diffPercent={r.YJLY_vs} diffData={r.YJLY_diff} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '单个订单价值',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<RenderVSDataCell
|
||||||
|
showDiffData={showDiff}
|
||||||
|
data1={result.ordercountTotal1?.Ordervalue}
|
||||||
|
data2={result.ordercountTotal2?.Ordervalue}
|
||||||
|
diffPercent={result.ordercountTotal1?.Ordervalue_vs}
|
||||||
|
diffData={result.ordercountTotal1?.Ordervalue_diff}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
titleX: [result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue].join(' vs '),
|
||||||
|
dataIndex: 'Ordervalue',
|
||||||
|
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.Ordervalue} diffPercent={r.Ordervalue_vs} diffData={r.Ordervalue_diff} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
size: 'small',
|
||||||
|
pagination: false,
|
||||||
|
scroll: { x: 100 * 7 },
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Row gutter={16} className={toJS(searchFormStore.siderBroken) ? '' : 'sticky-top'}>
|
||||||
|
<Col className="gutter-row" span={24}>
|
||||||
|
<SearchForm
|
||||||
|
defaultValue={{
|
||||||
|
initialValue: {
|
||||||
|
...toJS(searchFormStore.formValues),
|
||||||
|
...searchValues,
|
||||||
|
},
|
||||||
|
//
|
||||||
|
shows: ['DateType', 'WebCode', 'IncludeTickets', 'DepartmentList', 'dates'],
|
||||||
|
fieldProps: {
|
||||||
|
DepartmentList: { show_all: false, mode: 'multiple' },
|
||||||
|
WebCode: { show_all: false, mode: 'multiple' },
|
||||||
|
years: { hide_vs: true },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onSubmit={(_err, obj, form, str) => {
|
||||||
|
setSearchValues(obj, form);
|
||||||
|
getToBOrderCount(obj);
|
||||||
|
onTabChange(activeTab);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={[16, { sm: 16, lg: 32 }]}>
|
||||||
|
<Col span={24} style={{ textAlign: 'right' }}>
|
||||||
|
<DateGroupRadio
|
||||||
|
visible={orderCountDataLines.length !== 0}
|
||||||
|
dataRaw={orderCountDataRaw}
|
||||||
|
onChange={onChangeDateGroup}
|
||||||
|
value={activeDateGroupRadio}
|
||||||
|
dataMapper={orderCountDataMapper}
|
||||||
|
fieldMapper={orderCountDataFieldMapper}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<Line {...lineConfig} />
|
||||||
|
</Spin>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={24}>
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(active_key) => onTabChange(active_key)}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'customer_types',
|
||||||
|
label: (
|
||||||
|
<span>
|
||||||
|
<CustomerServiceOutlined />
|
||||||
|
分销客户
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
].map((ele) => {
|
||||||
|
return {
|
||||||
|
...ele,
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Table sticky key={`table_to_xlsx_${ele.key}`} {...tableProps} loading={typeLoading} />
|
||||||
|
<Divider orientation="right" plain>
|
||||||
|
<TableExportBtn label={ele.key} {...{ columns: tableProps.columns, dataSource: tableProps.dataSource }} />
|
||||||
|
</Divider>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>各项占比</h3>
|
||||||
|
{/* <Checkbox
|
||||||
|
checked={true}
|
||||||
|
// onChange={(e) => setIsShowEmpty(e.target.checked)}
|
||||||
|
>
|
||||||
|
包含空值
|
||||||
|
</Checkbox> */}
|
||||||
|
</div>
|
||||||
|
<Spin spinning={typeLoading}>
|
||||||
|
<Row>
|
||||||
|
<Col sm={24} lg={12}>
|
||||||
|
<Pie {...pieConfig} data={result?.ordercount1 || []} innerRadius={0.6} statistic={{ title: false, content: { content: '数量' } }} />
|
||||||
|
<Pie {...pieConfig} data={result?.ordercount1 || []} angleField="YJLYx" innerRadius={0.6} statistic={{ title: false, content: { content: '预计毛利' } }} />
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
{showDiff && (
|
||||||
|
<Col sm={24} lg={12}>
|
||||||
|
<Pie {...pieConfig} data={result?.ordercount2 || []} innerRadius={0.6} statistic={{ title: false, content: { content: '数量' } }} />
|
||||||
|
<Pie {...pieConfig} data={result?.ordercount2 || []} angleField="YJLYx" innerRadius={0.6} statistic={{ title: false, content: { content: '预计毛利' } }} />
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
export default ToBOrder;
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
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 { groupBy, isEmpty, } from '@haina/utils-commons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顾问业绩 (例会数据)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const defaultParams = { OrderType: 227001, IsDYTJ: '', IncludeTickets: 1, Team: '', WebCodeFX: '', CusType: '', OldCus: 0, lineClass: '', IsDetail: -1 };
|
||||||
|
|
||||||
|
export const fetchMeetingDataSales = async (params) => {
|
||||||
|
const { errcode, errmsg, result } = await fetchJSON(HT_HOST + '/service-web/QueryData/WLCountForMeetingNew', {
|
||||||
|
...defaultParams,
|
||||||
|
...params,
|
||||||
|
WebCode: (params.WebCode || '').replace('all', ''),
|
||||||
|
OPI_SN: params.operator || '',
|
||||||
|
});
|
||||||
|
const ret =
|
||||||
|
errcode !== 0
|
||||||
|
? []
|
||||||
|
: (result || [])
|
||||||
|
// .filter((ele) =>
|
||||||
|
// Object.keys(ele)
|
||||||
|
// .filter((col) => !['OPI_SN', 'OPI_Name', 'COLI_LineClass', 'LineClass', 'vi'].includes(col))
|
||||||
|
// .some((col) => !isEmpty(ele[col])),
|
||||||
|
// )
|
||||||
|
.map((ele) => ({ ...ele, key: `${ele.OPI_SN}_${ele.COLI_LineClass || ''}_${ele.vi || ''}` }));
|
||||||
|
const byOPI = groupBy(structuredClone(ret), 'OPI_SN');
|
||||||
|
const OPIValue = Object.keys(byOPI).reduce((r, opisn) => {
|
||||||
|
byOPI[opisn].forEach((ele, xi) => {
|
||||||
|
ele.rowSpan = xi === 0 ? byOPI[opisn].length : 0;
|
||||||
|
});
|
||||||
|
return [...r, ...byOPI[opisn]];
|
||||||
|
}, []);
|
||||||
|
const byLineClass = groupBy(structuredClone(ret), 'LineClass');
|
||||||
|
const LineClassValue = Object.keys(byLineClass).reduce((r, gkey) => {
|
||||||
|
byLineClass[gkey].forEach((ele, xi) => {
|
||||||
|
ele.rowSpan = xi === 0 ? byLineClass[gkey].length : 0;
|
||||||
|
});
|
||||||
|
return [...r, ...byLineClass[gkey]];
|
||||||
|
}, []);
|
||||||
|
return { result: ret, OPIValue, LineClassValue };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
salesDataTotal: [],
|
||||||
|
salesData: [],
|
||||||
|
matrixData: {},
|
||||||
|
matrixtableMajorKey: 'opi',
|
||||||
|
matrixTableData: [],
|
||||||
|
|
||||||
|
// 二级页面
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSalesInsightStore = 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 }),
|
||||||
|
|
||||||
|
|
||||||
|
// site effects
|
||||||
|
getMeetingDataSales: async (params) => {
|
||||||
|
const { setTypeLoading, } = get();
|
||||||
|
setTypeLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await fetchMeetingDataSales(params);
|
||||||
|
if (params.IsDetail === 1) {
|
||||||
|
set({ matrixData: res, matrixTableData: res.OPIValue, matrixtableMajorKey: 'opi', salesData: res.result });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set({ salesDataTotal: res.result });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setTypeLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onMatrixChange: () => {
|
||||||
|
const { matrixtableMajorKey, matrixData } = get();
|
||||||
|
const newKey = matrixtableMajorKey === 'opi' ? 'lineclass' : 'opi';
|
||||||
|
const dataKey = matrixtableMajorKey === 'opi' ? 'LineClassValue' : 'OPIValue' ;
|
||||||
|
set({ matrixtableMajorKey: newKey, matrixTableData: matrixData?.[dataKey] });
|
||||||
|
},
|
||||||
|
|
||||||
|
// sub
|
||||||
|
})),
|
||||||
|
{ name: 'SalesInsight' }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
export default useSalesInsightStore;
|
||||||
@ -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;
|
||||||
Loading…
Reference in New Issue