Compare commits

..

No commits in common. 'main' and 'feature/person-num' have entirely different histories.

@ -1 +1 @@
npm run build
npm run build

@ -1,7 +0,0 @@
process.env.REACT_APP_BUILD_TIME = new Date().getTime()+(5*60*1000);
require('child_process').execSync(
'react-scripts build',
{ stdio: 'inherit' }
);

33
package-lock.json generated

@ -1,18 +1,17 @@
{
"name": "haina-dashboard",
"version": "2.11.12",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "haina-dashboard",
"version": "2.11.12",
"version": "0.1.0",
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/pro-components": "^2.6.16",
"antd": "^4.22.6",
"dingtalk-jsapi": "^3.0.9",
"insert-css": "^2.0.0",
"mobx": "^6.6.1",
"mobx-react": "^7.5.2",
"react": "^18.2.0",
@ -7284,9 +7283,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001553",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz",
"integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A=="
"version": "1.0.30001458",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz",
"integrity": "sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w=="
},
"node_modules/case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
@ -11539,7 +11538,7 @@
},
"node_modules/insert-css": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz",
"resolved": "https://registry.npmmirror.com/insert-css/-/insert-css-2.0.0.tgz",
"integrity": "sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA=="
},
"node_modules/internal-slot": {
@ -19363,9 +19362,9 @@
}
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.1.1.tgz",
"integrity": "sha512-lVrM/bNdhVX2OgBFNa2YJ9Lxj7kPzylieHd3TNjuGE0Re9JB7joL5VUKOVH1kdNNJTgGPpT8hmwIAPLaSyEVFQ=="
},
"node_modules/supercluster": {
"version": "7.1.5",
@ -26807,9 +26806,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001553",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz",
"integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A=="
"version": "1.0.30001458",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz",
"integrity": "sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w=="
},
"case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
@ -29964,7 +29963,7 @@
},
"insert-css": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz",
"resolved": "https://registry.npmmirror.com/insert-css/-/insert-css-2.0.0.tgz",
"integrity": "sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA=="
},
"internal-slot": {
@ -35568,9 +35567,9 @@
}
},
"stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.1.1.tgz",
"integrity": "sha512-lVrM/bNdhVX2OgBFNa2YJ9Lxj7kPzylieHd3TNjuGE0Re9JB7joL5VUKOVH1kdNNJTgGPpT8hmwIAPLaSyEVFQ=="
},
"supercluster": {
"version": "7.1.5",

@ -1,13 +1,12 @@
{
"name": "haina-dashboard",
"version": "2.11.12",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/pro-components": "^2.6.16",
"antd": "^4.22.6",
"dingtalk-jsapi": "^3.0.9",
"insert-css": "^2.0.0",
"mobx": "^6.6.1",
"mobx-react": "^7.5.2",
"react": "^18.2.0",
@ -19,7 +18,7 @@
},
"scripts": {
"start": "react-scripts start",
"build": "node build.js",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint ./src",

@ -25,20 +25,10 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Hainatravel Dashboard</title>
<script crossorigin="anonymous" src="https://page-spy.mycht.cn/page-spy/index.min.js"></script>
<script crossorigin="anonymous" src="https://page-spy.mycht.cn/plugin/data-harbor/index.min.js"></script>
<script crossorigin="anonymous" src="https://page-spy.mycht.cn/plugin/rrweb/index.min.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script defer>
window.initPageSpy = function () {
PageSpy.registerPlugin(new DataHarborPlugin({ maximum: 2 * 1024 * 1024, }));
window.$pageSpy = new PageSpy({ api: 'page-spy.mycht.cn', project: 'Dashboard', title: window.__spytitle, autoRender: false, });
// console.log(window.$pageSpy.address.substring(0, 4)); // device ID
}
</script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

@ -32,12 +32,5 @@
padding: 0;
}
.p-s1{
padding: .5rem!important;
}
.sticky-top{
margin: -16px -8px .5em -8px;
position: sticky;
top: 0;
z-index: 100;
padding: .5em;
}

@ -1,6 +1,6 @@
import './App.css';
import React, { useContext, useState } from 'react';
import Icon, {
import React, { useContext } from 'react';
import {
HomeOutlined,
TeamOutlined,
DashboardOutlined,
@ -10,14 +10,9 @@ import Icon, {
DollarOutlined,
AreaChartOutlined,
WechatOutlined,
UserOutlined,
FlagOutlined,
PieChartOutlined,
BarChartOutlined,
CoffeeOutlined, DesktopOutlined,
WhatsAppOutlined
UserOutlined, FlagOutlined, PieChartOutlined, BarChartOutlined
} from '@ant-design/icons';
import { Layout, Menu, Image, Badge, Button } from 'antd';
import { Layout, Menu, Image, Badge } from 'antd';
import { BrowserRouter, Route, Routes, NavLink } from 'react-router-dom';
import Home from './views/Home';
import Dashboard from './views/Dashboard';
@ -27,7 +22,6 @@ import ProtectedRoute from './views/ProtectedRoute';
import Customer_care_inchina from './charts/Customer_care_inchina';
import Customer_care_potential from './charts/Customer_care_potential';
import Customer_care_regular from './charts/Customer_care_regular';
import Customer_care_regular_pivot from './charts/Customer_care_regular_pivot';
import Wechat_session from './charts/Wechat_session';
import WhatsApp_session from './charts/WhatsApp_session';
import AgentGroupCount from './views/AgentGroupCount';
@ -38,51 +32,21 @@ import Credit_card_bill from './views/Credit_card_bill';
import Sale from './views/Sale';
import Sale_sub from './views/Sale_sub';
import Sale_KPI from './views/Sale_KPI';
// import Logo from './logo.png';
import Logo from './logo.png';
import { stores_Context } from './config';
import { observer } from 'mobx-react';
import ExchangeRate from './charts/ExchangeRate';
import KPI from './views/KPI';
import Distribution from './views/Distribution';
import Detail from './views/Detail';
import ServicePersonNum from './views/ServicePersonNum';
import DataPivot from './views/DataPivot';
import Welcome from './views/Welcome';
import Meeting2024GH from './views/Meeting2024-GH';
import Meeting2025GH from './views/Meeting2025-GH';
import { stores_Context, APP_VERSION } from './config';
import { WaterMark } from '@ant-design/pro-components';
import CooperationIcon from './components/icons/CooperationIcon';
import OPDashboard from './views/sales-crm/Dashboard';
import OPProcess from './views/sales-crm/Process';
import OPRisk from './views/sales-crm/Risk';
import Cruise from './views/Cruise';
import Hotel from './views/Hotel';
import HostCaseCount from './views/HostCaseCount';
const App = () => {
const { Content, Footer, Sider, } = Layout;
const { auth_store, date_picker_store } = useContext(stores_Context);
const [collapsed, setCollapsed] = useState(false);
const { Content, Footer, Sider } = Layout;
const { auth_store } = useContext(stores_Context);
const menu_items = [
{ key: 1, label: <NavLink to="/">欢迎</NavLink>, icon: <CoffeeOutlined /> },
{ key: 'annual', label: <NavLink to="/annual">综合年度</NavLink>, icon: <DashboardOutlined /> },
{
key: 'mixed',
label: '报告',
icon: <DesktopOutlined />,
children: [
{
key: 'meeting-2025-GH',
label: <NavLink to="/orders/meeting-2025-GH">GH例会数据-2025</NavLink>, // GH-2024
},
{
key: 'meeting-2024-GH',
label: <NavLink to="/orders/meeting-2024-GH">GH区域数据</NavLink>, // GH-2024
},
]
},
{ key: 1, label: <NavLink to="/">主页</NavLink>, icon: <HomeOutlined /> },
{
key: 2,
label: '市场',
@ -91,16 +55,12 @@ const App = () => {
{
key: 21,
label: <NavLink to="/orders">订单数据</NavLink>,
// icon: <FileProtectOutlined />,
icon: <FileProtectOutlined />,
},
{
key: 22,
label: <NavLink to="/dashboard">仪表盘</NavLink>,
// icon: <DashboardOutlined />,
},
{
key: 'orders-pivot',
label: <NavLink to="/orders/pivot">数据透视</NavLink>,
icon: <DashboardOutlined />,
},
],
},
@ -112,7 +72,6 @@ const App = () => {
{ key: 51, label: <NavLink to="/sale">业绩数据</NavLink> },
{ key: 52, label: <NavLink to="/sale_kpi">销售进度</NavLink> },
{ key: 'distribution', label: <NavLink to="/distribution">统计分布</NavLink> },
{ key: 'trade-pivot', label: <NavLink to="/trade/pivot">数据透视</NavLink> },
],
},
{
@ -128,11 +87,18 @@ const App = () => {
key: 32,
label: <NavLink to="/customer_care_regular">老客户</NavLink>,
},
{ key: 'customer_care_regular_pivot', label: <NavLink to="/customer_care_regular_pivot">老客户-分析</NavLink> },
{
key: 33,
label: <NavLink to="/customer_care_inchina">在华客户</NavLink>,
},
{
key: 34,
label: <NavLink to="/wechat_session">微信会话存档</NavLink>,
},
{
key: 35,
label: <NavLink to="/whatsapp_session">WhatsApp会话存档</NavLink>,
},
],
},
{
@ -151,7 +117,7 @@ const App = () => {
{
key: 6,
label: '客服',
icon: <CooperationIcon/>,
icon: <WechatOutlined />,
children: [
{
key: 61,
@ -161,20 +127,6 @@ const App = () => {
key: 62,
label: <NavLink to="/destination/group/count">目的地接团</NavLink>,
},
{ key: 'cruise', label: <NavLink to="/cruise">三峡游船</NavLink> },
{ key: 'hotel', label: <NavLink to="/hotel">酒店</NavLink> },
{ key: 'hostcase', label: <NavLink to="/hostcase/count">东道主项目</NavLink> },
],
},
{
key: 'crm',
label: '销售平台',
icon: <WhatsAppOutlined />,
children: [
// { key: 'xx', type: 'divider' },
{ key: 'op_dashboard', label: <NavLink to="/sales-crm/dashboard">结果</NavLink> },
{ key: 'op_process', label: <NavLink to="/sales-crm/process">过程</NavLink> },
// { key: 'op_risk', label: <NavLink to="/op_risk"></NavLink> },
],
},
{ key: 'kpi', label: <NavLink to="/kpi">目标配置</NavLink>, icon: <FlagOutlined /> },
@ -189,122 +141,88 @@ const App = () => {
// },
];
const callDebug = () => {
// const vConsole = new window.VConsole({ theme: 'dark' });
// auth_store.get_auth();
// window.$pageSpy.render();
window.$pageSpy.triggerPlugins('onOfflineLog', 'upload');
window.$pageSpy.triggerPlugins('onOfflineLog', 'download');
};
return (
<BrowserRouter>
<WaterMark content={['HT统计']} gapY={100} gapX={100} rotate={-20} offsetLeft={150} offsetTop={150} fontColor="rgba(0,0,0,.06)" zIndex={20}>
<WaterMark content={[auth_store.user.name, auth_store.user.userid.slice(-4)]} gapY={100} gapX={100} rotate={-20} fontColor="rgba(0,0,0,.08)" zIndex={20}>
<Layout
hasSider
<Layout
hasSider
style={{
minHeight: '100vh',
}}
>
<Sider
collapsible={true}
breakpoint="lg"
style={{
overflow: 'auto',
height: '100vh',
position: 'sticky',
left: 0,
top: 0,
bottom: 0,
}}
>
<Image src={Logo} preview={false} />
<Menu
theme="dark"
defaultSelectedKeys={['1']}
defaultOpenKeys={['2', '5']}
mode="inline"
items={menu_items}
/>
</Sider>
<Layout className="site-layout">
<Content
style={{
minHeight: '100vh',
padding: 16,
minHeight: 480,
}}
>
<Sider
collapsible={true}
breakpoint="lg"
collapsedWidth="0"
collapsed={collapsed}
style={{
// overflow: 'auto',
height: '100vh',
position: 'sticky',
left: 0,
top: 0,
bottom: 0,
zIndex: 999,
}}
zeroWidthTriggerStyle={collapsed ? { zIndex: 90, bottom: '64px', top: '3px', left: 'unset', right: '-34px' } : { bottom: '64px', top: '3px', left: 'unset', right: 0 }}
onBreakpoint={(broken) => {
date_picker_store.setSiderBroken(broken);
}}
onCollapse={(collapsed, type) => {
setCollapsed(collapsed);
}}
>
{/* <Image src={Logo} preview={false} /> */}
<Menu theme="dark" defaultSelectedKeys={['1']} defaultOpenKeys={[]} mode="inline" items={menu_items} onClick={() => date_picker_store.siderBroken ? setCollapsed(!collapsed) : false} />
</Sider>
<Layout className="site-layout">
<Content
style={{
padding: 16,
minHeight: 480,
}}
>
<Routes>
<Route path="/" element={<Welcome />} />
<Route path="/detail" element={<Detail />} />
<Route element={<ProtectedRoute auth={['admin', 'director_bu']} />}>
<Route path="/annual" element={<Home />} />
<Route path="/kpi" element={<KPI />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'marketing']} />}>
<Route path="/orders" element={<Orders />} />
<Route path="/orders_sub/:ordertype/:ordertype_sub/:ordertype_title" element={<Orders_sub />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/:page/pivot" element={<DataPivot />} />
<Route path="/orders/meeting-2024-GH" element={<Meeting2024GH />} />
<Route path="/orders/meeting-2025-GH" element={<Meeting2025GH />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'customer_care']} />}>
<Route path="/customer_care_inchina" element={<Customer_care_inchina />} />
<Route path="/customer_care_regular" element={<Customer_care_regular />} />
<Route path="/customer_care_regular_pivot" element={<Customer_care_regular_pivot />} />
<Route path="/customer_care_potential" element={<Customer_care_potential />} />
<Route path="/whatsapp_session" element={<WhatsApp_session />} />
<Route path="/wechat_session" element={<Wechat_session />} />
<Route path="/agent/group/count" element={<AgentGroupCount />} />
<Route path="/destination/group/count" element={<DestinationGroupCount />} />
<Route path="/agent/:agentId/group/list" element={<AgentGroupList />} />
<Route path="/destination/:destinationId/group/list" element={<DestinationGroupList />} />
<Route path="/cruise" element={<Cruise />} />
<Route path="/hotel" element={<Hotel />} />
<Route path="/hostcase/count" element={<HostCaseCount />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'financial']} />}>
<Route path="/credit_card_bill" element={<Credit_card_bill />} />
<Route path="/exchange_rate" element={<ExchangeRate />} />
<Route path="/service_person_num" element={<ServicePersonNum />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'sale']} />}>
<Route path="/sale" element={<Sale />} />
<Route path="/sale_sub/:type_sub" element={<Sale_sub />} />
<Route path="/sale_kpi" element={<Sale_KPI />} />
<Route path="/distribution" element={<Distribution />} />
<Route path="/:page/pivot" element={<DataPivot />} />
<Route path="/sales-crm/dashboard" element={<OPDashboard />} />
<Route path="/sales-crm/process" element={<OPProcess />} />
<Route path="/sales-crm/risk" element={<OPRisk />} />
<Route path="/sales-crm/risk/sales/:opisn" element={<OPRisk />} />
</Route>
</Routes>
</Content>
<Footer
style={{
textAlign: 'center',
}}
>
<UserOutlined /> {auth_store.user.name} ({auth_store.user.userid})
<br />
Hainatravel Dashboard{' '}
<span>
v<span>{APP_VERSION}</span>{' '}
</span>{' '}
©2022 <span > Created by IT</span>
{window.$pageSpy && <Button type='link' onClick={callDebug}>上传/下载debug日志({window.$pageSpy.address.substring(0, 4)})</Button>}
</Footer>
</Layout>
</Layout>
</WaterMark>
</WaterMark>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/detail" element={<Detail />} />
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'marketing']} />}>
<Route path="/orders" element={<Orders />} />
<Route path="/orders_sub/:ordertype/:ordertype_sub/:ordertype_title" element={<Orders_sub />} />
<Route path="/dashboard" element={<Dashboard />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'customer_care']} />}>
<Route path="/customer_care_inchina" element={<Customer_care_inchina />} />
<Route path="/customer_care_regular" element={<Customer_care_regular />} />
<Route path="/customer_care_potential" element={<Customer_care_potential />} />
<Route path="/whatsapp_session" element={<WhatsApp_session />} />
<Route path="/wechat_session" element={<Wechat_session />} />
<Route path="/agent/group/count" element={<AgentGroupCount />} />
<Route path="/destination/group/count" element={<DestinationGroupCount />} />
<Route path="/agent/:agentId/group/list" element={<AgentGroupList />} />
<Route path="/destination/:destinationId/group/list" element={<DestinationGroupList />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'financial']} />}>
<Route path="/credit_card_bill" element={<Credit_card_bill />} />
<Route path="/exchange_rate" element={<ExchangeRate />} />
<Route path="/service_person_num" element={<ServicePersonNum />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu']} />}>
<Route path="/kpi" element={<KPI />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'sale']} />}>
<Route path="/sale" element={<Sale />} />
<Route path="/sale_sub/:type_sub" element={<Sale_sub />} />
<Route path="/sale_kpi" element={<Sale_KPI />} />
<Route path="/distribution" element={<Distribution />} />
</Route>
</Routes>
</Content>
<Footer
style={{
textAlign: 'center',
}}
>
<UserOutlined /> {auth_store.user.name} ({auth_store.user.userid})
<br />
Hainatravel Dashboard ©2022 Created by IT
</Footer>
</Layout>
</Layout>
</BrowserRouter>
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

@ -1,9 +1,19 @@
import React, {useContext, useEffect} from 'react';
import {Row, Col, Divider, Table} from 'antd';
import {Row, Col, Button, Divider, Table, Space, Radio, Tooltip} from 'antd';
import {
ContainerOutlined,
SearchOutlined,
} from '@ant-design/icons';
import {stores_Context} from '../config';
import {Line} from "@ant-design/charts";
import {observer} from 'mobx-react';
import DatePickerCharts from '../components/search/DatePickerCharts';
import {NavLink, useParams} from "react-router-dom";
import * as comm from "../utils/commons";
import * as config from "../config";
import SiteSelect from "../components/search/SiteSelect";
import GroupSelect from "../components/search/GroupSelect";
import {utils, writeFileXLSX} from "xlsx";
import SearchForm from './../components/search/SearchForm';
const Customer_care_inchina = () => {
@ -17,35 +27,38 @@ const Customer_care_inchina = () => {
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...inchina_data.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'inchina_data');
customer_store.inchina_customer_order();
customer_store.inchina_customer_order(true);
}}
/>
</Col>
</Row>
<Row gutter={16} >
<Col span={24}>
<Row>
<Col span={8}>
<h2>在华客人</h2>
</Col>
<Col span={15}>
<Row>
<Col span={6}>
<SiteSelect store={inchina_data} show_all={true}/>
</Col>
<Col span={18}> <GroupSelect store={inchina_data}/></Col>
<Col span={24}> <Space>
<DatePickerCharts hide_vs={true}/>
<Radio.Group value={inchina_data.date_type}
onChange={inchina_data.onChange_datetype}>
<Radio value="applyDate">提交日期</Radio>
<Radio value="startDate">出发日期</Radio>
<Radio value="ConfirmDate">确认日期</Radio>
</Radio.Group>
<Button type="primary" icon={<SearchOutlined/>} loading={inchina_data.loading}
onClick={() => {
inchina_data.inchina_customer_order();
inchina_data.inchina_customer_order(true);
}}>统计</Button>
</Space>
</Col>
</Row>
</Col>
<Col span={1}></Col>
<Col span={24}>
<Table dataSource={inchina_data.data} loading={inchina_data.loading} columns={
<Table dataSource={inchina_data.data} columns={
[
{
title: '统计条目',
@ -88,7 +101,7 @@ const Customer_care_inchina = () => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx").getElementsByTagName('table')[0]);
writeFileXLSX(wb, "在华客人.xlsx");
}}>导出excel</a></Divider>
<Table id="table_to_xlsx" pagination={false} loading={inchina_data.loading} dataSource={inchina_data.data_detail} scroll={{x: 1200 }} columns={
<Table id="table_to_xlsx" pagination={false} dataSource={inchina_data.data_detail} columns={
[
{
title: '订单号',

@ -1,9 +1,19 @@
import React, {useContext, useEffect} from 'react';
import {Row, Col, Divider, Table} from 'antd';
import {Row, Col, Button, Divider, Table, Space, Radio, Tooltip} from 'antd';
import {
ContainerOutlined,
SearchOutlined,
} from '@ant-design/icons';
import {stores_Context} from '../config';
import {Line} from "@ant-design/charts";
import {observer} from 'mobx-react';
import DatePickerCharts from '../components/search/DatePickerCharts';
import {NavLink, useParams} from "react-router-dom";
import * as comm from "../utils/commons";
import * as config from "../config";
import SiteSelect from "../components/search/SiteSelect";
import GroupSelect from "../components/search/GroupSelect";
import {utils, writeFileXLSX} from "xlsx";
import SearchForm from './../components/search/SearchForm';
const Customer_care_potential = () => {
@ -11,41 +21,43 @@ const Customer_care_potential = () => {
const potential_data = customer_store.potential_data;
useEffect(() => {
}, []);
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"}>
<Col className="gutter-row" span={24}>
<SearchForm className={date_picker_store.siderBroken ? "" : "sticky-top"}
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...potential_data.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: true, mode: 'multiple' },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'potential_data');
customer_store.potential_customer_order();
customer_store.potential_customer_order(true);
}}
/>
</Col>
</Row>
<Row gutter={16} >
<Col span={24}>
<Row>
<Col span={8}>
<h2>潜力客户</h2>
</Col>
{/* <Col span={1}></Col> */}
<Col span={15}>
<Row>
<Col span={6}>
<SiteSelect store={potential_data} show_all={true}/>
</Col>
<Col span={18}> <GroupSelect store={potential_data}/></Col>
<Col span={24}> <Space>
<DatePickerCharts hide_vs={true}/>
<Radio.Group value={potential_data.date_type}
onChange={potential_data.onChange_datetype}>
<Radio value="applyDate">预定日期</Radio>
<Radio value="startDate">出发日期</Radio>
<Radio value="ConfirmDate">确认日期</Radio>
</Radio.Group>
<Button type="primary" icon={<SearchOutlined/>} loading={potential_data.loading}
onClick={() => {
potential_data.potential_customer_order();
potential_data.potential_customer_order(true);
}}>统计</Button>
</Space>
</Col>
</Row>
</Col>
<Col span={1}></Col>
<Col span={24}>
<Table dataSource={potential_data.data} loading={potential_data.loading} columns={
<Table dataSource={potential_data.data} columns={
[
{
title: '订单数',
@ -83,7 +95,7 @@ const Customer_care_potential = () => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx").getElementsByTagName('table')[0]);
writeFileXLSX(wb, "潜力客户.xlsx");
}}>导出excel</a></Divider>
<Table id="table_to_xlsx" pagination={false} loading={potential_data.loading} dataSource={potential_data.data_detail} scroll={{x: 1200 }} columns={
<Table id="table_to_xlsx" pagination={false} dataSource={potential_data.data_detail} columns={
[
{
title: '订单号',
@ -153,9 +165,9 @@ const Customer_care_potential = () => {
key: 'SourceType',
},
{
title: '页面类型',
dataIndex: 'COLI_LineClass',
key: 'COLI_LineClass',
title: '在华',
dataIndex: 'ZH',
key: 'ZH',
},
]
} size="small"

@ -1,353 +1,189 @@
import React, { useContext, useState } from 'react';
import { Row, Col, Divider, Table, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { utils, writeFileXLSX } from 'xlsx';
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 React, {useContext, useEffect} from 'react';
import {Row, Col, Button, Divider, Table, Space, Radio, Tooltip} from 'antd';
import {read, utils, writeFileXLSX} from 'xlsx';
import {
ContainerOutlined,
SearchOutlined,
} from '@ant-design/icons';
import {stores_Context} from '../config';
import {Line} from "@ant-design/charts";
import {observer} from 'mobx-react';
import DatePickerCharts from '../components/search/DatePickerCharts';
import {NavLink, useParams} from "react-router-dom";
import * as comm from "../utils/commons";
import * as config from "../config";
import SiteSelect from "../components/search/SiteSelect";
import GroupSelect from "../components/search/GroupSelect";
const Customer_care_regular = () => {
const { orders_store, date_picker_store, customer_store } = useContext(stores_Context);
const regular_data = customer_store.regular_data;
// useEffect(() => {}, []);
const {orders_store, date_picker_store, customer_store} = useContext(stores_Context);
const regular_data = customer_store.regular_data;
const columns = [
{
title: '订单号',
dataIndex: 'COLI_ID',
key: 'COLI_ID',
},
{
title: '预定日期',
dataIndex: 'COLI_ApplyDate',
key: 'COLI_ApplyDate',
},
{
title: '订单状态',
width: '4rem',
dataIndex: 'OrderState1',
key: 'OrderState1',
render: (text, record) => record.OrderState === 1 ? '成行' : '未成行',
sorter: (a, b) => b.OrderState - a.OrderState,
},
{
title: '毛利',
dataIndex: 'ML',
key: 'ML',
},
{
title: '人数',
dataIndex: 'PersonNum',
key: 'PersonNum',
},
{
title: '天数',
dataIndex: 'COLI_Days',
key: 'COLI_Days',
},
{
title: '人天数',
dataIndex: 'CGI_PersonDays',
key: 'CGI_PersonDays',
},
{
title: '走团日期',
dataIndex: 'COLI_OrderStartDate',
key: 'COLI_OrderStartDate',
},
{
title: '走团国家',
dataIndex: 'recommend_country',
key: 'recommend_country',
width: '4em',
},
{
title: '小组',
dataIndex: 'Department',
key: 'Department',
},
{
title: '老客户',
dataIndex: 'COLI_IsOld',
key: 'COLI_IsOld',
},
{
title: '老客户推荐',
dataIndex: 'COLI_IsCusCommend',
key: 'COLI_IsCusCommend',
},
{
title: '国籍',
dataIndex: 'MEI_Country',
key: 'MEI_Country',
},
{
title: '网站',
dataIndex: 'COLI_WebCode',
key: 'COLI_WebCode',
},
{
title: '来源',
dataIndex: 'SourceType',
key: 'SourceType',
},
{
title: '页面类型',
dataIndex: 'COLI_LineClass',
key: 'COLI_LineClass',
},
{
title: '券额',
dataIndex: 'Voucher_amount',
key: 'Voucher_amount',
width: '4em',
},
{
title: '券类别',
dataIndex: 'Voucher_type',
key: 'Voucher_type',
width: '5em',
},
{
title: '上次 订单号',
dataIndex: 'coli_id_Last',
key: 'coli_id_Last',
width: '5em',
onCell: (r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '上次 走团日期',
dataIndex: 'COLI_OrderStartDate_Last',
key: 'COLI_OrderStartDate_Last',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' } ,
}),
},
{
title: '上次 小组',
dataIndex: 'Department_Last',
key: 'Department_Last',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' } ,
}),
},
];
const export_columns = [].concat(columns, [
{
title: '上次经过国家',
dataIndex: 'last_country',
key: 'last_country',
onCell: (r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '复购周期',
dataIndex: 'Repurchase_cycle',
key: 'Repurchase_cycle',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '下单次数',
dataIndex: 'Orders_number',
key: 'Orders_number',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '推荐次数',
dataIndex: 'recommend_time',
key: 'recommend_time',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '历史成行次数',
dataIndex: 'Travel_count',
key: 'Travel_count',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '旅行周期',
dataIndex: 'Travel_cycle',
key: 'Travel_cycle',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '第1次走团日期',
dataIndex: 'firstStartdate',
key: 'firstStartdate',
width: '4em',
onCell: (_, r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
]);
useEffect(() => {
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...regular_data.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
years: { hide_vs: true },
},
}}
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);
}
}}
/>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<h2>老客户</h2>
</Col>
<Col span={24}>
<Table
dataSource={regular_data.data}
loading={regular_data.loading}
columns={[
{
title: '统计条目',
dataIndex: 'ItemName',
key: 'ItemName',
},
{
title: () => (
<>
订单数{' '}
<Tooltip key='total_data_tips_title' title="总订单: 当同时勾选老客户和推荐时, 将重复计数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'OrderNum',
key: 'OrderNum',
render: (text, record, index) => (
<>
<span>{text}</span>&nbsp;&nbsp;
{<Tooltip key='total_data_tips' title={regular_data.total_data_tips}>
{index === 0 && regular_data.total_data_tips!=='' && <InfoCircleOutlined className='ant-tag-gold' />}
</Tooltip>}
</>
),
},
{
title: '订单数占比',
dataIndex: 'OrderRate',
key: 'OrderRate',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
},
{
title: '成行数',
dataIndex: 'SUCOrderNum',
key: 'SUCOrderNum',
},
{
title: '成行率',
dataIndex: 'SUCRate',
key: 'SUCRate',
render: (text) => typeof text === 'number'?<span>{Math.round(text * 100)}%</span>:text,
},
{
title: '毛利',
dataIndex: 'ML',
key: 'ML',
},
{
title: '毛利占比',
dataIndex: 'OrderMLRate',
key: 'OrderMLRate',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
},
{
title: '人数(含成人+儿童)',
dataIndex: 'PersonNum',
key: 'PersonNum',
},
]}
size="small"
pagination={false}
rowKey={(record) => record.ItemName}
/>
</Col>
}, []);
<Col span={24}>
<LineWithAvg dataSource={regular_data.pivotData} loading={regular_data.detail_loading} xField={regular_data.pivotX} yField={regular_data.pivotY}
seriesField='_ylabel' showCompareSum={regular_data.showCompareSum} solidLineTime={regular_data.solidLineTime} solidLineCompareTime={regular_data.solidLineCompareTime}
solidLineDash={regular_data.solidLineDash} isCompareLine={regular_data.isCompareLine}/>
</Col>
<Col span={24}>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]);
writeFileXLSX(wb, '老客户.xlsx');
}}
>
导出下表
</a>
<TableExportBtn btnTxt='导出详情' label={'老客户-详情'} columns={export_columns} dataSource={regular_data.data_detail} style={{ marginLeft: '10px' }} />
</Divider>
<Table
id="table_to_xlsx"
pagination={false}
loading={regular_data.detail_loading}
dataSource={regular_data.data_detail}
scroll={{ x: 1200 }}
columns={columns}
size="small"
rowKey={(record) => record.COLI_ID}
/>
</Col>
</Row>
</div>
);
return (
<div>
<Row>
<Col span={8}>
<h2>老客户</h2>
</Col>
<Col span={15}>
<Row>
<Col span={6}>
<SiteSelect store={regular_data} show_all={true}/>
</Col>
<Col span={18}> <GroupSelect store={regular_data}/></Col>
<Col span={24}> <Space>
<DatePickerCharts hide_vs={true}/>
<Radio.Group value={regular_data.date_type}
onChange={regular_data.onChange_datetype}>
<Radio value="applyDate">预定日期</Radio>
<Radio value="startDate">出发日期</Radio>
<Radio value="ConfirmDate">确认日期</Radio>
</Radio.Group>
<Button type="primary" icon={<SearchOutlined/>} loading={regular_data.loading}
onClick={() => {
regular_data.regular_customer_order();
regular_data.regular_customer_order(true);
}}>统计</Button>
</Space>
</Col>
</Row>
</Col>
<Col span={1}></Col>
<Col span={24}>
<Table dataSource={regular_data.data} columns={
[
{
title: '统计条目',
dataIndex: 'ItemName',
key: 'ItemName',
},
{
title: '订单数',
dataIndex: 'OrderNum',
key: 'OrderNum',
},
{
title: '成行数',
dataIndex: 'SUCOrderNum',
key: 'SUCOrderNum',
},
{
title: '成行率',
dataIndex: 'SUCRate',
key: 'SUCRate',
render: (text, record) => <span>{Math.round(text * 100)}%</span>
},
{
title: '毛利',
dataIndex: 'ML',
key: 'ML',
},
{
title: '人数(含成人+儿童)',
dataIndex: 'PersonNum',
key: 'PersonNum',
},
]
} size="small" pagination={false} rowKey={record => record.ItemName}
/>
</Col>
<Col span={24}>
<Divider orientation="right" plain><a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx").getElementsByTagName('table')[0]);
writeFileXLSX(wb, "老客户.xlsx");
}}>导出excel</a></Divider>
<Table id="table_to_xlsx" pagination={false} dataSource={regular_data.data_detail} columns={
[
{
title: '订单号',
dataIndex: 'COLI_ID',
key: 'COLI_ID',
},
{
title: '预定日期',
dataIndex: 'COLI_ApplyDate',
key: 'COLI_ApplyDate',
},
{
title: '订单状态',
dataIndex: 'OrderState',
key: 'OrderState',
render: (text, record) => <span>{text == 1 ? '成行' : '未成行'}</span>,
sorter: (a, b) => b.OrderState - a.OrderState,
},
{
title: '毛利',
dataIndex: 'ML',
key: 'ML',
},
{
title: '人数',
dataIndex: 'PersonNum',
key: 'PersonNum',
},
{
title: '天数',
dataIndex: 'COLI_Days',
key: 'COLI_Days',
},
{
title: '人天数',
dataIndex: 'CGI_PersonDays',
key: 'CGI_PersonDays',
},
{
title: '走团日期',
dataIndex: 'COLI_OrderStartDate',
key: 'COLI_OrderStartDate',
},
{
title: '小组',
dataIndex: 'Department',
key: 'Department',
},
{
title: '老客户',
dataIndex: 'COLI_IsOld',
key: 'COLI_IsOld',
},
{
title: '老客户推荐',
dataIndex: 'COLI_IsCusCommend',
key: 'COLI_IsCusCommend',
},
{
title: '网站',
dataIndex: 'COLI_WebCode',
key: 'COLI_WebCode',
},
{
title: '来源',
dataIndex: 'SourceType',
key: 'SourceType',
},
{
title: '在华',
dataIndex: 'ZH',
key: 'ZH',
},
]
} size="small"
rowKey={record => record.COLI_ID}
/>
</Col>
</Row>
</div>
);
};
export default observer(Customer_care_regular);

@ -1,231 +0,0 @@
import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Table, Row, Col, Divider, Switch, Space, Tabs } from 'antd';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn, VSTag } from '../components/Data';
import { fixTo2Decimals, isEmpty } from '../utils/commons';
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const pivotOptions = [
{ key: 'operatorName', label: '顾问' },
{ key: 'country', label: '国籍' },
];
const pivotColOptions = [
{ key: 'hasOld', value: 'hasOld', label: '老客户+推荐' },
{ key: 'IsOld_txt', value: 'IsOld_txt', label: '老客户' },
{ key: 'isCusCommend_txt', value: 'isCusCommend_txt', label: '推荐' },
];
const CustomerCareRegularPivot = (props) => {
const { date_picker_store: searchFormStore, customer_store } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { loading, pivotResult, filterColValues, rawData } = customer_store.sales_regular_data;
const [pivotRow, setPivotRow] = useState('operatorName');
const [pivotCol, setPivotCol] = useState('hasOld');
const [pivotColLabel, setPivotColLabel] = useState('老客户+推荐');
const onTabsChange = (key) => {
setPivotRow(key);
customer_store.regular_data_pivot(key, pivotCol);
};
const [dataSource, setDataSource] = useState([]);
const [ifmerge, setIfmerge] = useState(false);
const [dataForExport, setDataForExport] = useState([]);
const [dataForExportS, setDataForExportS] = useState([]);
useEffect(() => {
if ( ! ifmerge) {
// setDataSource(pageData[pivotRow].data);
setDataSource(pivotResult);
// setDataForExport(
// // pageData[pivotRow].data.reduce(
// pivotResult.reduce(
// (r, c) =>
// r.concat(
// [{ ...c, children: undefined }],
// (c?.children || [])
// .reduce((rc, ele) => rc.concat([{ ...ele, [pivotRow]: ele.rowLabel }], [{ ...ele.vsData, [pivotRow]: ele.vsData.rowLabel, vsData: {} }]), [])
// .filter((ele) => ele.SumOrder !== undefined)
// ),
// []
// )
// );
setDataForExportS(pivotResult.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
} else {
// setDataSource(pageData[pivotRow].mergedData);
// setDataForExport(
// pageData[pivotRow].mergedData.reduce(
// (r, c) =>
// r.concat(
// [{ ...c, children: undefined }],
// c.children.reduce((rc, ele) => rc.concat([{ ...ele, operatorName: ele.rowLabel }], [{ ...ele.vsData, operatorName: ele.vsData.rowLabel, vsData: {} }]), [])
// .filter((ele) => ele.SumOrder !== undefined)
// ),
// []
// )
// );
// setDataForExportS(pageData[pivotRow].mergedData.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
}
return () => {};
}, [ifmerge, pivotResult]);
const rowColumns = [
{ title: '日期区间', dataIndex: 'seriesKey', key: 'seriesKey' },
{ title: '顾问', dataIndex: 'operatorName', key: 'operatorName' },
{ title: '订单号', dataIndex: 'o_id', key: 'o_id' },
{ title: '预定日期', dataIndex: 'applyDate', key: 'applyDate' },
{ title: '订单状态', key: 'orderState', render: (_, r) => r.orderState === '1' ? '成行' : '', },
{ title: '毛利', dataIndex: 'ML', key: 'ML' },
{ title: '人数', dataIndex: 'personNum', key: 'personNum' },
{ title: '天数', dataIndex: 'tourdays', key: 'tourdays' },
// { title: '', dataIndex: 'CGI_PersonDays', key: 'CGI_PersonDays' },
// { title: '', dataIndex: 'COLI_OrderStartDate', key: 'COLI_OrderStartDate' },
{ title: '小组', dataIndex: 'dept', key: 'dept' },
{ title: '老客户', key: 'IsOld', render: (_, r) => r.IsOld === '1' ? '是' : '' },
{ title: '老客户推荐', key: 'IsCusCommend', render: (_, r) => r.isCusCommend === '1' ? '是' : '' },
{ title: '网站', dataIndex: 'WebCode', key: 'WebCode' },
{ title: '来源', dataIndex: 'SourceType', key: 'SourceType' },
{ title: '页面类型', dataIndex: 'COLI_LineClass', key: 'COLI_LineClass' },
];
const calcDelta = (r, key) => !isEmpty(Number(r?.vsData?.[key])) ? fixTo2Decimals((Number(r[key] || 0) - Number(r.vsData[key]))/Number(r.vsData[key]) *100) : null;
const renderVS = (v, r, key) => {
const delta = calcDelta(r, key);
return <>
<Space direction={'vertical'}>
<span>
{v || 0}
{r?.vsData?.[key] ? <span type="secondary"> VS {r.vsData[key]}</span> : null}
</span>
{delta && <VSTag diffPercent={delta} />}
</Space>
</>;
};
const columns = [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em', render: (v, r) => renderVS(v, r, 'SumOrder') },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmOrder') },
{ key: 'ConfirmPersonNum', title: '✔人数(SUM)', dataIndex: 'ConfirmPersonNum', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmPersonNum') },
{ key: 'confirmTourdays', title: '✔团天数(AVG)', dataIndex: 'confirmTourdays', width: '5em', render: (v, r) => renderVS(v, r, 'confirmTourdays') },
{ key: 'SumML', title: '预计毛利', dataIndex: 'SumML', width: '5em', render: (v, r) => renderVS(v, r, 'SumML') }, // SumML_txt
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmRates') },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em', render: (v, r) => renderVS(v, r, 'SingleML') },
].map(c => ({...c, sorter: (a, b) => (a[c.key] - b[c.key])}));
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...customer_store.sales_regular_data.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
dates: { hide_vs: false },
},
}}
onSubmit={async (_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'sales_regular_data');
await customer_store.get_sales_regular_data_vs(obj, pivotRow);
customer_store.regular_data_pivot(pivotRow, pivotCol);
}}
/>
</Col>
</Row>
<Tabs tabBarExtraContent={{ left: (<div style={{ width: '6rem'}}></div>)}}
type={'card'}
activeKey={pivotRow}
onChange={onTabsChange}
items={pivotOptions.map((ele) => {
return {
...ele,
children: (
<>
{/* <h2>{ele.label}-老客户, 含推荐</h2> */}
<>
<Divider orientation={'right'} style={{backgroundColor: '#fff', margin: 0, padding: '10px 0'}} >
{/* {dataSource.length > 0 && pivotRow === 'operatorName' && (
<Switch
unCheckedChildren="各账户"
checkedChildren="合并"
key={'openOrMerge'}
checked={ifmerge}
onChange={(e) => {
setIfmerge(e);
}}
/>
)}
<Divider type={'vertical'} /> */}
<TableExportBtn
btnTxt="导出明细"
label={`${formValuesToSub.Date1}-老客户-明细`}
{...{ columns: [{ title: ele.label, dataIndex: pivotRow, key: pivotRow }, ...rowColumns], dataSource: rawData }}
/>
{/* <Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出下表-展开"
label={`${formValuesToSub.Date1}-${ele.label}.老客户`}
{...{ columns: [{ title: ele.label, dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExport }}
/> */}
<Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出下表"
label={`${formValuesToSub.Date1}-${ele.label}.${pivotColLabel}`}
{...{ columns: [{ title: ele.label, dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExportS }}
/>
</Divider>
</>
<Tabs style={{ backgroundColor: '#fff' }}
tabPosition={'left'} defaultActiveKey={'mix'} activeKey={pivotCol}
onChange={(sub) => {
setPivotCol(sub);
setPivotColLabel(pivotColOptions.find((ele) => ele.key === sub).label);
customer_store.regular_data_pivot(pivotRow, sub);
}}
items={pivotColOptions.map((col, i) => {
// const SubjectTableComponent = subjectComponents[ele.key];
return {
...col,
children: (
<Table
sticky
dataSource={dataSource}
loading={loading}
columns={[
{
key: ele.key,
title: ele.label,
dataIndex: ele.key,
width: '6em',
filters: filterColValues,
onFilter: (value, record) => value.includes(record[ele.key]),
filterSearch: true,
},
...columns,
]}
pagination={false}
/>)
};
})}
/>
</>
),
};
})}
/>
</>
);
};
export default observer(CustomerCareRegularPivot);

@ -1,45 +1,41 @@
import React, { Component } from 'react';
import { Table, } from 'antd';
import SearchForm from './../components/search/SearchForm';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import React, {Component} from 'react';
import {Table, Button, Space, Radio} from 'antd';
import {SearchOutlined} from '@ant-design/icons';
import GroupSelect from '../components/search/GroupSelect';
import DatePickerCharts from "../components/search/DatePickerCharts";
import {stores_Context} from "../config";
import {observer} from "mobx-react";
class MobileDeal extends Component {
static contextType = stores_Context;
static contextType = stores_Context;
constructor(props) {
super(props);
}
constructor(props) {
super(props);
}
render() {
const { dashboard_store, date_picker_store } = this.context;
const mobile_data = dashboard_store.mobile_data;
return (
<div>
<h2>移动成交</h2>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...mobile_data.mobileSearchValues,
},
shows: ['DateType', 'DepartmentList', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 24 },
WebCode: { show_all: true },
dates: { hide_vs: true, col: 12 },
DateType: { col: 6, disabledKeys: ['confirmDate'] },
},
}}
onSubmit={(_err, obj, form, str) => {
dashboard_store.setMobileSearchValues(obj, form);
mobile_data.asyncFetch();
}}
/>
<Table dataSource={mobile_data.data} columns={mobile_data.columns} pagination={false} size="small" />
</div>
);
}
render() {
const {dashboard_store} = this.context;
const mobile_data = dashboard_store.mobile_data;
return (
<div>
<h2>移动成交</h2>
<GroupSelect store={mobile_data}/>
<Space size="large">
<DatePickerCharts hide_vs={true}/>
<Radio.Group value={mobile_data.date_type} onChange={mobile_data.onChange_datetype}>
<Radio value="applyDate">预定日期</Radio>
<Radio value="startDate">出发日期</Radio>
</Radio.Group>
<Button type="primary" icon={<SearchOutlined/>} loading={mobile_data.loading} onClick={() => {
mobile_data.asyncFetch();
}}>统计</Button>
</Space>
<Table dataSource={mobile_data.data} columns={mobile_data.columns} pagination={false} size="small"/>
</div>
);
}
}
export default observer(MobileDeal);

@ -1,10 +1,16 @@
import React, { Component } from 'react';
import { Button, Space } from 'antd';
import { Row, Col, Button, Tabs, Table, Space } from 'antd';
import {
ContainerOutlined,
CarryOutOutlined,
SmileOutlined,
TagsOutlined,
GlobalOutlined,
SearchOutlined,
} from '@ant-design/icons';
import { stores_Context } from '../config';
import { Line } from '@ant-design/charts';
import SiteSelect from '../components/search/SiteSelect';
import { observer } from 'mobx-react';
import DatePickerCharts from '../components/search/DatePickerCharts';
import DateGroupRadio from '../components/DateGroupRadio';
@ -34,7 +40,7 @@ class Orders extends Component {
// xAxis: {
// type: 'timeCat',
// },
// smooth: true,
smooth: true,
legend: {
position: 'right-top',
title: {

@ -70,7 +70,7 @@ class OrdersTempTable extends Component {
<div>
<h2>临时订单数量</h2>
<SiteSelect store={ordersTemp_data} mode={'multiple'} />
<SiteSelect store={ordersTemp_data}/>
<Space><DatePickerCharts hide_vs={true}/>
<Button type="primary" icon={<SearchOutlined/>} loading={ordersTemp_data.loading}
onClick={() => {

@ -13,47 +13,32 @@ export default observer((props) => {
const maxKPI = Math.max(...(origin || []).map((ele) => (ele?.[targetField] || 0)));
const maxValue = Math.max(...(origin || []).map((ele) => ele[measureField]));
const _max = Math.max(maxKPI, maxValue);
const minValue = Math.min(...(origin || []).map((ele) => ele[measureField]));
const _min = Math.ceil(Math.min(0, minValue));
const sortData = origin.sort(sortBy(measureField)).slice(-itemLength);
//
const _parseData = sortData?.map((ele) => ({ ...ele,
[rangeField]: [_min, Math.ceil(_max / 0.9)],
[rangeField]: [0, Math.ceil(_max / 0.9)],
// [measureField]: [ele[measureField]],
[measureField]: ele[measureFieldArrKey] || [ele[measureField]],
[targetField]: (ele?.[targetField] || 0)
}));
return { _parseData, _max, _min };
return _parseData;
};
const dataMapped = dataSource.reduce((r, v) => ({...r, [v.groupsLabel]: v}), {});
const ifMergeTB = isEmpty(dataSource) ? false : !isEmpty(dataSource[0]?.[`${extProps.measureField}_arr`]);
const [parseData, setParseData] = useState([]);
const [maxV, setMaxV] = useState(0);
const [minV, setMinV] = useState(0);
useEffect(() => {
const _pdata = dataParser(dataSource);
setParseData(_pdata._parseData);
setMaxV(_pdata._max);
setMinV(_pdata._min);
setParseData(dataParser(dataSource));
return () => {};
}, [extProps.measureField, dataSource]);
const config = merge({
color: {
range: [].concat((minV < 0 ? ['#ffe4e4'] : []), [ '#FFF3E1', '#FFF3E1']),
range: [ '#FFF3E1', '#FFF3E1'],
// range: [ '#FFF3E1', '#FFF3E1', '#FFe0b0', '#bfeec8'], // '#FFbcb8', '#FFe0b0',
measure: ['#5B8FF9', '#61ddaa'],
target: '#FF9845',
},
bulletStyle: {
measure: (item, ...r) => {
if (item[extProps.measureField] < 0) {
return {
fill: '#F4664A',
};
}
},
},
label: {
target: false,
measure: {

@ -35,12 +35,6 @@ export default observer((props) => {
// xField: 'value',
// yField: 'year',
// seriesField: 'type',
xAxis: {
label: {
autoHide: false,
autoRotate: true,
},
},
label: {
// label
position: 'middle',
@ -68,6 +62,5 @@ export default observer((props) => {
},
annotations: [...annotationsLine],
}, extProps);
// console.log(config);
return <Column {...config} data={dataSource} />;
});

@ -1,8 +1,5 @@
import React, { useState, useEffect } from "react";
import { Tag, Button, message } from 'antd';
import { CaretUpOutlined, CaretDownOutlined, DownloadOutlined } from '@ant-design/icons';
import { utils, writeFile } from "xlsx";
import { isEmpty, getNestedValue } from "../utils/commons";
import { Tag } from 'antd';
import { CaretUpOutlined, CaretDownOutlined } from '@ant-design/icons';
/**
* @property diffPercent
@ -27,73 +24,3 @@ export const VSTag = (props) => {
</span>
);
};
/**
* 导出表格数据存为xlsx
* @property label 文件名字
* @property columns 表格列
* @property dataSource 表格数据
* @property btnTxt 按钮文字
*/
export const TableExportBtn = ({label, columns, dataSource, btnTxt, ...props}) => {
const output_name = `${label}`;
const [columnsMap, setColumnsMap] = useState([]);
const [summaryRow, setSummaryRow] = useState({});
useEffect(() => {
const r1 = columns.reduce((r, v) => ({
...r,
...(v.children ? v.children.reduce((rc, vc, ci) => ({
...rc,
...(vc?.titleX ? {[`${v?.titleX || v.title},${vc.titleX}`]: vc.titleX } : {[(v?.titleX || v.title) + (vc.key || ci || '')]: `${vc?.titleX || vc?.title || ''}`}),
}), {}) : {})
}), {});
const flatCols = columns.flatMap((v, k) =>
v.children ? v.children.map((vc, ci) => ({ ...vc, title: `${v?.titleX || v.title}` + (vc?.titleX ? `,${vc.titleX}` : (vc.key || ci || '')) })) : {...v, title: `${v?.titleX || v.title}`}
);
// .filter((c) => c.dataIndex)
// !['string', 'number'].includes(typeof vc.title) ? `${v?.titleX || v.title}` : `${v?.titleX || v.title}-${vc.title || ''}`
;
setColumnsMap(flatCols);
// console.log('flatCols', flatCols);
setSummaryRow(r1);
// console.log('summaryRow', r1);
return () => {};
}, [columns]);
const onExport = () => {
if (isEmpty(dataSource)) {
message.warning('无结果.');
return false;
}
const data = dataSource.map((item) => {
const itemMapped = columnsMap.reduce((sv, kset) => {
const export_val = typeof kset?.dataExport === 'function' ? kset.dataExport('', item) : null;
const render_val = typeof kset?.render === 'function' ? kset.render('', item) : null;
const data_val = kset?.dataIndex ? (Array.isArray(kset.dataIndex) ? getNestedValue(item, kset.dataIndex) : item[kset.dataIndex]) : undefined;
const x_val = item[`${kset.dataIndex}_X`];
// const _title = kset.title.replace('-[object Object]', '');
const v = { [kset.title]: x_val || export_val || data_val || render_val };
return { ...sv, ...v };
}, {});
return itemMapped;
});
const ws = utils.json_to_sheet([].concat(isEmpty(summaryRow) ? [] : [summaryRow], data), { header: columnsMap.filter((r) => r.dataIndex).map((r) => r.title) });
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, 'sheet');
writeFile(wb, `${output_name}.xlsx`);
};
return (
<Button {...props}
type="link"
icon={<DownloadOutlined />}
size="small"
disabled={false}
onClick={onExport}
>
{btnTxt || '导出excel'}
</Button>
);
};

@ -1,5 +1,5 @@
import moment from 'moment';
import { fixTo2Decimals, groupBy } from '../../utils/commons';
import { groupBy } from '../../utils/commons';
export const datePartOptions = [
{ label: '日', value: 'day' },
@ -83,7 +83,7 @@ export const parseDateType = (data, dateType = 'day', { dateKey, valueKey, serie
const dateRange = [min, max];
const summaryVal = everySeries[key].reduce((rows, row) => rows + row[valueKey], 0);
const retValue = _f === 'sum' ? summaryVal : _calcF(summaryVal, everySeries[key].length);
a.push({ groupKey: key, value: fixTo2Decimals(retValue), dateKey: dateRangeStr, dateRange, containDate, [seriesKey]: _seriesKey, [dateKey]: _dateKey });
a.push({ groupKey: key, value: retValue, dateKey: dateRangeStr, dateRange, containDate, [seriesKey]: _seriesKey, [dateKey]: _dateKey });
return a;
}, []);
const avgDiv = [...new Set(dateArr)].length;
@ -108,7 +108,7 @@ export const resultDataCb = (dataRaw, dateGroup, { data1, data2 }, fieldMapper,
const _data2 = data2 ? dataRaw[data2] : [];
const parse1 = parseDateType(_data1, dateGroup, fieldMapper);
const parseData1 = parse1.data.map((ele) => ({
[fieldMapper.dateKey]: ele[fieldMapper.dateKey] === 'Invalid date' ? '空日期' : ele[fieldMapper.dateKey],
[fieldMapper.dateKey]: ele[fieldMapper.dateKey],
[fieldMapper.valueKey]: ele.value,
[fieldMapper.seriesKey]: ele[fieldMapper.seriesKey],
groups: _data1[0].groups,
@ -118,7 +118,7 @@ export const resultDataCb = (dataRaw, dateGroup, { data1, data2 }, fieldMapper,
}));
const parse2 = parseDateType(_data2, dateGroup, fieldMapper);
const parseData2 = parse2.data.map((ele) => ({
[fieldMapper.dateKey]: ele[fieldMapper.dateKey] === 'Invalid date' ? '空日期' : ele[fieldMapper.dateKey],
[fieldMapper.dateKey]: ele[fieldMapper.dateKey],
// [fieldMapper.dateKey]: ele.groupKey,
[fieldMapper.valueKey]: ele.value,
[fieldMapper.seriesKey]: ele[fieldMapper.seriesKey],
@ -151,15 +151,8 @@ export const resultDataCb = (dataRaw, dateGroup, { data1, data2 }, fieldMapper,
[fieldMapper.dateKey]: keyMapped[ele[fieldMapper.dateKey]],
dateKey: ele.dateKey,
}));
const retData = [].concat(parseData1, reindexData2 ).map(ele => ({...ele,
[fieldMapper.dateKey]: data1KeyMappedStr[ele[fieldMapper.dateKey]] === 'Invalid date' ? '空日期' : (data1KeyMappedStr[ele[fieldMapper.dateKey]] || data2KeyMappedStr[ele[fieldMapper.dateKey]])}));
const retData = [].concat(parseData1, reindexData2 ).map(ele => ({...ele, [fieldMapper.dateKey]: data1KeyMappedStr[ele[fieldMapper.dateKey]] || data2KeyMappedStr[ele[fieldMapper.dateKey]]}));
const avg1 = parse1.avgVal;
// console.log('callback','\ndateGroup', dateGroup,
// '\nretData', retData,
// '\navg1', avg1,
// '\nparse2', parse2.avgVal,
// '\ndata1KeyMappedStr', data1KeyMappedStr,
// '\ndata2KeyMappedStr', data2KeyMappedStr
// );
cb(dateGroup, retData, avg1, parse2.avgVal);
// console.log('callback', dateGroup, retData, data1KeyMappedStr, data2KeyMappedStr);
cb(dateGroup, retData, avg1);
};

@ -1,240 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Row, Col, Spin } from 'antd';
import { Line } from '@ant-design/plots';
import { observer } from 'mobx-react';
import { dataFieldAlias } from '../libs/ht';
import DateGroupRadio from '../components/DateGroupRadio';
import { cloneDeep, groupBy, sortBy } from '../utils/commons';
export default observer((props) => {
const { dataSource: rawData, showAVG, showCompareSum, loading, solidLineTime,
solidLineDash, isCompareLine,solidLineCompareTime, ...config } = props;
const { xField, yField, yFieldAlias, seriesField } = config;
const [dataBeforeXChange, setDataBeforeXChange] = useState([]);
const [dataSource, setDataSource] = useState([]);
const [sumSeries, setSumSeries] = useState([]);
const line_config = {
// data: dataSource,
padding: 'auto',
xField,
yField,
seriesField,
// seriesField: 'rowLabel',
// xAxis: {
// type: 'timeCat',
// },
color: isCompareLine?['#17f485', '#1890ff','#17f485', '#1890ff',"#181414","#181414"]:undefined,
point: {
size: 4,
shape: "cicle",
},
lineStyle: (datum) => {
return {
stroke: isCompareLine?datum._ylabel.includes("总计")?"#181414":datum._ylabel.includes(solidLineDash) ? '#1890ff':'#17f485' : undefined, //
lineDash: isCompareLine?datum._ylabel.includes(solidLineTime) ? undefined:[4, 4] : undefined, // 线
};
},
yAxis: {
min: 0,
maxTickInterval: 5,
},
meta: {
...cloneDeep(dataFieldAlias),
},
// smooth: true,
label: {}, //
legend: {
position: 'right-top',
// title: {
// text: ' ' + dataSource.reduce((a, b) => a + b.SumOrder, 0),
// },
itemMarginBottom: 12, //
},
tooltip: {
customItems: (originalItems) => {
return originalItems
.map((ele) => ({ ...ele, valueR: ele.data[yField] }))
.sort(sortBy('valueR'))
.reverse();
},
},
};
const [lineConfig, setLineConfig] = useState(cloneDeep(line_config));
useEffect(() => {
resetX();
return () => {};
}, [rawData]);
useEffect(() => {
setLineConfig(cloneDeep(line_config));
return () => {};
}, [isCompareLine,solidLineTime]);
useEffect(() => {
if (lineChartX === 'day') {
setDataBeforeXChange(dataSource);
}
return () => {};
}, [dataSource]);
//
const [lineChartX, setLineChartX] = useState('day');
const orderCountDataMapper = { data1: 'data1', data2: undefined };
const orderCountDataFieldMapper = { 'dateKey': xField, 'valueKey': yField, 'seriesKey': seriesField, _f: 'sum' };
const resetX = () => {
setLineChartX('day');
setDataSource(rawData);
setDataBeforeXChange(rawData);
// ``线, ``线
const byDays = groupBy(rawData, xField);
const sumY = rawData.reduce((a, b) => a + b[yField], 0);
// const avgVal = Math.round(sumY / (Object.keys(byDays).length));
// const avgLine = [
// { type: 'text', position: ['start', avgVal], content: avgVal, offsetX: -15, style: { fill: '#F4664A', textBaseline: 'bottom' } },
// { type: 'line', start: [-10, avgVal], end: ['max', avgVal], style: { stroke: '#F4664A', lineDash: [2, 2] } },
// ];
// setLineConfig({ ...lineConfig, yField, xField, annotations: avgLine });
setLineConfig({ ...lineConfig, yField, xField,});
if (showCompareSum) {
const _sumLine = Object.keys(byDays).reduce((r, _d) => {
const summaryVal = byDays[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineTime)){
return rows + row[yField];
}
else{
return rows;
}
}
, 0);
const summaryCompareVal = byDays[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineCompareTime)){
return rows + row[yField];
}
else{
return rows;
}
}
, 0);
r.push({ ...byDays[_d][0], [yField]: summaryVal, [seriesField]: solidLineTime+'总计' });
r.push({ ...byDays[_d][0], [yField]: summaryCompareVal, [seriesField]: solidLineCompareTime+'总计' });
return r;
}, []);
setSumSeries(_sumLine);
}
else{
const _sumLine = Object.keys(byDays).reduce((r, _d) => {
const summaryVal = byDays[_d].reduce((rows, row) => rows + row[yField], 0);
r.push({ ...byDays[_d][0], [yField]: summaryVal, [seriesField]: '总计' });
return r;
}, []);
// console.log(_sumLine.map((ele) => ele[yField]));
setSumSeries(_sumLine);
}
};
const onChangeXDateFieldGroup = (value, data, avg1) => {
// console.log(value, data, avg1);
const _sumLine = [];
const { xField, yField, seriesField } = lineConfig;
const groupByDate = data.reduce((r, v) => {
(r[v[xField]] || (r[v[xField]] = [])).push(v);
return r;
}, {});
// console.log(groupByDate);
const _data = Object.keys(groupByDate).reduce((r, _d) => {
if (showCompareSum) {
const summaryVal = groupByDate[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineTime)){
return rows + row[yField];
}
else{
return rows;
}
}
, 0);
const summaryCompareVal = groupByDate[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineCompareTime)){
return rows + row[yField];
}
else{
return rows;
}
}
, 0);
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryVal, [seriesField]: solidLineTime+'总计' });
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryCompareVal, [seriesField]: solidLineCompareTime+'总计' });
}
else{
const summaryVal = groupByDate[_d].reduce((rows, row) => rows + row[yField], 0);
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryVal, [seriesField]: '总计' });
}
const xAxisGroup = groupByDate[_d].reduce((a, v) => {
(a[v[seriesField]] || (a[v[seriesField]] = [])).push(v);
return a;
}, {});
// console.log(xAxisGroup);
Object.keys(xAxisGroup).map((_group) => {
const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row[yField], 0);
r.push({ ...xAxisGroup[_group][0], [yField]: summaryVal, });
return _group;
});
return r;
}, []);
// const _sum = Object.values(groupBy(_data, 'dateGroup')).reduce((ac, b) => ({...b, [yField]: 0}), {});
// console.log(xField, avg1);
// console.log('date source=====', _data);
setLineChartX(value);
setDataSource(_data);
setSumSeries(_sumLine);
// setAvgLine1(avg1);
// const avg1Int = Math.round(avg1);
// const mergedConfig = { ...lineConfig,
// annotations: [
// { type: 'text', position: ['start', avg1Int], content: avg1Int, offsetX: -15, style: { fill: '#F4664A', textBaseline: 'bottom' } },
// { type: 'line', start: [-10, avg1Int], end: ['max', avg1Int], style: { stroke: '#F4664A', lineDash: [2, 2] } },
// ],
// };
// console.log(mergedConfig);
// setLineConfig(mergedConfig);
setLineConfig(cloneDeep(line_config));
};
return (
<section>
<Row gutter={16} justify={'space-between'} className="mb-1">
<Col flex={'auto'}>
<h3>
走势: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</h3>
</Col>
<Col style={{ textAlign: 'right' }} align={'end'}>
<DateGroupRadio
visible={true}
dataRaw={{ data1: dataBeforeXChange }}
onChange={onChangeXDateFieldGroup}
value={lineChartX}
dataMapper={orderCountDataMapper}
fieldMapper={orderCountDataFieldMapper}
/>
</Col>
</Row>
<Spin spinning={loading}>
<Line {...lineConfig} data={[].concat(dataSource, sumSeries)} />
</Spin>
</section>
);
});

@ -4,100 +4,44 @@ import { merge, isEmpty, groupBy, sortBy } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
export default observer((props) => {
const { dataSource, showKPI, ...config } = props;
const { dataSource, ...config } = props;
const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v;
const seriesData = groupBy(dataSource, ele => ele[config.seriesField]);
const splitData = showKPI ? dataSource.reduce((r, v) => {
const splitData = dataSource.reduce((r, v) => {
r.push(v);
if ( ! isEmpty(v[kpiKey])) { // 线, 线
r.push({...v, [config.yField]: v[kpiKey], [config.seriesField]: `${v[config.seriesField]} ${dataFieldAlias[kpiKey].label}`, extraLine: true,});
}
return r;
}, []).sort(sortBy(config.xField)) : (dataSource.slice()).sort(sortBy(config.xField));
}, []).sort(sortBy(config.xField));
const dataColors = [
"#5D7092","#F6BD16","#6F5EF9","#6DC8EC","#945FB9","#FF9845","#1E9493",
"#FF99C3","#FF6B3B","#626681","#FFC100","#9FB40F","#76523B","#DAD5B5",
"#0E8E89","#E19348","#F383A2","#247FEA","#5B8FF9","#5AD8A6",
"#5B8FF9","#5AD8A6","#5D7092","#F6BD16","#6F5EF9","#6DC8EC","#945FB9","#FF9845","#1E9493",
"#FF99C3","#FF6B3B","#626681","#FFC100","#9FB40F","#76523B","#DAD5B5","#0E8E89","#E19348",
"#F383A2","#247FEA",
];
const colorSets = Object.keys(seriesData)
.sort()
.filter((ele) => !ele.includes(' '))
.reduce((obj, k, i) => ({ ...obj, [k]: dataColors[i] || dataColors[i % 20] }), {});
// console.log('colorSets', colorSets);
const mergeLineConfig = merge(
{
color: (item) => {
const thisSeries = item[config.seriesField]?.split(' ')?.[0];
return colorSets[thisSeries];
},
lineStyle: (data) => {
if (data[config.seriesField].includes('目标')) {
return {
lineDash: [8, 20],
opacity: 0.5,
};
}
if (data[config.seriesField].includes('@')) {
return {
lineDash: [4, 8],
opacity: 0.6,
lineWidth: 1.5,
};
}
const colorSets = Object.keys(seriesData).sort().reduce((obj, k, i) => ({...obj, [k]: dataColors[i]}), {});
const mergeLineConfig = merge({
color: (item) => {
const thisSeries = item[config.seriesField]?.split(' ')?.[0];
return colorSets[thisSeries];
// return thisSeries.includes('') ? '#F4664A' : colorSets[thisSeries];
},
lineStyle: (data) => {
if (data[config.seriesField].includes('目标')) {
return {
opacity: 1,
lineDash: [4, 8],
opacity: 0.7,
};
},
legend: {
custom: true,
items: Object.keys(seriesData)
.map((ele) => ({
id: ele,
name: ele,
value: ele,
marker: {
symbol: ele.includes(' ') ? 'hyphen' : 'circle',
style: { fill: colorSets[ele], stroke: colorSets[ele?.split(' ')?.[0]] || '#5B8FF9', r: 3, lineWidth: 2, color: colorSets[ele] },
},
}))
.sort(sortBy('name')),
},
tooltip: {
// title: dataFieldAlias[config.yField]?.alias,
showTitle: true,
customItems: (items) => items.sort(sortBy('name')).map((ele) => ({ ...ele, title: `${ele.title} ${dataFieldAlias[config.yField]?.alias}` })),
},
// annotations: [
// // 0
// {
// type: 'regionFilter',
// start: ['min', 0],
// end: ['max', 0],
// color: '#F4664A',
// },
// {
// type: 'text',
// position: ['min', 0],
// content: '0',
// offsetY: -4,
// style: {
// textBaseline: 'bottom',
// },
// },
// {
// type: 'line',
// start: ['min', 0],
// end: ['max', 0],
// style: {
// stroke: '#F4664A',
// lineDash: [2, 2],
// },
// },
// ],
}
return {
opacity: 1,
};
},
legend: {
custom: true,
items: Object.keys(seriesData).map((ele) => ({ id: ele, name: ele, value: ele, marker: { symbol: 'circle', style: { fill: colorSets[ele], color: colorSets[ele] } } })),
},
config
);
}, config);
return <Line {...mergeLineConfig} data={splitData} />;
});

@ -1,136 +0,0 @@
import { useContext, useState, useEffect, useMemo } from 'react';
import { observer } from 'mobx-react';
import { ChoroplethMap } from '@ant-design/maps';
import { dataFieldAlias } from '../libs/ht';
import { cloneDeep } from '../utils/commons';
export default observer((props) => {
const { dataSource, sourceField, valueField, containerNode, ...extConfig } = props;
const [mdataSource, setMdataSource] = useState([]);
useEffect(() => {
const dataMapped = (cloneDeep(dataSource) || []).reduce((r, v) => ({...r,
[(v[sourceField] || '_').replace('(待删除)', '')]: v
}), {});
if (dataMapped?.['中国']) {
dataMapped['中国'][sourceField] = '中华人民共和国';
}
setMdataSource(Object.values(dataMapped));
return () => {};
}, [dataSource, valueField]);
const config = {
container: containerNode || '#topC',
map: {
// type: 'amap',
type: 'mapbox',
// style: 'blank',
center: [120.19382669582967, 30.258134],
zoom: 2,
pitch: 0,
// scrollZoom: false,
// dragPan: false,
// zoomEnable: false,
// token: 'd78b5ba25a4699a1cb567b7a933e630b', // amap
},
// geoArea: {
// url: 'https://gw.alipayobjects.com/os/alisis/geo-data-v0.1.2/choropleth-data',
// type: 'topojson',
// },
source: {
data: mdataSource.filter((ele) => ele[sourceField] && ele[valueField] !== 0 ),
joinBy: {
geoField: 'name',
sourceField: sourceField || 'name',
},
},
autoFit: true,
color: {
field: valueField || 'value',
value: [
'#820C1B',
'#a31022',
'#ac2738',
'#b53f4e',
'#be5764',
'#c76f7a',
'#d18790',
'#da9fa6',
'#e3b7bc',
'#eccfd2',
'#f5e7e8',
'#fde7ea',
// '#8a1313',
// '#ad1818',
// '#b52f2f',
// '#bd4646',
// '#c55d5d',
// '#cd7474',
// '#d68b8b',
// '#dea2a2',
// '#e6b9b9',
// '#eed0d0',
// '#f6e7e7',
// '#fde7ea',
// '#001D70',
// '#0047A5',
// '#1A4397',
// '#2555B7',
// '#3165D1',
// '#3D76DD',
// '#467BE8',
// '#6296FE',
// '#7EA6F9',
// '#98B7F7',
// '#BDD0F8',
// '#DDE6F7',
// '#F2F5FC'
].reverse(),
scale: { type: 'quantile' },
},
viewLevel: {
// level: 'country',
// adcode: '100000',
// granularity: 'province',
level: 'world',
adcode: 'all',
granularity: 'country',
},
chinaBorder: false,
style: {
opacity: 1,
stroke: '#fff',
lineWidth: 0.6,
lineOpacity: 1,
},
state: {
active: {
stroke: 'yellow',
lineWidth: 0.6,
// lineOpacity: 0.8,
},
},
label: {
visible: true,
field: 'name',
style: {
fill: '#000',
opacity: 0.8,
fontSize: 10,
stroke: '#fff',
strokeWidth: 1.5,
textAllowOverlap: false,
padding: [8, 8],
},
},
tooltip: {
items: ['name', { field: valueField, alias: dataFieldAlias[valueField].alias, customValue: (v) => dataFieldAlias[valueField].formatter(v) }],
},
zoom: false,
legend: false,
};
return <ChoroplethMap {...config} />;
});

@ -1,265 +0,0 @@
import { observer } from 'mobx-react';
import { message } from 'antd';
import { Mix, getCanvasPattern, } from '@ant-design/plots';
import { merge, isEmpty, cloneDeep } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
const COLOR_SETS = [
"#FF6B3B",
"#9FB40F",
"#76523B",
"#DAD5B5",
"#E19348",
"#F383A2",
];
const COLOR_SETS2 = [
"#5B8FF9",
"#61DDAA",
"#65789B",
];
/**
* 订单数, 团数: 柱形图
* 成交率: 折线图
*/
export default observer((props) => {
const { dataSource, summaryData: areaData, ...config } = props;
const { xField, yFields, colFields, lineFields, seriesField, tooltip, ...extConfig } = config;
const diffData0 = dataSource.reduce((r, row) => {
r.push({ ...row, yField: row[colFields[0]], yGroup: dataFieldAlias[colFields[0]].alias });
r.push({ ...row, yField: row[colFields[1]], yGroup: dataFieldAlias[colFields[1]].alias });
return r;
}, []);
const diffData1 = dataSource.reduce((r, row) => {
r.push({ ...row, yField: row[lineFields[1]], yGroup: dataFieldAlias[lineFields[1]].alias });
return r;
}, []);
const calcAxis = isEmpty(diffData0) ? 300 : (Math.max(...diffData0.map(ele => ele.yField))) * 3;
// const calcAxisC = isEmpty(diffData0) ? 300 : (Math.max(...diffDataPercent.map(ele => ele.yField))) * 3;
const diffLine = [
// {
// type: 'text',
// position: ['start', 0],
// content: `, `,
// offsetX: -15,
// style: {
// fill: COLOR_SETS[0],
// textBaseline: 'bottom',
// },
// },
// {
// type: 'line',
// start: [-10, 0],
// end: ['max', 0],
// style: {
// stroke: COLOR_SETS[0],
// // lineDash: [2, 2],
// lineWidth: 0.5,
// },
// },
];
const pattern = (datum, color) => {
return getCanvasPattern({
type: String(datum.yGroup).includes(' ') ? 'line' : '',
cfg: {
backgroundColor: color,
},
});
};
const MixConfig = {
appendPadding: 15,
height: 400,
syncViewPadding: true,
tooltip: {
shared: true,
// customItems: (originalItems) => {
// // process originalItems,
// const items = originalItems.map((ele) => ({ ...ele, name: ele.data?.extraLine ? ele.name : `${ele.name} ${dataFieldAlias[yField]?.alias || yField}` }));
// return items;
// },
},
legend: {
position: 'top',
layout: 'horizontal',
custom: true,
items: [
...['团数', '订单数'].map((ele, ei) => ({
name: `${ele}`,
value: `${ele}`,
marker: {
symbol: 'square',
style: {
fill: COLOR_SETS2[ei],
r: 5,
},
},
})),
...['', '成行率'].map((ele, ei) => ({ // '',
name: `${ele}`,
value: `${ele}`,
marker: {
symbol: 'hyphen',
style: {
stroke: COLOR_SETS[ei],
r: 5,
lineWidth: 2
},
},
})),
],
},
// event: (chart, e) => {
// console.log('mix', chart, e);
// if (e.type === 'click') {
// props.itemClick(e);
// }
// },
onReady: (plot) => {
plot.on('plot:click', (...args) => {
// message.info(', ');
});
plot.on('element:click', (e) => {
const {
data: { data },
} = e;
// console.log('plot element', data);
props.itemClick(data);
});
// axis-label
plot.on('axis-label:click', (e, ...args) => {
const { text } = e.target.attrs;
// console.log(text);
props.itemClick({ [xField]: text });
});
},
plots: [
{
type: 'column',
options: {
data: diffData0,
isGroup: true,
xField,
yField: 'yField',
seriesField: 'yGroup',
// xAxis: false,
meta: merge({
...cloneDeep(dataFieldAlias),
}),
// color: '#b32b19',
// color: '#f58269',
legend: false, // {},
// smooth: true,
yAxis: {
type: 'linear',
tickCount: 4,
min: 0,
max: calcAxis,
title: { text: '团数', autoRotate: false, position: 'end' },
},
xAxis: {
label: {
autoHide: false,
autoRotate: true,
},
},
label: false,
color: COLOR_SETS2,
pattern,
},
},
{
type: 'line',
options: {
data: diffData1,
isGroup: true,
xField,
yField: 'yField',
seriesField: 'yGroup',
xAxis: false,
legend: false, // {},
meta: merge(
{
...cloneDeep(dataFieldAlias),
},
{ yField: dataFieldAlias[lineFields[1]] }
),
// color: '#1AAF8B',
color: COLOR_SETS[1],
// smooth: true,
point: {
size: 4,
shape: 'cicle',
},
yAxis: {
type: 'linear',
// tickCount: 4,
min: 0,
position: 'right',
line: null,
grid: null,
title: { text: dataFieldAlias[lineFields[1]].label, autoRotate: false, position: 'end' },
},
label: {
style: {
fontWeight: 700,
stroke: '#fff',
lineWidth: 1,
},
},
lineStyle: (datum) => {
if (String(datum.yGroup).includes(' ')) {
return {
lineDash: [4, 4],
opacity: 0.75,
};
}
return {
opacity: 1,
};
},
},
},
// {
// type: 'column',
// options: {
// data: diffData2,
// xField,
// yField: 'yField',
// seriesField: 'yGroup',
// columnWidthRatio: 0.28,
// meta: {
// // yField: {
// // formatter: (v) => `${v}%`,
// // },
// },
// isGroup: true,
// xAxis: false,
// yAxis: {
// line: null,
// grid: null,
// label: false,
// position: 'left',
// // min: -calcAxisC,
// // max: calcAxisC/4,
// min: -3000,
// max: 250,
// tickCount: 4,
// },
// legend: false, // {},
// color: COLOR_SETS,
// // annotations: diffLine,
// minColumnWidth: 5,
// maxColumnWidth: 5,
// // ()
// dodgePadding: 1,
// // ()
// // intervalPadding: 20,
// },
// },
],
};
return <Mix {...MixConfig} />;
});

@ -3,6 +3,26 @@ import { Mix } from '@ant-design/plots';
import { merge, isEmpty, groupBy, cloneDeep } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
const uniqueByKey = (array, key, pickLast) => {
const seen = new Map();
const isPickLast = pickLast === true;
return array.filter((item) => {
const k = item[key];
const storedItem = seen.get(k);
if (storedItem) {
if (isPickLast) {
seen.set(k, item); // update with last item
}
return false;
}
seen.set(k, item);
return true;
});
};
export default observer((props) => {
const { dataSource, summaryData: areaData, ...config } = props;
const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v;
@ -31,17 +51,11 @@ export default observer((props) => {
shape: 'cicle',
},
xAxis: false,
yAxis: {
line: null,
grid: null,
label: false,
position: 'left',
min: 0,
},
yAxis: false,
meta: {
[yField]: {
sync: true,
},
}
},
// color: ['#598cf3', '#69deae', '#F4664A', '#FAAD14'],
color: (item) => {
@ -84,52 +98,23 @@ export default observer((props) => {
yField,
seriesField,
xAxis: false,
yAxis: {
// line: null,
// grid: null,
// label: false,
position: 'left',
min: 0,
},
meta: merge(
{
...cloneDeep(dataFieldAlias),
},
{ [xField]: { sync: true }, [yField]: { sync: true } }
),
// color: '#b32b19',
color: '#f58269',
// color: (datum) => {
// console.log('color', datum, String(datum[seriesField]).includes(''));
// return String(datum[seriesField]).includes('') ? '#f7a593' : '#f58269';
// }, // '#f58269',
// smooth: true,
line: {
size: 1,
style: (datum) => {
return String(datum[seriesField]).includes('对比')
? {
// lineWidth: 0.1,
lineDash: [4, 5],
stroke: '#f7a593',
}
: {
stroke: '#f58269',
};
},
smooth: true,
areaStyle: () => {
return {
fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#f7a593',
};
},
areaStyle: (datum) => {
// console.log('areaStyle', datum);
return String(datum[seriesField]).includes(' ')
? {
fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#fac9bd',
}
: {
fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#f7a593',
// lineWidth: 0.1,
// lineOpacity: 0.5,
};
label: {
offsetY: -8,
},
label: (datum) => ({ offsetY: -8 }),
annotations: areaData.map((d) => {
return {
type: 'dataMarker',
@ -137,7 +122,7 @@ export default observer((props) => {
point: {
style: {
stroke: '#F4664A',
lineWidth: 0.5,
lineWidth: 1.5,
},
},
};

@ -1,5 +1,5 @@
import { observer } from 'mobx-react';
import { Mix, getCanvasPattern } from '@ant-design/plots';
import { Mix } from '@ant-design/plots';
import { merge, isEmpty, cloneDeep } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
@ -11,10 +11,6 @@ const COLOR_SETS = [
"#E19348",
"#F383A2",
];
const COLOR_SETS2 = [
"#5B8FF9",
"#61DDAA",
"#65789B",];
/**
* 当期数据; 同比; 环比
*/
@ -66,18 +62,7 @@ export default observer((props) => {
// lineDash: [2, 2],
lineWidth: 0.5,
},
},
];
const pattern = (datum, color) => {
return getCanvasPattern({
type: String(datum.yGroup).includes(' ') ? 'line' : '',
cfg: {
backgroundColor: color,
},
});
};
},];
const MixConfig = {
appendPadding: 15,
height: 600,
@ -90,58 +75,7 @@ export default observer((props) => {
// return items;
// },
},
legend: {
position: 'top',
layout: 'horizontal',
custom: true,
items: [
...['当期', '上期', '去年同期'].map((ele, ei) => ({
name: `${ele} 团数`,
value: `${ele} 团数`,
marker: {
symbol: 'square',
style: {
fill: COLOR_SETS2[ei],
r: 5,
},
},
})),
...['当期', '上期', '去年同期'].map((ele, ei) => ({
name: `${ele} 业绩`,
value: `${ele} 业绩`,
marker: {
symbol: 'hyphen',
style: {
stroke: COLOR_SETS2[ei],
r: 5,
lineWidth: 2
},
},
})),
...['环比', '同比'].map((ele, ei) => ({
name: `团数 ${ele}`,
value: `团数 ${ele}`,
marker: {
symbol: 'square',
style: {
fill: COLOR_SETS[ei],
r: 5,
},
},
})),
...['环比', '同比'].map((ele, ei) => ({
name: `业绩 ${ele}`,
value: `业绩 ${ele}`,
marker: {
symbol: 'square',
style: {
fill: COLOR_SETS[ei+2],
r: 5,
},
},
})),
],
},
legend: {position: 'top',layout: 'horizontal' },
plots: [
{
type: 'column',
@ -157,8 +91,8 @@ export default observer((props) => {
}),
// color: '#b32b19',
// color: '#f58269',
legend: false, // {},
// smooth: true,
legend: {},
smooth: true,
yAxis: {
type: 'linear',
tickCount: 4,
@ -173,8 +107,6 @@ export default observer((props) => {
},
},
label: false,
color: COLOR_SETS2,
pattern,
},
},
{
@ -186,7 +118,7 @@ export default observer((props) => {
yField: 'yField',
seriesField: 'yGroup',
xAxis: false,
legend: false, // {},
legend: {},
meta: merge(
{
...cloneDeep(dataFieldAlias),
@ -194,7 +126,7 @@ export default observer((props) => {
{ yField: dataFieldAlias[yFields[1]] }
),
// color: '#1AAF8B',
// smooth: true,
smooth: true,
point: {
size: 4,
shape: 'cicle',
@ -215,17 +147,6 @@ export default observer((props) => {
lineWidth: 1,
},
},
lineStyle: (datum) => {
if (String(datum.yGroup).includes(' ')) {
return {
lineDash: [4, 4],
opacity: 0.75,
};
}
return {
opacity: 1,
};
},
},
},
{
@ -254,16 +175,9 @@ export default observer((props) => {
max: 250,
tickCount: 4,
},
legend: false, // {},
legend: {},
color: COLOR_SETS,
annotations: diffLine,
minColumnWidth: 5,
maxColumnWidth: 5,
// ()
dodgePadding: 1,
// ()
// intervalPadding: 20,
},
},
],

@ -5,24 +5,23 @@ import { RingProgress, Progress, Bullet } from '@ant-design/plots';
import RcResizeObserver from 'rc-resize-observer';
import { stores_Context } from '../config';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import { Table, Space } from 'antd';
import { Table } from 'antd';
const { Statistic, Divider } = StatisticCard;
export default observer((props) => {
const { icon, traditional, biz, kpiVal, originVal, diff, ...extProps } = props;
const { icon, traditional, biz, kpiVal, originVal, ...extProps } = props;
const ValueIcon = props.icon;
const valueStyle = { color: '#3f8600' };
// const valueStyle = { color: (props?.VSrate || -1) < 0 ? '#3f8600' : '#cf1322' };
// const VSIcon = () => ((props?.VSrate || -1) < 0 ? <ArrowDownOutlined /> : <ArrowUpOutlined />);
const valueStyle = { color: (props?.VSrate || -1) < 0 ? '#3f8600' : '#cf1322' };
const VSIcon = () => ((props?.VSrate || -1) < 0 ? <ArrowDownOutlined /> : <ArrowUpOutlined />);
// console.log(props, ';;;;');
const [responsive, setResponsive] = useState(false);
const showMulti = traditional.value && biz.value;
const rangeMax = Math.max(originVal, kpiVal);
const bulletData = [
{
title: '',
ranges: [0, Math.ceil(rangeMax / 0.95)], // ,
// ranges: [0, kpiVal || (traditional.value + biz.value + 100 )],
ranges: [0, Math.ceil(originVal * 1.1)],
measures: [traditional.value, biz.value],
target: kpiVal || 0,
},
@ -71,16 +70,9 @@ export default observer((props) => {
valueStyle,
...extProps,
value: props.valueSuffix ? `${props.value} ${props.valueSuffix}` : props.value,
prefix: <ValueIcon twoToneColor={"#89B67F"} />,
description: diff ? (
<Space>
<Statistic title={diff.label} value={` ${diff.value}`} />
{diff.VSrate && <Statistic title="" value={`${diff.VSrate}%`} trend={diff.VSrate > 0 ? 'up' : 'down'} />}
</Space>
) : null,
prefix: <ValueIcon twoToneColor="#89B67F" />,
}}
chart={showMulti ? <Bullet data={bulletData} {...bulletConfig} layout={'horizontal'} />: false}
footer={null}
/>
</StatisticCard.Group>
</RcResizeObserver>

@ -1,9 +0,0 @@
import Icon, {} from '@ant-design/icons';
const CooperationSvg = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11.8611 2.39057C12.8495 1.73163 14.1336 1.71797 15.1358 2.35573L19.291 4.99994H20.9998C21.5521 4.99994 21.9998 5.44766 21.9998 5.99994V14.9999C21.9998 15.5522 21.5521 15.9999 20.9998 15.9999H19.4801C19.5396 16.9472 19.0933 17.9102 18.1955 18.4489L13.1021 21.505C12.4591 21.8907 11.6609 21.8817 11.0314 21.4974C10.3311 22.1167 9.2531 22.1849 8.47104 21.5704L3.33028 17.5312C2.56387 16.9291 2.37006 15.9003 2.76579 15.0847C2.28248 14.7057 2 14.1254 2 13.5109V6C2 5.44772 2.44772 5 3 5H7.94693L11.8611 2.39057ZM4.17264 13.6452L4.86467 13.0397C6.09488 11.9632 7.96042 12.0698 9.06001 13.2794L11.7622 16.2518C12.6317 17.2083 12.7903 18.6135 12.1579 19.739L17.1665 16.7339C17.4479 16.5651 17.5497 16.2276 17.4448 15.9433L13.0177 9.74551C12.769 9.39736 12.3264 9.24598 11.9166 9.36892L9.43135 10.1145C8.37425 10.4316 7.22838 10.1427 6.44799 9.36235L6.15522 9.06958C5.58721 8.50157 5.44032 7.69318 5.67935 7H4V13.5109L4.17264 13.6452ZM14.0621 4.04306C13.728 3.83047 13.3 3.83502 12.9705 4.05467L7.56943 7.65537L7.8622 7.94814C8.12233 8.20827 8.50429 8.30456 8.85666 8.19885L11.3419 7.45327C12.5713 7.08445 13.8992 7.53859 14.6452 8.58303L18.5144 13.9999H19.9998V6.99994H19.291C18.9106 6.99994 18.5381 6.89148 18.2172 6.68727L14.0621 4.04306ZM6.18168 14.5448L4.56593 15.9586L9.70669 19.9978L10.4106 18.7659C10.6256 18.3897 10.5738 17.9178 10.2823 17.5971L7.58013 14.6247C7.2136 14.2215 6.59175 14.186 6.18168 14.5448Z"></path></svg>
);
const CooperationIcon = (props) => <Icon component={CooperationSvg} {...props} />;
export default CooperationIcon;

@ -3,11 +3,12 @@ import { observer } from 'mobx-react';
import { stores_Context } from './../../config';
import { Typography, Row, Col, Tabs, } from 'antd';
import SearchForm from './../search/SearchForm';
import { KPIObjects } from './../../libs/ht';
import { merge, pick } from './../../utils/commons';
import { bu, KPIObjects, KPISubjects } from './../../libs/ht';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt, merge } from './../../utils/commons';
import ProfitTable from './SubjectTable/Profit';
import Count from './SubjectTable/Count';
import Rates from './SubjectTable/Rates';
import { toJS } from 'mobx';
const Todo = (props) => {
return <h2>TODO</h2>;
@ -31,7 +32,7 @@ export default observer((props) => {
const { sort, initialValue, hides, shows, fieldProps: _fieldProps } = {
sort: '',
// initialValue: '',
initialValue: pick(searchFormStore.formValues, ['DateType', 'year']),
initialValue: searchFormStore.formValues,
fieldProps: {},
hides: [],
shows: ['DateType', 'years'],

@ -228,7 +228,7 @@ export default observer((props) => {
<>
<Row gutter={16} className="mb-1 ">
<Col className="gutter-row mb-n1 p-none" span={24}>
<EditableProTable sticky pagination={{ pageSize: 10}}
<EditableProTable sticky
key={KPIStore.settingYear}
// headerTitle={``}
columns={columns}

@ -84,7 +84,6 @@ export default observer((props) => {
dataIndex: 'object_id',
editable: false,
width: '5em',
fixed: 'left',
render: (_, r) => r.object_name,
},
{
@ -93,7 +92,6 @@ export default observer((props) => {
valueType: 'digit',
fieldProps: { style: { width: '100%' }, step: 10000 * 100 },
width: '6em',
fixed: 'left',
formItemProps: {
style: { width: '100%' },
},
@ -231,7 +229,7 @@ export default observer((props) => {
<>
<Row gutter={16} className="mb-1 ">
<Col className="gutter-row mb-n1 p-none" span={24}>
<EditableProTable sticky pagination={{ pageSize: 10}}
<EditableProTable sticky
key={KPIStore.settingYear}
// headerTitle={``}
columns={columns}

@ -226,7 +226,7 @@ export default observer((props) => {
<>
<Row gutter={16} className="mb-1 ">
<Col className="gutter-row mb-n1 p-none" span={24}>
<EditableProTable sticky pagination={{ pageSize: 10}}
<EditableProTable sticky
key={KPIStore.settingYear}
// headerTitle={``}
columns={columns}

@ -35,7 +35,7 @@ class DataTypeSelect extends Component {
{...extProps}
>
{dateTypes.map((ele) => (
<Select.Option key={ele.key} value={ele.key} disabled={extProps.disabledkeys.includes(ele.key)}>
<Select.Option key={ele.key} value={ele.key}>
{ele.label}
</Select.Option>
))}

@ -28,7 +28,7 @@ class GroupSelect extends Component {
}}
labelInValue={false}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => `+${omittedValues.length}...`}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={_mode != null}
{...extProps}
>

@ -1,40 +0,0 @@
import React from 'react';
import { Select } from 'antd';
import { observer } from 'mobx-react';
import { HotelStars as options } from '../../libs/ht';
const HotelStars = (props) => {
const { mode, value, onChange, show_all, ...extProps } = props;
const _show_all = ['tags', 'multiple'].includes(mode) ? false : show_all;
return (
<div>
<Select
mode={mode || null}
allowClear
style={{ width: '100%' }}
placeholder="星级"
value={value || undefined}
onChange={(value) => {
if (typeof onChange === 'function') {
onChange(value);
}
}}
labelInValue={false}
{...extProps}
options={options}
>
{_show_all ? (
<Select.Option key="-1" value="ALL">
ALL
</Select.Option>
) : (
''
)}
</Select>
</div>
);
};
/**
* 酒店星级
*/
export default observer(HotelStars);

@ -1,5 +1,7 @@
import React from 'react';
import { Select } from 'antd';
import querystring from 'querystring';
// import * as oMapper from 'object-mapper';
import { fetchJSON } from './../../utils/request';
import { observer } from 'mobx-react';
import { isEmpty, merge, objectMapper } from './../../utils/commons';
@ -17,6 +19,7 @@ function curl(opts, callback) {
currentValue = opts.value;
function fake() {
// console.log(currentValue, opts.value);
if (currentValue === opts.value && opts.value === '空') {
const _p = [{ 'key': '0', 'label': '空' }];
return callback(_p);
@ -26,12 +29,11 @@ function curl(opts, callback) {
// code: 'utf-8',
// q: opts.value,
// }).toString();
const resultkey = opts.resultkey || 'result';
fetchJSON(`${opts.url}`, param)
.then(d => {
if (currentValue === opts.value) {
const result = objectMapper(d[resultkey], opts.map) || [];
const result = objectMapper(d.result, opts.map) || [];
callback(result);
}
});
@ -63,7 +65,7 @@ class SearchInput extends React.Component {
if (this.props.autoGet === true) {
const { map, resultkey, dependenciesFun } = this.props;
const param = typeof dependenciesFun === 'function' ? dependenciesFun() : {};
const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: typeof map[v] === 'string' ? { key: map[v] } : (map[v] || []).map(vi => ({ key: vi})) }), {});
const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {});
curl({ value: '', url: this.props.url || '', map: mapKey, resultkey, param }, (data) =>
this.setState({ data, autoData: data }, () => (typeof this.props.onSearchAfter === 'function' ? this.props.onSearchAfter(data, this.state.value) : ''))
);
@ -88,10 +90,8 @@ class SearchInput extends React.Component {
}
const { map, resultkey, dependenciesFun } = this.props;
const param = typeof dependenciesFun === 'function' ? dependenciesFun() : {};
// const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {});
const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: typeof map[v] === 'string' ? { key: map[v] } : (map[v] || []).map(vi => ({ key: vi})) }), {});
if ((value && this.state.data.length === 0) || !isEmpty(param)) {
const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {});
if (value || !isEmpty(param)) {
curl({ value, url: this.props.url || '', map: mapKey, resultkey, param }, (data) =>
this.setState({ data }, () => (typeof this.props.onSearchAfter === 'function' ? this.props.onSearchAfter(data, this.state.value) : ''))
);
@ -104,10 +104,6 @@ class SearchInput extends React.Component {
this.setState({ value }, () => this.props.onChange(value, option));
};
handleFilter = (value, option) => {
return String(option?.children || option?.label || option?.value || '').toLowerCase().includes(value.toLowerCase());
};
render() {
const options = this.state.data.map(d => <Option key={d.key} extradata={d.options}>{d.label}</Option>);
const { onSearchAfter, defaultOptions, autoGet, dependenciesFun, ...props } = this.props;
@ -121,15 +117,13 @@ class SearchInput extends React.Component {
placeholder={this.props.placeholder}
defaultActiveFirstOption={false}
showArrow={false}
filterOption={this.handleFilter}
filterOption={false}
onSearch={this.handleSearch}
onChange={this.handleChange}
onFocus={() => this.handleSearch('')}
notFoundContent={null}
allowClear={true}
onClear={this.handleClear}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => `+${omittedValues.length}`}
>
{options}
</Select>

@ -3,7 +3,7 @@ import { toJS } from 'mobx';
import { observer } from 'mobx-react';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, stores_Context } from './../../config';
import { SearchOutlined } from '@ant-design/icons';
import { Form, Row, Col, Select, Button, Space, DatePicker, Input, InputNumber } from 'antd';
import { Form, Row, Col, Select, Button, Space, DatePicker } from 'antd';
import moment from 'moment';
// import locale from 'antd/es/date-picker/locale/zh_CN';
import BusinessSelect from './BusinessSelect';
@ -14,11 +14,9 @@ import DateTypeSelect from './DataTypeSelect';
import DatePickerCharts from './DatePickerCharts';
import YearPickerCharts from './YearPickerCharts';
import SearchInput from './Input';
import { objectMapper, at, empty, isEmpty } from './../../utils/commons';
import { departureDateTypes } from './../../libs/ht';
import { objectMapper, at, empty } from './../../utils/commons';
import './search.css';
import HotelStarSelect from './HotelStarSelect';
const EditableContext = createContext();
const Option = Select.Option;
@ -54,11 +52,6 @@ export default observer((props) => {
transform: (value) => value?.key || '',
default: '',
},
'departureDateType': {
key: 'DateType',
transform: (value) => value?.key || '',
default: '',
},
'HTBusinessUnits': {
key: 'HTBusinessUnits',
transform: (value) => {
@ -69,21 +62,21 @@ export default observer((props) => {
'businessUnits': {
key: 'businessUnits',
transform: (value) => {
return isEmpty(value) ? 'ALL': Array.isArray(value) ? value.map((ele) => ele.value).join(',') : value ? value.value : '';
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '';
},
default: '',
},
'DepartmentList': {
key: 'DepartmentList',
transform: (value) => {
return isEmpty(value) ? 'ALL': Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : '';
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : '';
},
default: '',
},
'WebCode': {
key: 'WebCode',
transform: (value) => {
return isEmpty(value) ? 'ALL': Array.isArray(value) ? value.map((ele) => ele.key).filter(ele => ele !== 'ALL').join(',') : value ? value.key : '';
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : '';
},
default: '',
},
@ -94,13 +87,7 @@ export default observer((props) => {
},
'operator': {
key: 'operator',
// transform: (value) => value?.key || '',
transform: (value) => Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '',
default: '',
},
'date': {
key: 'date',
transform: (value) => (value ? moment(value).format(SMALL_DATETIME_FORMAT) : undefined),
transform: (value) => value?.key || '',
default: '',
},
'applyDate': [
@ -153,7 +140,7 @@ export default observer((props) => {
],
'country': {
key: 'country',
transform: (value) => Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '',
transform: (value) => value?.key || '',
default: '',
},
'city': {
@ -161,54 +148,9 @@ export default observer((props) => {
transform: (value) => value?.key || '',
default: '',
},
'billtype': {
key: 'billtype',
transform: (value) => isEmpty(value) ? 'ALL' : value?.key || '',
default: '',
},
'agency': {
key: 'agency',
transform: (value) => value?.key || '',
default: '',
},
'countryArea': {
key: 'countryArea',
transform: (value) => value?.value || value?.key || '',
default: '',
},
'orderStatus': {
key: 'orderStatus',
transform: (value) => value?.value || value?.key || '',
default: '',
},
'cruiseDirection': {
key: 'cruiseDirection',
transform: (value) => value?.value || '',
default: '',
},
'cruiseBookType': {
key: 'cruiseBookType',
transform: (value) => value?.value || '',
default: '',
},
'hotelBookType': {
key: 'hotelBookType',
transform: (value) => value?.value || '',
default: '',
},
'hotelRecommandRate': {
key: 'hotelRecommandRate',
transform: (value) => value?.value || '',
default: '',
},
'hotelStar': {
key: 'hotelStar',
transform: (value) => value?.value || '',
default: '',
},
};
let dest = {};
const { departureDateType, applyDate, applyDate2, year, yearDiff, dates, months, date, ...omittedValue } = values;
const { applyDate, applyDate2, year, yearDiff, dates, months, ...omittedValue } = values;
dest = { ...omittedValue, ...objectMapper(values, destinationObject) };
for (const key in dest) {
if (Object.prototype.hasOwnProperty.call(dest, key)) {
@ -245,21 +187,21 @@ export default observer((props) => {
};
const onValuesChange = (...args) => {
const [changedValues, allValues] = args;
// console.log('form onValuesChange', Object.keys(changedValues), args);
const dest = formValuesMapper(allValues);
searchFormStore.setFormValues(allValues);
searchFormStore.setFormValuesToSub(dest);
// console.log('form onValuesChange', Object.keys(changedValues), args);
};
return (
// layout="inline"
<Form form={form} name="advanced_search" className="orders-search-form" onFinish={onFinish} onValuesChange={onValuesChange}>
<EditableContext.Provider value={form}>
<Row gutter={10} style={{ background: '#f9fafa', margin: '0px 0px 10px 0px', padding: '16px 8px 0 8px', boxShadow: '0px 0px 3px 0px rgba(0,0,0,0.15)' }}>
<Row gutter={10} style={{ background: '#f9fafa', margin: '0px 0px 10px 0px', padding: '16px 8px', boxShadow: '0px 0px 3px 0px rgba(0,0,0,0.15)' }}>
{getFields({ sort, initialValue, hides, shows, fieldProps, form })}
{/* 'textAlign': 'right' */}
<Col flex="1 0 90px" style={{ padding: '0px 5px', display: 'flex', justifyContent: 'flex-end', alignItems: 'flex-start' }}>
<Col flex="1 0 120px" style={{ padding: '0px 5px' }}>
<Space align="center">
<Button size={'middle'} type="primary" icon={<SearchOutlined />} htmlType="submit">
{confirmText || '统计'}
@ -282,53 +224,22 @@ function getFields(props) {
const layoutProps = {
gutter: { xs: 8, sm: 8, lg: 16 },
lg: { span: 4 },
md: { span: 8 },
sm: { span: 12 },
xs: { span: 24 },
};
const item = (name, sort = 0, render, col) => {
const customCol = col || 4;
const mdCol = customCol * 2;
return {
'key': '',
sort,
name,
render,
'hide': false,
'col': { lg: { span: customCol }, md: { span: mdCol < 8 ? 10 : mdCol}, flex: mdCol < 8 ? "1 0" : "" },
'col': { lg: { span: customCol } },
};
};
let baseChildren = [];
baseChildren = [
item(
'keyword', // {...fieldComProps.keyword}
99,
<Form.Item name="keyword" {...fieldProps.keyword}>
<Input allowClear {...fieldProps.keyword} />
</Form.Item>,
fieldProps?.keyword?.col || 6
),
item(
'agency',
99,
<Form.Item name={'agency'}>
<SearchInput autoGet url="/service-web/QueryData/GetVEIName" map={{ 'CAV_VEI_SN': 'key', 'VEI2_CompanyBN': 'label' }} resultkey={'result1'} placeholder="所有地接社" {...fieldProps.agency} />
</Form.Item>
),
item(
'billtype',
99,
<Form.Item name={'billtype'}>
<SearchInput
autoGet
url="/service-web/QueryData/GetCreditCardBillType"
map={{ 'cb_billtype': ['key', 'label'] }}
// map={{ 'cb_billtype': 'key' }}
resultkey={'billtype'}
placeholder="所有账单类型"
/>
</Form.Item>
),
item(
'HTBusinessUnits',
99,
@ -346,22 +257,16 @@ function getFields(props) {
item(
'DepartmentList',
99,
<Form.Item
name={`DepartmentList`}
initialValue={at(props, 'initialValue.DepartmentList')[0] || (fieldProps?.DepartmentList?.show_all ? { key: 'ALL', label: '所有小组' } : undefined)}
rules={[{ required: true, message: '选择小组' }]}
>
<Form.Item name={`DepartmentList`} initialValue={at(props, 'initialValue.DepartmentList')[0] || (fieldProps?.DepartmentList?.show_all ? { key: 'ALL', label: '所有小组' } : undefined)}>
<GroupSelect {...fieldProps.DepartmentList} labelInValue={true} />
</Form.Item>,
fieldProps?.DepartmentList?.col
</Form.Item>
),
item(
'WebCode',
99,
<Form.Item name={`WebCode`} initialValue={at(props, 'initialValue.WebCode')[0] || (fieldProps?.WebCode?.show_all ? { key: 'ALL', label: '所有来源' } : undefined)}>
<SiteSelect {...fieldProps.WebCode} labelInValue={true} />
</Form.Item>,
fieldProps?.WebCode?.col
</Form.Item>
),
item(
'IncludeTickets',
@ -377,70 +282,16 @@ function getFields(props) {
</Option>
</Select>
</Form.Item>,
3
),
item(
'countryArea',
99,
<Form.Item name={`countryArea`} initialValue={at(props, 'initialValue.countryArea')[0] || (fieldProps?.countryArea?.show_all ? { key: 'all', label: '国内外' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="国内外" labelInValue allowClear={fieldProps?.countryArea?.show_all || false}>
{fieldProps?.countryArea?.show_all && (
<Option key="all" value="" disabled>
国内外
</Option>
)}
<Option key="china" value="china">
国内
</Option>
<Option key="foreign" value="foreign">
国外
</Option>
</Select>
</Form.Item>,
3
),
item(
'orderStatus',
99,
<Form.Item name={`orderStatus`} initialValue={at(props, 'initialValue.orderStatus')[0] || (fieldProps?.orderStatus?.show_all ? { key: '-1', label: '成行状态' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="成行状态" labelInValue allowClear>
{fieldProps?.orderStatus?.show_all && (
<Select.Option key="-1" value="-1" disabled>
成行状态
</Select.Option>
)}
<Select.Option key="已成行" value="1">
已成行
</Select.Option>
<Select.Option key="未成行" value="0">
未成行
</Select.Option>
</Select>
</Form.Item>,
3
2
),
//
item(
'DateType',
99,
<Form.Item name={`DateType`} initialValue={at(props, 'initialValue.DateType')[0] || { key: 'applyDate', label: '提交日期' }}>
<DateTypeSelect labelInValue={true} disabledkeys={fieldProps?.DateType?.disabledKeys || []} />
</Form.Item>,
fieldProps?.DateType?.col || 3
),
item(
'departureDateType',
99,
<Form.Item name={`departureDateType`} initialValue={at(props, 'initialValue.departureDateType')[0] || { key: 'departureDate', label: '抵达日期' }}>
<Select labelInValue={true} style={{ width: '100%' }} placeholder="选择日期类型">
{departureDateTypes.map((ele) => (
<Select.Option key={ele.key} value={ele.key} disabled={fieldProps?.departureDateType?.disabledKeys.includes(ele.key)}>
{ele.label}
</Select.Option>
))}
</Select>
<DateTypeSelect labelInValue={true} />
</Form.Item>,
fieldProps?.departureDateType?.col || 3
2
),
item(
'years',
@ -449,7 +300,7 @@ function getFields(props) {
{/* <DatePicker picker="year" placeholder='年份' /> */}
<YearPickerCharts {...fieldProps.years} />
</Form.Item>,
3
2
),
item(
'months',
@ -457,7 +308,7 @@ function getFields(props) {
<Form.Item>
<DatePicker picker="month" placeholder="月份" />
</Form.Item>,
3
2
),
item(
'dates',
@ -465,40 +316,19 @@ function getFields(props) {
<Form.Item>
<DatePickerCharts isform={true} {...fieldProps.dates} form={form} />
</Form.Item>,
fieldProps?.dates?.col || midCol
),
item(
'date',
99,
<Form.Item name={`date`} initialValue={at(props, 'initialValue.date')[0]}>
<DatePicker picker="date" placeholder="日期" />
</Form.Item>,
fieldProps?.date?.col || midCol
midCol
),
item(
'operator',
99,
<Form.Item name={'operator'} dependencies={['DepartmentList']}>
<Form.Item name={'operator'} dependencies={['DepartmentList']} >
<SearchInput
{...fieldProps.operator}
autoGet
url="/service-Analyse2/GetOperatorInfo"
map={{ 'op_id': 'key', 'cn_name': 'label' }}
resultkey={'result'}
placeholder="输入搜索顾问: 中/英名字"
dependenciesFun={() => {
let dependenciesValue = '';
if (Array.isArray(form.getFieldValue('DepartmentList'))) {
dependenciesValue = form
.getFieldValue('DepartmentList')
.map((e) => e.value)
.join(',')
.replace('ALL', '');
} else {
dependenciesValue = (form.getFieldValue('DepartmentList')?.value || '').replace('ALL', '');
}
return { dept_id: dependenciesValue, ...(fieldProps?.operator?.param || {}) };
}}
dependenciesFun={() => ({ dept_id: (form.getFieldValue('DepartmentList')?.value || '').replace('ALL', ''), ...(fieldProps?.operator?.param || {}) })}
/>
</Form.Item>
),
@ -506,7 +336,7 @@ function getFields(props) {
'country',
99,
<Form.Item name={'country'}>
<SearchInput {...fieldProps?.country || {}} autoGet url="/service-Analyse2/GetCountryInfo" map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索国籍: 中/英名字" />
<SearchInput autoGet url="/service-Analyse2/GetCountryInfo" map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索国籍: 中/英名字" />
</Form.Item>
),
item(
@ -523,128 +353,6 @@ function getFields(props) {
<SearchInput autoGet url="/service-Analyse2/GetGlobalDestinationInfo" map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索城市: 中/英名字" />
</Form.Item>
),
item(
'cruiseDirection',
99,
<Form.Item name={`cruiseDirection`} initialValue={at(props, 'initialValue.cruiseDirection')[0] || undefined}>
<Select style={{ width: '100%' }} placeholder="上下水" labelInValue allowClear>
{fieldProps?.cruiseDirection?.show_all && (
<Option key="all" value="" disabled>
上下水
</Option>
)}
<Option key="1" value="1">
上水
</Option>
<Option key="2" value="2">
下水
</Option>
{/* <Option key="long" value="long">
长线
</Option> */}
</Select>
</Form.Item>,
3
),
item(
'cruiseBookType',
99,
<Form.Item name={`cruiseBookType`} initialValue={at(props, 'initialValue.cruiseBookType')[0] || (fieldProps?.cruiseBookType?.show_all ? { key: '-1', label: '预定类型' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="预定类型" labelInValue allowClear>
{fieldProps?.cruiseBookType?.show_all && (
<Option key="-1" value="-1" disabled>
预定类型
</Option>
)}
<Option key="1" value="1">
单订三峡
</Option>
<Option key="0" value="0">
含行程
</Option>
</Select>
</Form.Item>,
3
),
item(
'roomsRange',
99,
<Form.Item noStyle>
<Input.Group compact>
<Form.Item name={'RoomNumStart'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center' }} placeholder="房间数" />
</Form.Item>
<Input style={{ width: 30, borderLeft: 0, borderRight: 0, pointerEvents: 'none', }} placeholder="~" disabled />
<Form.Item name={'RoomNumEnd'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center', borderLeft: 0 }} placeholder="房间数" />
</Form.Item>
</Input.Group>
</Form.Item>,
fieldProps?.roomsRange?.col || 4
),
item(
'personRange',
99,
<Form.Item noStyle>
<Input.Group compact>
<Form.Item name={'PersonNumStart'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center' }} placeholder="人数" />
</Form.Item>
<Input style={{ width: 30, borderLeft: 0, borderRight: 0, pointerEvents: 'none', }} placeholder="~" disabled />
<Form.Item name={'PersonNumEnd'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center', borderLeft: 0 }} placeholder="人数" />
</Form.Item>
</Input.Group>
</Form.Item>,
fieldProps?.personRange?.col || 4
),
item(
'hotelBookType',
99,
<Form.Item name={`hotelBookType`} initialValue={at(props, 'initialValue.hotelBookType')[0] || (fieldProps?.hotelBookType?.show_all ? { key: 'all', label: '预定类型' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="预定类型" labelInValue allowClear={fieldProps?.hotelBookType?.show_all || false}>
{fieldProps?.hotelBookType?.show_all && (
<Option key="all" value="" disabled>
预定类型
</Option>
)}
<Option key="1" value="1">
代订
</Option>
<Option key="0" value="0">
自订
</Option>
</Select>
</Form.Item>,
3
),
item(
'hotelRecommandRate',
99,
<Form.Item name={`hotelRecommandRate`} initialValue={at(props, 'initialValue.hotelRecommandRate')[0] || (fieldProps?.hotelRecommandRate?.show_all ? { key: 'all', label: '推荐等级' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="推荐等级" labelInValue allowClear={fieldProps?.hotelRecommandRate?.show_all || false}>
{fieldProps?.hotelRecommandRate?.show_all && (
<Option key="all" value="" disabled>
推荐等级
</Option>
)}
<Option key="1" value="1">
主推
</Option>
<Option key="0" value="0">
非主推
</Option>
</Select>
</Form.Item>,
3
),
item(
'hotelStar',
99,
<Form.Item name={`hotelStar`} initialValue={at(props, 'initialValue.hotelStar')[0] || undefined}>
<HotelStarSelect {...fieldProps.hotelStar} labelInValue={true} />
</Form.Item>
),
];
baseChildren = baseChildren
.map((x) => {

@ -13,17 +13,13 @@ class SiteSelect extends Component {
const { store, mode, value, onChange, show_all, ...extProps } = this.props;
const _mode = mode || store?.group_select_mode || null;
const _show_all = ['tags', 'multiple'].includes(_mode) ? false : show_all;
const __value = ['tags', 'multiple'].includes(_mode) ? (value?.constructor === Object ? [value] : value) : undefined;
const _value = !['tags', 'multiple'].includes(_mode)
? value || store?.webcode || undefined
: (__value || store?.webcode || []).filter((item) => String(item?.value || item.key).toLowerCase() !== 'all');
return (
<div>
<Select
mode={_mode}
style={{width: '100%'}}
placeholder="所有来源"
value={_value}
placeholder="选择来源"
defaultValue={value || store?.webcode || undefined }
onChange={(value) => {
if (typeof onChange === 'function') {
onChange(value);
@ -32,14 +28,12 @@ class SiteSelect extends Component {
}}
labelInValue={false}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` +${omittedValues.length}...`}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={_mode != null}
dropdownStyle={{height: '400px'}}
listHeight={400}
{...extProps}
>
{_show_all===true ? <Select.Option key="ALL" value="ALL">所有来源</Select.Option> : ''}
{sites.map(ele => <Select.Option key={ele.code} value={ele.code}>{ele.label}</Select.Option>)}
{_show_all===true ? <Select.Option key="1" value="ALL">所有来源</Select.Option> : ''}
{sites.map(ele => <Select.Option key={ele.key} value={ele.code}>{ele.label}</Select.Option>)}
</Select>
</div>
);

@ -51,7 +51,7 @@ class DatePickerCharts extends Component {
locale={locale}
placeholder={"对比 Year"}
onChange={(value) => {
const fullYear = value ? [value.clone().set('month', 0).set('date', 1), value.clone().set('month', 11).set('date', 31)] : undefined;
const fullYear = [value.clone().set('month', 0).set('date', 1), value.clone().set('month', 11).set('date', 31)];
if (typeof this.props.onChange === 'function') {
this.props.onChange(fullYear);
}

@ -1,9 +1,7 @@
import React from "react";
import packageInfo from './../package.json';
export const APP_VERSION = packageInfo.version;
export const stores_Context = React.createContext();
export const DATE_FORMAT = "YYYY-MM-DD";
export const SMALL_DATETIME_FORMAT = 'YYYY-MM-DD 23:59:00';
export const DATETIME_FORMAT = 'YYYY-MM-DD 23:59:59';
export const HT_HOST = process.env.NODE_ENV === "production" ? "https://p9axztuwd7x8a7.mycht.cn" : "http://202.103.68.144:890";
export const HT_HOST = process.env.NODE_ENV === "production" ? "https://p9axztuwd7x8a7.mycht.cn" : "http://202.103.68.100:890";

@ -1,5 +1,4 @@
import moment from 'moment';
import { fixTo4Decimals, fixTo1Decimals, fixToInt, groupBy, sortBy, cloneDeep, pick, unique, flush, fixTo2Decimals, isEmpty } from '../utils/commons';
import { fixTo4Decimals, fixTo1Decimals } from "../utils/commons";
/**
* 事业部
@ -42,7 +41,7 @@ export const groups = [
{ value: '10,18,16,30', key: '10,18,16,30', label: '孵化学院', code: '', children: [10, 18, 16, 30] },
{ value: '1', key: '1', label: 'CH直销', code: '', children: [] },
{ value: '2', key: '2', label: 'CH大客户', code: '', children: [] },
{ value: '28', key: '28', label: 'HT项目组(前AH)', code: 'HT', children: [] },
{ value: '28', key: '28', label: 'AH亚洲项目组', code: 'AH', children: [] },
{ value: '33', key: '33', label: 'GH项目组', code: '', children: [] },
{ value: '7', key: '7', label: '市场推广', code: '', children: [] },
{ value: '8', key: '8', label: '德语', code: '', children: [] },
@ -58,7 +57,6 @@ export const groups = [
{ value: '31', key: '31', label: '花梨鹰', code: '', children: [] },
];
export const groupsMappedByCode = groups.reduce((a, c) => ({ ...a, [String(c.code || c.key)]: c }), {});
export const groupsMappedByKey = groups.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {});
export const leafGroup = groups.slice(3);
export const overviewGroup = groups.slice(0, 3); // todo: 花梨鹰 APP Trippest
/**
@ -67,49 +65,33 @@ export const overviewGroup = groups.slice(0, 3); // todo: 花梨鹰 APP Trippest
export const sites = [
{ value: '2', key: '2', label: 'CHT', code: 'CHT' },
{ value: '8', key: '8', label: 'AH', code: 'AH' },
{ value: '187', key: '187', label: 'HTravel', code: 'HTravel' },
{ value: '186', key: '186', label: 'JH', code: 'JH' },
{ value: '163', key: '163', label: 'GH', code: 'GH' },
{ value: '184', key: '184', label: 'GH站外渠道 (中国)', code: 'ZWQD' },
{ value: '185', key: '185', label: 'GH站外渠道 (海外)', code: 'GH_ZWQD_HW' },
{ value: '28', key: '28', label: '客运中国', code: 'GHKYZG' },
{ value: '7', key: '7', label: '客运海外', code: 'GHKYHW' },
{ value: '172', key: '172', label: 'GHToB 海外', code: 'GHTOBHW' },
{ value: '176', key: '176', label: 'GHToB 中国', code: 'GHTOBZG' },
{ value: '11,12,20,21,10,18', key: '11,12,20,21,10,18', label: '国际(入境)', code: 'JP,VAC,IT,GM,RU,VC' },
{ value: '122,200,211,100,188', key: '122,200,211,100,188', label: '国际(海外)', code: 'VACHW,ITHW,GMHW,RUHW,VCHW' },
{ value: '178,179,180,181,182,183', key: '178,179,180,181,182,183', label: '国际GH', code: 'gh_gm,gh_jp,gh_vc,gh_vac,gh_ru,gh_it'},
{ value: '11', key: '11', label: '日语', code: 'JP' },
{ value: '179', key: '179', label: 'GH-日语', code: 'gh_jp'}, // www.globalhighlights.jp
{ value: '12', key: '12', label: '西语', code: 'VAC' },
{ value: '122', key: '122', label: '西语海外', code: 'VACHW' },
{ value: '181', key: '181', label: 'GH-西语', code: 'gh_vac'}, // www.globalhighlights.es
{ value: '20', key: '20', label: '意大利', code: 'IT' },
{ value: '200', key: '200', label: '意大利海外', code: 'ITHW' },
{ value: '183', key: '183', label: 'GH-意语', code: 'gh_it'}, // www.globalhighlights.it
{ value: '21', key: '21', label: '德语', code: 'GM' },
{ value: '211', key: '211', label: '德语海外', code: 'GMHW' },
{ value: '178', key: '178', label: 'GH-德语', code: 'gh_gm'}, // wwww.globalhighlights.de
{ value: '10', key: '10', label: '俄语', code: 'RU' },
{ value: '100', key: '100', label: '俄语海外', code: 'RUHW' },
{ value: '182', key: '182', label: 'GH-俄语', code: 'gh_ru'}, // www.globalhighlights.ru
{ value: '18', key: '18', label: '法语', code: 'VC' },
{ value: '188', key: '188', label: '法语海外', code: 'VCHW' },
{ value: '180', key: '180', label: 'GH-法语', code: 'gh_vc'}, // www.globalhighlights.fr
{ value: '16', key: '16', label: 'CT', code: 'CT' },
{ value: '30', key: '30', label: 'TP', code: 'trippest' },
{ value: '31', key: '31', label: '花梨鹰', code: 'HLY' },
];
export const sitesMappedByCode = sites.reduce((a, c) => ({ ...a, [String(c.code)]: { ...c, key: c.code, value: c.code } }), {});
export const dateTypes = [
{ key: 'applyDate', value: 'applyDate', label: '提交日期' },
{ key: 'confirmDate', value: 'confirmDate', label: '确认日期' },
{ key: 'ConfirmDate', value: 'ConfirmDate', label: '确认日期' },
{ key: 'startDate', value: 'startDate', label: '走团日期' },
];
export const departureDateTypes = [
...dateTypes,
{ key: 'departureDate', value: 'departureDate', label: '抵达日期' },
];
/**
* 结果字段
@ -139,7 +121,11 @@ export const dataFieldAlias = dataFieldOptions.reduce(
* KPI对象
*/
export const KPIObjects = [
{ key: 'overview', value: 'overview', label: '海纳', data: [{ key: 'ALL', value: 'ALL', label: '海纳' }, ...overviewGroup] },
{ key: 'overview', value: 'overview', label: '海纳', data: [
{ key: 'ALL', value: 'ALL', label: '海纳' },
...overviewGroup
]
},
{
key: 'bizarea',
value: 'bizarea',
@ -185,469 +171,3 @@ export const KPISubjects = [
// { key: 'reply_eff_wa', value: 'reply_eff_wa', label: 'WA回复效率'},
// { key: 'sum_person_num', value: 'sum_person_num', label: '人数' },
];
export const HotelStars = [
{ key: '1', value: '1', label: '五星' },
{ key: '2', value: '2', label: '四星' },
{ key: '3', value: '3', label: '三星' },
{ key: '4', value: '4', label: '二星' },
{ key: '8', value: '8', label: '准五星 ' },
{ key: '9', value: '9', label: '准四星' },
{ key: '10', value: '10', label: '客栈' },
{ key: '11', value: '11', label: '公寓' },
{ key: '12', value: '12', label: '四合院酒店' },
{ key: '13', value: '13', label: '豪华五星' },
];
export const CruiseAgency = [
{ key: '14193', value: '14193', label: '长江海外游轮旅游有限公司' },
{ key: '1067', value: '1067', label: '湖北东方皇家游船公司(待删除)' },
{ key: '70', value: '70', label: '湖北皇家长江旅游船有限公司' },
{ key: '159', value: '159', label: '文嘉船务' },
{ key: '77', value: '77', label: '武汉扬子江' },
{ key: '1337', value: '1337', label: '重庆薇灿' },
{ key: '4378', value: '4378', label: '重庆新世纪国旅' },
{ key: '-1', value: '-1', label: '自订' },
];
/**
* 计算指标值的分段区间
* @param {number} value
* @returns
*/
const calcPPPriceRange = (value) => {
if (value < 0) {
return '--';
}
const step = 30; // step = 30 USD
const start = Math.floor(value / step) * step;
const end = start + step;
if (value >= 301) {
return `≥301`;
}
return `${start === 0 ? start : (start+1)}-${end}`;
};
function calculateRangeScale(data, numScales = 36) {
if (!data || data.length === 0 || numScales <= 0) {
return [];
}
const sortedData = [...data].sort((a, b) => a - b);
const min = sortedData[0];
const max = sortedData[sortedData.length - 1];
if (min === max) {
return [roundToNice(min), roundToNice(min)];
}
const scales = [roundToNice(min)];
const scaleSize = sortedData.length / numScales;
for (let i = 1; i < numScales; i++) {
const index = Math.floor(i * scaleSize);
scales.push(roundToNice(sortedData[Math.min(index, sortedData.length - 1)]));
}
scales.push(roundToNice(max));
return [...new Set(scales)];
}
function roundToNice(value) {
if (value === 0) {
return 0;
}
const magnitude = Math.pow(10, Math.floor(Math.log10(Math.abs(value))));
const normalized = value / magnitude;
let rounded;
if (normalized < 1.5) {
rounded = Math.floor(normalized);
} else if (normalized < 3) {
rounded = Math.floor(normalized * 2) / 2; // round to 0.5
} else if (normalized < 5) {
rounded = Math.floor(normalized/2) * 2; // round to 2
} else if (normalized < 7.5) {
rounded = Math.floor(normalized / 5) * 5;
} else {
// rounded = Math.floor(normalized / 5) * 5;
rounded = Math.floor(normalized / 10) * 10;
}
return rounded * magnitude;
};
const findRange = (value, scale) => {
if (value < scale[0]) {
return `0-${scale[0]}`; // `Value ${value} is below the scale range.`;
}
for (let i = 1; i < scale.length; i++) {
if (value >= scale[i - 1] && value < scale[i]) {
return `${scale[i - 1]}-${scale[i]}`; // `Value ${value} is in the range [${scale[i - 1]}, ${scale[i]})`;
}
}
if (value >= scale[scale.length - 1]) {
return `${scale[scale.length - 1]}`; // `Value ${value} is in the range [${scale[scale.length - 1]}, Infinity)`;
}
};
const SumML_range = [1, 1.5, 2, 3, 4].map(v => v * 10000);
/**
* 数据透视计算
* @param {object[]} data
* @param {any[]} groupbyKeys
* @returns
*/
export const pivotBy = (_data, [rows, columns, date]) => {
console.time('pivot3----');
console.log('pivotBy', [rows, columns, date]);
const groupbyKeys = flush([].concat(rows, columns, [date]));
// if (groupbyKeys.includes('PPPriceRange')) {
// }
// 补充计算的字段
const RTXF_WB_values = cloneDeep(_data).map(ele => ele.RTXF_WB); // 人天消费
// const max_RTXF_WB = Math.max(...RTXF_WB_values);
const RTXF_WB_range = calculateRangeScale(RTXF_WB_values);
let data = cloneDeep(_data).map(ele => {
ele.startYearMonth = ele.startDate ? moment(ele.startDate).format('YYYY-MM') : '';
ele.startMonth = ele.startDate ? moment(ele.startDate).format('MM') : '';
ele.applyYearMonth = ele.applyDate ? moment(ele.applyDate).format('YYYY-MM') : '';
ele.applyMonth = ele.applyDate ? moment(ele.applyDate).format('MM') : '';
ele.PPPrice = (Number(ele.orderState) === 1 && ele.tourdays && ele.personNum) ? fixToInt(ele.quotePrice / ele.tourdays / ele.personNum) : -1; // 报价: 人均天
ele.PPPriceRange = calcPPPriceRange(ele.PPPrice);
ele.RTXF_WB_range = findRange(ele.RTXF_WB, RTXF_WB_range);
ele.IsOld_txt = ele.IsOld === '1' ? '老客户' : '否';
ele.isCusCommend_txt = ele.isCusCommend === '1' ? '老客户推荐' : '否';
const hasOld = (ele.IsOld === '1' || ele.isCusCommend === '1') ? 1 : 0;
ele.hasOld = hasOld;
ele.hasOld_txt = hasOld === 1 ? '老客户(推荐)' : '';
// ele.SumML_ctxt1 = ele.ML > 10000 ? '1W+' : '1W-';
// ele.SumML_ctxt1_5 = ele.ML > 15000 ? '1.5W+' : '1.5W-';
// ele.SumML_ctxt2 = ele.ML > 20000 ? '2W+' : '2W-';
// ele.SumML_ctxt3 = ele.ML > 30000 ? '3W+' : '3W-';
// ele.SumML_ctxt4 = ele.ML > 40000 ? '4W+' : '4W-';
ele.SumML_ctxt = findRange(ele.ML, SumML_range);
return ele;
});
// 数组的字段值, 拆分处理
if (groupbyKeys.includes('destinationCountry')) {
data = data.reduce((r, v, i) => {
const vjson = isEmpty(v.destinationCountry) ? [] : v.destinationCountry;
const xv = (vjson).reduce((rv, cv, vi) => {
rv.push({...v, destinationCountry: cv, key: vi === 0 ? v.key : `${v.key}@${cv}`});
return rv;
}, []);
r = r.concat(xv);
return r;
}, []);
}
if (groupbyKeys.includes('destinations')) {
data = data.reduce((r, v, i) => {
const vjson = isEmpty(v.destinations) ? [] : v.destinations;
const xv = (vjson).reduce((rv, cv, vi) => {
rv.push({...v, destinations: cv, key: vi === 0 ? v.key : `${v.key}@${cv}`});
return rv;
}, []);
r = r.concat(xv);
return r;
}, []);
}
const getKeys = (keys) => keys.map((keyField) => [...new Set(data.map((f) => f[keyField]))]);
const [rowsKeys, columnsKeys, dateKeys] = [getKeys(rows), getKeys(columns), [getKeys([date])[0].filter(s => s)]];
// console.log('rowsKeys', rowsKeys, 'columnsKeys', columnsKeys, 'dateKeys', dateKeys);
const calcTradeFields = (dataObj, keepKeys = [], seriesKey = '') => {
const outerKeys = [];
const _keepKeys = [...keepKeys, seriesKey];
const DataGroupByKeys = {};
Object.keys(dataObj).forEach((colKey) => {
const _len = dataObj[colKey].length;
const _rowKey = dataObj[colKey].map((v) => v.key).join('_');
outerKeys.push(_rowKey);
const initialData = {
...pick(dataObj[colKey][0], _keepKeys),
...(keepKeys.length === 0
? { rowLabel: '总' }
: {
rowLabel: cloneDeep(keepKeys)
// .slice(0, -1)
.map((_k) => dataObj[colKey][0][_k])
.join('»'),
}),
_label: colKey || '(空)',
key: _rowKey,
_a:dataObj[colKey].map((v) => `${v.PPPrice}: ${v.PPPriceRange}`).join(', '),
_b:dataObj[colKey].map((v) => `${v.orderState}: ${v.quotePrice}/ ${v.tourdays}/ ${v.personNum}`).join(', '),
SumOrder: _len,
ResumeOrder: 0,
ResumeConfirmOrder: 0,
SumPersonNum: 0,
ConfirmPersonNum: 0,
ConfirmOrder: 0,
transactions: 0,
SumML: 0,
SumML_txt: '',
quotePrice: 0,
tourdays: 0,
applyDays: 0,
confirmDays: 0,
SingleML: 0,
OrderValue: 0,
PPPrice: 0,
AvgPPPrice: 0,
confirmTourdays: 0,
PPPriceRange: '',
unitPPPriceRange: '',
};
const calculatedData = dataObj[colKey].reduce((r, v) => {
r.SumPersonNum += v.personNum;
r.ConfirmPersonNum += Number(v.orderState) === 1 ? v.personNum : 0;
r.ConfirmOrder += Number(v.orderState) === 1 ? 1 : 0;
r.ResumeOrder += v.hasOld === 1 ? 1 : 0;
r.ResumeConfirmOrder += Number(v.orderState) === 1 && v.hasOld === 1 ? 1 : 0;
r.transactions += v.transactions;
r.SumML += Number(v.orderState) === 1 ? v.ML : 0;
r.quotePrice += Number(v.orderState) === 1 ? v.quotePrice : 0;
r.tourdays += v.tourdays;
r.applyDays += v.applyDays;
r.confirmDays += v.confirmDays;
r.PPPrice += Number(v.orderState) === 1 ? v.PPPrice : 0;
r.confirmTourdays += Number(v.orderState) === 1 ? v.tourdays : 0;
return r;
}, initialData);
// Calculations
calculatedData.tourdays = Math.ceil(calculatedData.tourdays / _len);
calculatedData.confirmTourdays = calculatedData.ConfirmOrder > 0 ? Math.ceil(calculatedData.confirmTourdays / calculatedData.ConfirmOrder) : '0';
calculatedData.applyDays = Math.ceil(calculatedData.applyDays / _len);
calculatedData.confirmDays = Math.ceil(calculatedData.confirmDays / _len);
const _rowCalc = {
ConfirmRates: calculatedData.ConfirmOrder ? fixTo4Decimals(calculatedData.ConfirmOrder / calculatedData.SumOrder*100) : 0,
OrderValue: calculatedData.SumOrder ? fixToInt(calculatedData.SumML / calculatedData.SumOrder) : 0,
SingleML: calculatedData.ConfirmOrder ? fixToInt(calculatedData.SumML / calculatedData.ConfirmOrder) : 0,
AvgPPPrice: calculatedData.ConfirmOrder ? fixToInt(calculatedData.PPPrice / calculatedData.ConfirmOrder) : -1,
unitPPPrice:
calculatedData.confirmTourdays && calculatedData.ConfirmPersonNum ? fixToInt(calculatedData.quotePrice / calculatedData.confirmTourdays / calculatedData.ConfirmPersonNum) : -1,
};
// Formatter
calculatedData.transactions = fixTo2Decimals(calculatedData.transactions);
calculatedData.SumML = fixTo2Decimals(calculatedData.SumML);
calculatedData.SumML_txt = dataFieldAlias.SumML.formatter(calculatedData.SumML);
calculatedData.quotePrice = fixTo2Decimals(calculatedData.quotePrice);
calculatedData.ConfirmRates_txt = dataFieldAlias.ConfirmRates.formatter(_rowCalc.ConfirmRates);
// calculatedData.SingleML = fixTo2Decimals(calculatedData.SingleML);
calculatedData.PPPrice = fixTo2Decimals(calculatedData.PPPrice);
calculatedData.PPPriceRange = calcPPPriceRange(_rowCalc.AvgPPPrice);
calculatedData.unitPPPriceRange = calcPPPriceRange(_rowCalc.unitPPPrice);
DataGroupByKeys[colKey] = { ...calculatedData, ..._rowCalc };
});
return { groupByKeys: DataGroupByKeys, key: outerKeys.join('_'), keys: outerKeys.join('_').split('_') };
};
const groupData = groupBy(data, (row) => groupbyKeys.map((kk) => `${row[kk]}`).join('=@='));
const rowsNcolumnsItems = calcTradeFields(groupData, [...rows, ...columns], date);
const pivotResult = Object.values(rowsNcolumnsItems.groupByKeys);
const transposeData = (keys, dataProp, [dataKey, colKeys]=[]) =>
Object.keys(dataProp)
.map((rowKey) => {
const rowLabel = keys.length === 0 ? '总' : keys.map(ekey => dataProp[rowKey][0][ekey]).join('»');
const _colKey = dataKey || 'dataKey';
const _colData = groupBy(dataProp[rowKey], (crow) => (colKeys || keys).map((kk) => `${crow[kk]}`).join('=@='));
const _columnsObj = calcTradeFields(_colData);
return { ...pick(dataProp[rowKey][0], keys), [_colKey]: _columnsObj.groupByKeys, key: _columnsObj.key, keys: _columnsObj.keys, rowLabel };
})
.map((everyR) => {
const _colKey = dataKey || 'dataKey';
const allColumns = Object.values(everyR[_colKey]).reduce((r, c) => r.concat([c]), []);
const summaryCalc = [
'ConfirmOrder',
'SumOrder',
'ResumeOrder', 'ResumeConfirmOrder',
'SumML',
'transactions',
'SumPersonNum',
'ConfirmPersonNum',
'quotePrice',
'tourdays',
'applyDays',
'confirmDays',
'PPPrice', 'AvgPPPrice',
'confirmTourdays',
].reduce((r, skey) => ({ ...r,
[skey]: allColumns.reduce((a, c) => (fixTo2Decimals(a + c[skey])), 0),
[`${skey}_arr`]: allColumns.reduce((a, c) => (a.concat(c[skey])), []),
}),everyR);
summaryCalc.tourdays = Math.ceil(summaryCalc.tourdays / allColumns.length);
summaryCalc.confirmTourdays = summaryCalc.ConfirmOrder > 0 ? Math.ceil(summaryCalc.confirmTourdays / summaryCalc.ConfirmOrder) : '0';
summaryCalc.applyDays = Math.ceil(summaryCalc.applyDays / allColumns.length);
summaryCalc.confirmDays = Math.ceil(summaryCalc.confirmDays / allColumns.length);
summaryCalc.ConfirmRates = summaryCalc.ConfirmOrder ? fixTo2Decimals(summaryCalc.ConfirmOrder / summaryCalc.SumOrder*100) : 0;
summaryCalc.ResumeConfirmRates = summaryCalc.ResumeConfirmOrder ? fixTo2Decimals(summaryCalc.ResumeConfirmOrder / summaryCalc.ResumeOrder*100) : 0;
summaryCalc.OrderValue = summaryCalc.SumOrder ? fixToInt(summaryCalc.SumML / summaryCalc.SumOrder) : 0;
summaryCalc.SingleML = summaryCalc.ConfirmOrder ? fixTo2Decimals(summaryCalc.SumML / summaryCalc.ConfirmOrder) : 0;
summaryCalc.AvgPPPrice = Math.ceil(summaryCalc.AvgPPPrice / allColumns.length);
summaryCalc.PPPriceRange = calcPPPriceRange(summaryCalc.AvgPPPrice);
summaryCalc.unitPPPrice = summaryCalc.confirmTourdays && summaryCalc.ConfirmPersonNum ? fixToInt(summaryCalc.quotePrice / summaryCalc.confirmTourdays / summaryCalc.ConfirmPersonNum) : -1;
summaryCalc.unitPPPriceRange = calcPPPriceRange(summaryCalc.unitPPPrice);
summaryCalc.SumML_txt = dataFieldAlias.SumML.formatter(summaryCalc.SumML);
summaryCalc.ConfirmRates_txt = dataFieldAlias.ConfirmRates.formatter(summaryCalc.ConfirmRates);
return { ...everyR, ...summaryCalc };
});
const rowsData = groupBy(data, (row) => rows.map((kk) => `${row[kk]}`).join('=@='));
const summaryRows = transposeData(rows, rowsData, ['columns', columns]);
const columnsData = groupBy(data, (row) => columns.map((kk) => `${row[kk]}`).join('=@='));
const summaryColumns = transposeData(columns, columnsData, ['rows', rows]);
const rowsMixcolumns = flush([].concat(rows, columns));
const rowsMixcolumnsData = groupBy(data, (row) => rowsMixcolumns.map((kk) => `${row[kk]}`).join('=@='));
const summaryMix = transposeData(rowsMixcolumns, rowsMixcolumnsData);
console.timeEnd('pivot3----');
return { data: pivotResult, columnValues: [rowsKeys, columnsKeys, dateKeys], summaryRows, summaryColumns, pivotKeys: groupbyKeys, summaryMix };
};
// todo: 优化 pivotBy 速度
export const pivotBy3 = (data, [rows, columns, date]) => {
console.log('pivotBy', [rows, columns, date]);
console.time('pivot2');
// const rowKeys = new Set(data.map(row => row[rows[0]]));
const rowKeys = rows.map((keyField) => {
const keyu = new Set(data.map((f) => f[keyField]));
return keyu;
});
const colKeys = new Set(data.map(row => row[columns[0]]));
const dateKeys = new Set(data.map(row => row[date]));
const aggregatedData = {};
data.forEach(row => {
const rowKey = row[rows[0]] ?? '__total';
const colKey = row[columns[0]] ?? '__total';
const dateKey = row[date];
if (!aggregatedData[rowKey]) {
aggregatedData[rowKey] = {};
}
// if (!aggregatedData[rowKey][colKey]) {
// aggregatedData[rowKey][colKey] = {};
// }
if (!aggregatedData[rowKey][colKey]) {
aggregatedData[rowKey][colKey] = {
SumOrder: 0,
// other aggregated fields
SumPersonNum: 0,
ConfirmOrder: 0,
transactions: 0,
SumML: 0,
// ...
quotePrice: 0,
tourdays: 0,
applyDays: 0,
confirmDays: 0,
};
}
aggregatedData[rowKey][colKey].SumOrder++;
aggregatedData[rowKey][colKey].SumPersonNum += row.personNum;
aggregatedData[rowKey][colKey].ConfirmOrder += Number(row.orderState === 1);
aggregatedData[rowKey][colKey].transactions += row.transactions;
aggregatedData[rowKey][colKey].SumML += row.ML;
// aggregate other fields
});
const summarizedData = [];
// Generate summary rows
for (const rowKey of rowKeys) {
const rowAggregations = {
SumOrder: 0,
// other aggregated fields
SumPersonNum: 0,
ConfirmOrder: 0,
transactions: 0,
SumML: 0,
// ...
quotePrice: 0,
tourdays: 0,
applyDays: 0,
confirmDays: 0,
};
// Calculate aggregates over colKey
for (const colKey in aggregatedData[rowKey]) {
rowAggregations.SumOrder += aggregatedData[rowKey][colKey].SumOrder;
rowAggregations.SumPersonNum += aggregatedData[rowKey][colKey].SumPersonNum;
rowAggregations.ConfirmOrder += aggregatedData[rowKey][colKey].ConfirmOrder;
rowAggregations.transactions += aggregatedData[rowKey][colKey].transactions;
rowAggregations.SumML += aggregatedData[rowKey][colKey].SumML;
// ...aggregate all other fields
}
const row = {
[rows[0]]: rowKey,
...rowAggregations
};
summarizedData.push(row);
}
// Generate summary columns
for (const colKey of colKeys) {
const colAggregations = {
SumOrder: 0,
// other aggregated fields
SumPersonNum: 0,
ConfirmOrder: 0,
transactions: 0,
SumML: 0,
// ...
quotePrice: 0,
tourdays: 0,
applyDays: 0,
confirmDays: 0,
};
// Calculate aggregates over rowKey
for (const rowKey in aggregatedData) {
if (aggregatedData[rowKey][colKey]) {
colAggregations.SumOrder += aggregatedData[rowKey][colKey].SumOrder;
colAggregations.SumPersonNum += aggregatedData[rowKey][colKey].SumPersonNum;
colAggregations.ConfirmOrder += aggregatedData[rowKey][colKey].ConfirmOrder;
colAggregations.transactions += aggregatedData[rowKey][colKey].transactions;
colAggregations.SumML += aggregatedData[rowKey][colKey].SumML;
// ...aggregate all other fields
}
}
const col = {
[columns[0]]: colKey,
...colAggregations
};
summarizedData.push(col);
}
console.timeEnd('pivot2');
console.log('pivot2 ddd', aggregatedData);
return {
data: [], // aggregatedData,
columnValues: [rowKeys, colKeys, dateKeys],
summaryRows: summarizedData.filter(r => r[rows[0]]),
summaryColumns: summarizedData.filter(c => c[columns[0]])
};
};

@ -1,61 +1,59 @@
import { makeAutoObservable, runInAction } from 'mobx';
import * as dd from 'dingtalk-jsapi';
import * as config from '../config';
import { makeAutoObservable, runInAction } from "mobx";
import * as dd from "dingtalk-jsapi";
import * as config from "../config";
// 权限管理
class AuthStore {
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
if (process.env.NODE_ENV === 'production') {
this.get_auth(); // 放到钉钉环境才能开启
}
}
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
if (process.env.NODE_ENV == "production") {
this.get_auth(); // 放到钉钉环境才能开启
}
}
auth = process.env.NODE_ENV === 'production' ? [] : ['admin']; // 开发时候用,正式环境留空
user = { name: 'loading', userid: '...' }; // 开发时候用,正式环境留空
auth = ["admin"]; // 开发时候用,正式环境留空
user = { name: "loading", userid: "..." }; // 开发时候用,正式环境留空
has_permission(requireds) {
if (Object.keys(requireds).length === 0) {
return true;
}
const has_permission = requireds.filter((item) => this.auth.includes(item));
if (Object.keys(has_permission).length !== 0) {
return true;
}
return false;
}
has_permission(requireds) {
if (Object.keys(requireds).length == 0) {
return true;
}
const has_permission = requireds.filter(item => this.auth.includes(item));
if (Object.keys(has_permission).length !== 0) {
return true;
}
return false;
}
// 请求权限
get_auth() {
const _this = this;
const CORPID = 'ding48bce8fd3957c96b'; // 企业的id
dd.runtime.permission.requestAuthCode({
corpId: CORPID,
onSuccess: function (res) {
console.log(res);
const code = res.code;
const url = '/dingtalk/dingtalkwork/Getusers_auth?code=' + code;
// 请求获取HT接口获取用户权限和用户信息
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
runInAction(() => {
_this.user = json.result;
_this.auth = json.result.authlist;
window.__spytitle = json.result.name;
window.initPageSpy();
});
})
.catch((error) => {
console.log('fetch data failed', error);
});
},
onFail: function (err) {
console.log(err);
},
});
}
// 请求权限
get_auth() {
const _this = this;
const CORPID = "ding48bce8fd3957c96b"; // 企业的id
dd.runtime.permission.requestAuthCode({
corpId: CORPID,
onSuccess: function (res) {
console.log(res);
const code = res.code;
const url = "/dingtalk/dingtalkwork/Getusers_auth?code=" + code;
// 请求获取HT接口获取用户权限和用户信息
fetch(config.HT_HOST + url)
.then(response => response.json())
.then(json => {
runInAction(() => {
_this.user = json.result;
_this.auth = json.result.authlist;
});
})
.catch(error => {
console.log("fetch data failed", error);
});
},
onFail: function (err) {
console.log(err);
},
});
}
}
export default AuthStore;

@ -3,7 +3,7 @@ import moment from "moment";
import { NavLink } from "react-router-dom";
import * as config from "../config";
import * as req from '../utils/request';
import { groupBy, prepareUrl, isEmpty, show_vs_tag, formatPercent, percentToDecimal } from '../utils/commons';
import { prepareUrl } from '../utils/commons';
class CustomerServices {
@ -13,7 +13,7 @@ class CustomerServices {
this.endDate = moment().endOf('week').subtract(7, 'days');
this.startDateString = this.startDate.format(config.DATE_FORMAT);
this.endDateString = this.endDate.format(config.DATE_FORMAT) + '%2023:59';
this.dateType = 'departureDate';
this.dateType = 'startDate';
this.inProgress = false;
this.selectedAgent = '';
this.selectedTeam = '';
@ -43,8 +43,8 @@ class CustomerServices {
.append('DateType', this.dateType)
.append('Date1', this.startDateString)
.append('Date2', this.endDateString)
.append('OldDate1', this.startDateDiffString)
.append('OldDate2', this.endDateDiffString)
.append('OldDate1', this.startDateString)
.append('OldDate2', this.endDateString)
.append('VEI_SN', this.selectedAgent)
.append('DepList', this.selectedTeam)
.append('Country', this.selectedCountry)
@ -53,272 +53,110 @@ class CustomerServices {
.then(json => {
if (json.errcode === 0) {
runInAction(() => {
if (isEmpty(json.result2)){
const splitTotalList = groupBy(json.result1, row => row.EOI_ObjSN === -1 ? '0' : '1');
this.agentGroupList = splitTotalList['1'];
const total1 = splitTotalList['0']?.[0] || {}; // json.total1;
this.agentGroupListColumns = [
{
title: '地接社名称',
dataIndex: 'VendorName',
fixed: 'left',
children: [{
// title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT),
dataIndex: 'VendorName',
fixed: 'left',
render: (text, record) => <NavLink to={`/agent/${record.EOI_ObjSN}/group/list`}>{record.VendorName}</NavLink>
}
]
},
{
title: '团数',
dataIndex: 'GroupCount',
sorter: (a, b) => a.GroupCount - b.GroupCount,
children: [{
title: total1.GroupCount,
dataIndex: 'GroupCount'
}
]
},
{
title: '人数',
dataIndex: 'PersonNum',
sorter: (a, b) => a.PersonNum - b.PersonNum,
children: [{
title: total1.PersonNum,
dataIndex: 'PersonNum'
}
]
},
{
title: '团天数',
dataIndex: 'GroupDays',
sorter: (a, b) => a.GroupDays - b.GroupDays,
children: [{
title: total1.GroupDays,
dataIndex: 'GroupDays'
}
]
},
{
title: '交易额',
dataIndex: 'totalcost',
sorter: (a, b) => a.totalcost - b.totalcost,
children: [{
title: total1.totalcost,
dataIndex: 'totalcost'
}
]
},
{
title: '前勤分',
dataIndex: 'qianqin',
sorter: (a, b) => a.qianqin - b.qianqin,
children: [{
title: total1.qianqin,
dataIndex: 'qianqin'
}
]
},
{
title: '好评数',
dataIndex: 'GoodCount',
sorter: (a, b) => a.GoodCount - b.GoodCount,
children: [{
title: total1.GoodCount,
dataIndex: 'GoodCount'
}
]
},
{
title: '好评率',
dataIndex: 'GoodRate',
sorter: (a, b) => parseInt(a.GoodRate) - parseInt(b.GoodRate),
children: [{
title: total1.GoodRate,
dataIndex: 'GoodRate'
}
]
},
{
title: '差评数',
dataIndex: 'BadCount',
sorter: (a, b) => a.BadCount - b.BadCount,
children: [{
title: total1.BadCount,
dataIndex: 'BadCount'
}
]
},
{
title: '差评率',
dataIndex: 'BadRate',
sorter: (a, b) => parseInt(a.BadRate) - parseInt(b.BadRate),
children: [{
title: total1.BadRate,
dataIndex: 'BadRate'
}
]
}
];
}
else{
const splitTotalList1 = groupBy(json.result1, row => row.EOI_ObjSN === -1 ? '0' : '1');
const splitTotalList2 = groupBy(json.result2, row => row.EOI_ObjSN === -1 ? '0' : '1');
const result = [];
for (const item1 of splitTotalList1['1']) {
for (const item2 of splitTotalList2['1']) {
if (item1.EOI_ObjSN === item2.EOI_ObjSN) {
const goodRate1 = percentToDecimal(item1.GoodRate);
const goodRate2 = percentToDecimal(item2.GoodRate);
const badRate1 = percentToDecimal(item1.BadRate);
const badRate2 = percentToDecimal(item2.BadRate);
result.push({
EOI_ObjSN: item1.EOI_ObjSN,
VendorName: item1.VendorName,
GroupCount: show_vs_tag(formatPercent((item1.GroupCount-item2.GroupCount)/(item2.GroupCount===0?1:item2.GroupCount)),
item1.GroupCount-item2.GroupCount,item1.GroupCount,item2.GroupCount),
PersonNum: show_vs_tag(formatPercent((item1.PersonNum-item2.PersonNum)/(item2.PersonNum===0?1:item2.PersonNum)),
item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum),
GroupDays: show_vs_tag(formatPercent((item1.GroupDays-item2.GroupDays)/(item2.GroupDays===0?1:item2.GroupDays)),
item1.GroupDays-item2.GroupDays,item1.GroupDays,item2.GroupDays),
totalcost: show_vs_tag(formatPercent((item1.totalcost-item2.totalcost)/(item2.totalcost===0?1:item2.totalcost)),
(item1.totalcost-item2.totalcost).toFixed(2),item1.totalcost,item2.totalcost),
GoodCount: show_vs_tag(formatPercent((item1.GoodCount-item2.GoodCount)/(item2.GoodCount===0?1:item2.GoodCount)),
item1.GoodCount-item2.GoodCount,item1.GoodCount,item2.GoodCount),
GoodRate: show_vs_tag(formatPercent((goodRate1-goodRate2)/(goodRate2===0?1:goodRate2)),
formatPercent(goodRate1-goodRate2),item1.GoodRate,item2.GoodRate),
BadCount: show_vs_tag(formatPercent((item1.BadCount-item2.BadCount)/(item2.BadCount===0?1:item2.BadCount)),
item1.BadCount-item2.BadCount,item1.BadCount,item2.BadCount),
BadRate: show_vs_tag(formatPercent((badRate1-badRate2)/(badRate2===0?1:badRate2)),
formatPercent(badRate1-badRate2),item1.BadRate,item2.BadRate),
qianqin: show_vs_tag(formatPercent((item1.qianqin-item2.qianqin)/(item2.qianqin===0?1:item2.qianqin)),
(item1.qianqin-item2.qianqin).toFixed(2),item1.qianqin,item2.qianqin),
key:item1.key,
});
this.agentGroupList = json.result1;
const total1 = json.total1;
this.agentGroupListColumns = [
{
title: '地接社名称',
dataIndex: 'VendorName',
children: [{
// title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT),
dataIndex: 'VendorName',
render: (text, record) => <NavLink to={`/agent/${record.EOI_ObjSN}/group/list`}>{record.VendorName}</NavLink>
}
]
},
{
title: '团数',
dataIndex: 'GroupCount',
sorter: (a, b) => a.GroupCount - b.GroupCount,
children: [{
title: total1.GroupCount,
dataIndex: 'GroupCount'
}
]
},
{
title: '人数',
dataIndex: 'PersonNum',
sorter: (a, b) => a.PersonNum - b.PersonNum,
children: [{
title: total1.PersonNum,
dataIndex: 'PersonNum'
}
]
},
{
title: '团天数',
dataIndex: 'GroupDays',
sorter: (a, b) => a.GroupDays - b.GroupDays,
children: [{
title: total1.GroupDays,
dataIndex: 'GroupDays'
}
}
]
},
{
title: '交易额',
dataIndex: 'totalcost',
sorter: (a, b) => a.totalcost - b.totalcost,
children: [{
title: total1.totalcost,
dataIndex: 'totalcost'
}
]
},
{
title: '前勤分',
dataIndex: 'qianqin',
sorter: (a, b) => a.qianqin - b.qianqin,
children: [{
title: total1.qianqin,
dataIndex: 'qianqin'
}
]
},
{
title: '好评数',
dataIndex: 'GoodCount',
sorter: (a, b) => a.GoodCount - b.GoodCount,
children: [{
title: total1.GoodCount,
dataIndex: 'GoodCount'
}
]
},
{
title: '好评率',
dataIndex: 'GoodRate',
sorter: (a, b) => parseInt(a.GoodRate) - parseInt(b.GoodRate),
children: [{
title: total1.GoodRate,
dataIndex: 'GoodRate'
}
]
},
{
title: '差评数',
dataIndex: 'BadCount',
sorter: (a, b) => a.BadCount - b.BadCount,
children: [{
title: total1.BadCount,
dataIndex: 'BadCount'
}
]
},
{
title: '差评率',
dataIndex: 'BadRate',
sorter: (a, b) => parseInt(a.BadRate) - parseInt(b.BadRate),
children: [{
title: total1.BadRate,
dataIndex: 'BadRate'
}
]
}
this.agentGroupList = result;
const total1 = splitTotalList1['0']?.[0] || {};
const total2 = splitTotalList2['0']?.[0] || {};
this.agentGroupListColumns = [
{
title: '地接社名称',
dataIndex: 'VendorName',
fixed: 'left',
children: [{
// title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT),
dataIndex: 'VendorName',
fixed: 'left',
render: (text, record) => <NavLink to={`/agent/${record.EOI_ObjSN}/group/list`}>{record.VendorName}</NavLink>
}
]
},
{
title: '团数',
dataIndex: 'GroupCount',
sorter: (a, b) => a.GroupCount - b.GroupCount,
children: [{
title: show_vs_tag(formatPercent((total1.GroupCount-total2.GroupCount)/total2.GroupCount),total1.GroupCount-total2.GroupCount,total1.GroupCount,total2.GroupCount),
titleX: [total1.GroupCount, total2.GroupCount].join(' vs '),
dataIndex: 'GroupCount'
}
]
},
{
title: '人数',
dataIndex: 'PersonNum',
sorter: (a, b) => a.PersonNum - b.PersonNum,
children: [{
title: show_vs_tag(formatPercent((total1.PersonNum-total2.PersonNum)/total2.PersonNum),total1.PersonNum-total2.PersonNum,total1.PersonNum,total2.PersonNum),
titleX: [total1.PersonNum, total2.PersonNum].join(' vs '),
dataIndex: 'PersonNum'
}
]
},
{
title: '团天数',
dataIndex: 'GroupDays',
sorter: (a, b) => a.GroupDays - b.GroupDays,
children: [{
title: show_vs_tag(formatPercent((total1.GroupDays-total2.GroupDays)/total2.GroupDays),total1.GroupDays-total2.GroupDays,total1.GroupDays,total2.GroupDays),
titleX: [total1.GroupDays, total2.GroupDays].join(' vs '),
dataIndex: 'GroupDays'
}
]
},
{
title: '交易额',
dataIndex: 'totalcost',
sorter: (a, b) => a.totalcost - b.totalcost,
children: [{
title: show_vs_tag(formatPercent((total1.totalcost-total2.totalcost)/total2.totalcost),(total1.totalcost-total2.totalcost).toFixed(2),total1.totalcost,total2.totalcost),
titleX: [total1.totalcost, total2.totalcost].join(' vs '),
dataIndex: 'totalcost'
}
]
},
{
title: '前勤分',
dataIndex: 'qianqin',
sorter: (a, b) => a.qianqin - b.qianqin,
children: [{
title: show_vs_tag(formatPercent((total1.qianqin-total2.qianqin)/total2.qianqin),(total1.qianqin-total2.qianqin).toFixed(2),total1.qianqin,total2.qianqin),
titleX: [total1.qianqin, total2.qianqin].join(' vs '),
dataIndex: 'qianqin'
}
]
},
{
title: '好评数',
dataIndex: 'GoodCount',
sorter: (a, b) => a.GoodCount - b.GoodCount,
children: [{
title: show_vs_tag(formatPercent((total1.GoodCount-total2.GoodCount)/total2.GoodCount),total1.GoodCount-total2.GoodCount,total1.GoodCount,total2.GoodCount),
titleX: [total1.GoodCount, total2.GoodCount].join(' vs '),
dataIndex: 'GoodCount'
}
]
},
{
title: '好评率',
dataIndex: 'GoodRate',
sorter: (a, b) => parseInt(a.GoodRate) - parseInt(b.GoodRate),
children: [{
title: show_vs_tag(formatPercent((percentToDecimal(total1.GoodRate)-percentToDecimal(total2.GoodRate))/percentToDecimal(total2.GoodRate)),
formatPercent(percentToDecimal(total1.GoodRate)-percentToDecimal(total2.GoodRate)),total1.GoodRate,total2.GoodRate),
titleX: [total1.GoodRate, total2.GoodRate].join(' vs '),
dataIndex: 'GoodRate'
}
]
},
{
title: '差评数',
dataIndex: 'BadCount',
sorter: (a, b) => a.BadCount - b.BadCount,
children: [{
title: show_vs_tag(formatPercent((total1.BadCount-total2.BadCount)/total2.BadCount),total1.BadCount-total2.BadCount,total1.BadCount,total2.BadCount),
titleX: [total1.BadCount, total2.BadCount].join(' vs '),
dataIndex: 'BadCount'
}
]
},
{
title: '差评率',
dataIndex: 'BadRate',
sorter: (a, b) => parseInt(a.BadRate) - parseInt(b.BadRate),
children: [{
title: show_vs_tag(formatPercent((percentToDecimal(total1.BadRate)-percentToDecimal(total2.BadRate))/percentToDecimal(total2.GoodRate)),
formatPercent(percentToDecimal(total1.BadRate)-percentToDecimal(total2.BadRate)),total1.BadRate,total2.BadRate),
titleX: [total1.BadRate, total2.BadRate].join(' vs '),
dataIndex: 'BadRate'
}
]
}
];
}
];
});
}
})
@ -437,10 +275,9 @@ class CustomerServices {
});
}
fetchDistGroupInfoByCountry(destinationId) {
this.nationality_count_data.loading = true;
const fetchUrl = prepareUrl(config.HT_HOST + '/service-web/QueryData/GetDistGroupInfoALLByCountry')
.append('city', destinationId)
fetchDestinationGroupCount() {
this.inProgress = true;
const fetchUrl = prepareUrl(config.HT_HOST + '/service-web/QueryData/GetdistGroupInfoAll')
.append('DateType', this.dateType)
.append('Date1', this.startDateString)
.append('Date2', this.endDateString)
@ -451,19 +288,18 @@ class CustomerServices {
.append('OrderStatus', this.selectedOrderStatus)
.build();
req.fetchJSON(fetchUrl)
.then((json) => {
.then(json => {
if (json.errcode === 0) {
runInAction(() => {
const splitTotalList = groupBy(json.result1, row => row.COLD_ServiceCity === -1 ? '0' : '1');
this.nationality_count_data.destinationGroupByCountryList = splitTotalList['1'];
const total1 = splitTotalList['0']?.[0] || {};
this.nationality_count_data.destinationGroupByCountryListColumns =[
this.destinationGroupCount = json.result1;
const total1 = json.total1;
this.destinationGroupCountColumns = [
{
title: '国籍',
title: '城市',
dataIndex: 'COLD_ServiceCityName',
children: [{
title: total1.COLD_ServiceCityName,
dataIndex: 'COLD_ServiceCityName'
dataIndex: 'COLD_ServiceCityName',
render: (text, record) => <NavLink to={`/destination/${record.COLD_ServiceCity}/group/list`}>{record.COLD_ServiceCityName}</NavLink>
}
]
},
@ -502,7 +338,7 @@ class CustomerServices {
dataIndex: 'TotalCost',
sorter: (a, b) => a.TotalCost - b.TotalCost,
children: [{
title: total1.TotalCost,
title: total1.totalcost,
dataIndex: 'TotalCost'
}
]
@ -512,200 +348,13 @@ class CustomerServices {
dataIndex: 'TotalPrice',
sorter: (a, b) => a.TotalPrice - b.TotalPrice,
children: [{
title: total1.TotalPrice,
title: total1.totalprice,
dataIndex: 'TotalPrice'
}
]
}
];
});
}
})
.then(() => {
this.nationality_count_data.loading = false;
});
}
fetchDestinationGroupCount() {
this.inProgress = true;
const fetchUrl = prepareUrl(config.HT_HOST + '/service-web/QueryData/GetdistGroupInfoAll')
.append('DateType', this.dateType)
.append('Date1', this.startDateString)
.append('Date2', this.endDateString)
.append('OldDate1', this.startDateDiffString)
.append('OldDate2', this.endDateDiffString)
.append('DepList', this.selectedTeam)
.append('Country', this.selectedCountry)
.append('OrderStatus', this.selectedOrderStatus)
.build();
req.fetchJSON(fetchUrl)
.then(json => {
if (json.errcode === 0) {
runInAction(() => {
if (isEmpty(json.result2)){
const splitTotalList = groupBy(json.result1, row => row.COLD_ServiceCity === -1 ? '0' : '1');
this.agentGroupList = splitTotalList['1'];
const total1 = splitTotalList['0']?.[0] || {}; // json.total1;
this.destinationGroupCount = splitTotalList['1'];
this.destinationGroupCountColumns = [
{
title: '城市',
dataIndex: 'COLD_ServiceCityName',
fixed: 'left',
children: [{
dataIndex: 'COLD_ServiceCityName',
fixed: 'left',
render: (text, record) => <NavLink to={`/destination/${record.COLD_ServiceCity}/group/list`}>{record.COLD_ServiceCityName}</NavLink>
}
]
},
{
title: '团数',
dataIndex: 'GroupCount',
sorter: (a, b) => a.GroupCount - b.GroupCount,
children: [{
title: total1.GroupCount,
dataIndex: 'GroupCount'
}
]
},
{
title: '人数',
dataIndex: 'PersonNum',
sorter: (a, b) => a.PersonNum - b.PersonNum,
children: [{
title: total1.PersonNum,
dataIndex: 'PersonNum'
}
]
},
{
title: '团天数',
dataIndex: 'GroupDays',
sorter: (a, b) => a.GroupDays - b.GroupDays,
children: [{
title: total1.GroupDays,
dataIndex: 'GroupDays'
}
]
},
{
title: '交易额',
dataIndex: 'TotalCost',
sorter: (a, b) => a.TotalCost - b.TotalCost,
children: [{
title: total1.TotalCost,
dataIndex: 'TotalCost'
}
]
},
{
title: '报价',
dataIndex: 'TotalPrice',
sorter: (a, b) => a.TotalPrice - b.TotalPrice,
children: [{
title: total1.TotalPrice,
dataIndex: 'TotalPrice'
}
]
}
];
}
else{
const splitTotalList1 = groupBy(json.result1, row => row.COLD_ServiceCity === -1 ? '0' : '1');
const splitTotalList2 = groupBy(json.result2, row => row.COLD_ServiceCity === -1 ? '0' : '1');
const result = [];
for (const item1 of splitTotalList1['1']) {
for (const item2 of splitTotalList2['1']) {
if (item1.COLD_ServiceCity === item2.COLD_ServiceCity) {
result.push({
COLD_ServiceCity: item1.COLD_ServiceCity,
COLD_ServiceCityName: item1.COLD_ServiceCityName,
GroupCount: show_vs_tag(formatPercent((item1.GroupCount-item2.GroupCount)/(item2.GroupCount===0?1:item2.GroupCount)),
item1.GroupCount-item2.GroupCount,item1.GroupCount,item2.GroupCount),
PersonNum: show_vs_tag(formatPercent((item1.PersonNum-item2.PersonNum)/(item2.PersonNum===0?1:item2.PersonNum)),
item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum),
GroupDays: show_vs_tag(formatPercent((item1.GroupDays-item2.GroupDays)/(item2.GroupDays===0?1:item2.GroupDays)),
item1.GroupDays-item2.GroupDays,item1.GroupDays,item2.GroupDays),
TotalCost: show_vs_tag(formatPercent((item1.TotalCost-item2.TotalCost)/(item2.TotalCost===0?1:item2.TotalCost)),
(item1.TotalCost-item2.TotalCost).toFixed(2),item1.TotalCost,item2.TotalCost),
TotalPrice: show_vs_tag(formatPercent((item1.TotalPrice-item2.TotalPrice)/(item2.TotalPrice===0?1:item2.TotalPrice)),
item1.TotalPrice-item2.TotalPrice,item1.TotalPrice,item2.TotalPrice),
key:item1.key,
});
}
}
}
this.destinationGroupCount = result;
const total1 = splitTotalList1['0']?.[0] || {};
const total2 = splitTotalList2['0']?.[0] || {};
this.destinationGroupCountColumns = [
{
title: '城市',
dataIndex: 'COLD_ServiceCityName',
fixed: 'left',
children: [{
dataIndex: 'COLD_ServiceCityName',
fixed: 'left',
render: (text, record) => <NavLink to={`/destination/${record.COLD_ServiceCity}/group/list`}>{record.COLD_ServiceCityName}</NavLink>
}
]
},
{
title: '团数',
dataIndex: 'GroupCount',
sorter: (a, b) => a.GroupCount - b.GroupCount,
children: [{
title: show_vs_tag(formatPercent((total1.GroupCount-total2.GroupCount)/total2.GroupCount),total1.GroupCount-total2.GroupCount,total1.GroupCount,total2.GroupCount),
dataIndex: 'GroupCount'
}
]
},
{
title: '人数',
dataIndex: 'PersonNum',
sorter: (a, b) => a.PersonNum - b.PersonNum,
children: [{
title: show_vs_tag(formatPercent((total1.PersonNum-total2.PersonNum)/total2.PersonNum),total1.PersonNum-total2.PersonNum,total1.PersonNum,total2.PersonNum),
dataIndex: 'PersonNum'
}
]
},
{
title: '团天数',
dataIndex: 'GroupDays',
sorter: (a, b) => a.GroupDays - b.GroupDays,
children: [{
title: show_vs_tag(formatPercent((total1.GroupDays-total2.GroupDays)/total2.GroupDays),total1.GroupDays-total2.GroupDays,total1.GroupDays,total2.GroupDays),
dataIndex: 'GroupDays'
}
]
},
{
title: '交易额',
dataIndex: 'TotalCost',
sorter: (a, b) => a.TotalCost - b.TotalCost,
children: [{
title: show_vs_tag(formatPercent((total1.TotalCost-total2.TotalCost)/total2.TotalCost),
(total1.TotalCost-total2.TotalCost).toFixed(2),total1.TotalCost,total2.TotalCost),
dataIndex: 'TotalCost'
}
]
},
{
title: '报价',
dataIndex: 'TotalPrice',
sorter: (a, b) => a.TotalPrice - b.TotalPrice,
children: [{
title: show_vs_tag(formatPercent((total1.TotalPrice-total2.TotalPrice)/total2.TotalPrice),
(total1.TotalPrice-total2.TotalPrice).toFixed(2),total1.TotalPrice,total2.TotalPrice),
dataIndex: 'TotalPrice'
}
]
}
];
}
});
}
})
.then(() => {
@ -716,6 +365,8 @@ class CustomerServices {
fetchGroupListByDestinationId(destinationId) {
this.inProgress = true;
this.destinationName = '...';
this.destinationGroupList = [];
this.destinationGroupListColumns = [];
const fetchUrl = prepareUrl(config.HT_HOST + '/service-web/QueryData/GetdistGroupInfo')
.append('city', destinationId)
.append('DateType', this.dateType)
@ -725,7 +376,6 @@ class CustomerServices {
.append('OldDate2', this.endDateString)
.append('DepList', this.selectedTeam)
.append('Country', this.selectedCountry)
.append('OrderStatus', this.selectedOrderStatus)
.build();
req.fetchJSON(fetchUrl)
.then(json => {
@ -803,23 +453,6 @@ class CustomerServices {
});
}
searchValues = {
DateType: { key: 'departureDate', label: '抵达日期'},
// departureDateType: { key: 'departureDate', label: '抵达日期'},
};
setSearchValues(obj, values) {
this.dateType = obj.DateType;
this.selectedAgent = obj.agency;
this.startDateString = obj.Date1;
this.endDateString = obj.Date2;
this.startDateDiffString = obj.DateDiff1;
this.endDateDiffString = obj.DateDiff2;
this.selectedCountry = obj.countryArea;
this.selectedTeam = (obj.DepartmentList || '').replace('ALL', '');
this.selectedOrderStatus = obj.orderStatus;
}
selectDateRange(startDate, endDate) {
this.startDate = startDate;
this.endDate = endDate;
@ -859,7 +492,7 @@ class CustomerServices {
inProgress;
agentList = [];
groupList = [];
groupListColumns = [];
@ -867,15 +500,6 @@ class CustomerServices {
destinationGroupCount = [];
destinationGroupCountColumns =[];
destinationGroupList = [];
destinationGroupListColumns = [];
// 国籍统计
nationality_count_data = {
loading: false,
destinationGroupByCountryList:[],
destinationGroupByCountryListColumns:[]
};
agentGroupList = [{
EOI_ObjSN: 1,
VendorName: '---',
@ -894,7 +518,6 @@ class CustomerServices {
{
title: '地接社名称',
dataIndex: 'VendorName',
fixed: 'left',
render: (text, record) => {
if (record.EOI_ObjSN === -1) {
return text;
@ -946,4 +569,4 @@ class CustomerServices {
];
}
export default CustomerServices;
export default CustomerServices;

@ -1,17 +1,6 @@
import {makeAutoObservable, runInAction, toJS } from "mobx";
import { fetchJSON } from '../utils/request';
import {makeAutoObservable, runInAction} from "mobx";
import * as config from "../config";
import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht';
import { sortBy, show_vs_tag, formatPercent, groupBy, isEmpty, uniqWith, formatPercentToFloat } from "../utils/commons";
import moment from 'moment';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
class CustomerStore {
@ -27,16 +16,16 @@ class CustomerStore {
const date_picker_store = this.rootStore.date_picker_store;
let url = '/service-tourdesign/PotentialCusOrder';
url += '?Website=' + this.potential_data.webcode.toString() + '&DEI_SNList=' + this.potential_data.groups.toString();
if (String(this.potential_data.date_type).toLowerCase() === 'applydate') {
if (this.potential_data.date_type == 'applyDate') {
url += '&ApplydateCheck=1&EntrancedateCheck=0&ConfirmDateCheck=0';
} else if(String(this.potential_data.date_type).toLowerCase() === 'confirmdate'){
} else if(this.potential_data.date_type == 'ConfirmDate'){
url += '&ApplydateCheck=0&EntrancedateCheck=0&ConfirmDateCheck=1';
}else {
url += '&ApplydateCheck=0&EntrancedateCheck=1&ConfirmDateCheck=0';
}
url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&ConfirmdateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
url += '&ConfirmdateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
if (get_detail) {
url += '&IsDetail=1';
} else {
@ -90,186 +79,47 @@ class CustomerStore {
potential_customer_order: this.potential_customer_order.bind(this),
onChange_show_detail_table: this.onChange_show_detail_table.bind(this),
handleChange_webcode: this.handleChange_webcode.bind(this),
searchValues: {
DepartmentList: ['1', '2', '7'].map(kk => groupsMappedByKey[kk]),
WebCode: ['GHKYZG'].map(kk => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期'},
},
};
// 潜力客户 end
// 老客户 beign
// isCompare对比数据boolean isCompareRender对比折线boolean
regular_customer_order(get_detail = false, isCompare = false, isCompareRender=false) {
let pivotByOrder = 'SumOrder';
let pivotByDate = 'applyDate';
regular_customer_order(get_detail = false) {
this.regular_data.loading = true;
this.regular_data.detail_loading = get_detail;
const date_picker_store = this.rootStore.date_picker_store;
let url = '/service-tourdesign/RegularCusOrder';
url += '?Website=' + this.regular_data.webcode.toString() + '&DEI_SNList=' + this.regular_data.groups.toString();
if (String(this.regular_data.date_type).toLowerCase() === 'applydate') {
if (this.regular_data.date_type == 'applyDate') {
url += '&ApplydateCheck=1&EntrancedateCheck=0&ConfirmDateCheck=0';
} else if(String(this.regular_data.date_type).toLowerCase() === 'confirmdate'){
} else if(this.regular_data.date_type == 'ConfirmDate'){
url += '&ApplydateCheck=0&EntrancedateCheck=0&ConfirmDateCheck=1';
pivotByOrder = 'ConfirmOrder';
pivotByDate = 'confirmDate';
}else {
url += '&ApplydateCheck=0&EntrancedateCheck=1&ConfirmDateCheck=0';
pivotByOrder = 'ConfirmOrder';
pivotByDate = 'startDate';
}
if (isCompare){
url += '&ApplydateStart=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += '&EntrancedateStart=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += '&ConfirmdateStart=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
}
else{
url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&ConfirmdateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
}
url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
url += '&ConfirmdateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
if (get_detail) {
url += '&IsDetail=1';
} else {
url += '&IsDetail=0';
}
url += `&IncludeTickets=${this.regular_data.include_tickets}`;
return new Promise((resolve, reject) => {
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
runInAction(() => {
if (get_detail) {
if (!isCompare){
this.regular_data.data_detail = json;
}
if (isCompareRender){
this.regular_data.solidLineTime=date_picker_store.start_date.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
this.regular_data.solidLineCompareTime=date_picker_store.start_date_cp.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
const dump_l = (json || []).filter(ele => ele.COLI_IsOld !== '' && ele.COLI_IsCusCommend !== '').length;
this.regular_data.total_data_tips = dump_l > 0 ? `包含 ${dump_l} 条同时勾选的数据` : '';
/** 使用明细数据画图 */
const data_detail = (json || []).map((ele) => ({
...ele,
key: ele.COLI_ID,
orderState: ele.OrderState,
applyDate: moment(ele.COLI_ApplyDate).format('YYYY-MM-DD'),
startDate: ele.COLI_OrderStartDate,
confirmDate: moment(ele.COLI_ConfirmDate).format('YYYY-MM-DD'),
}));
const { data: IsOldData, } = pivotBy(data_detail.filter(ele => ele.COLI_IsOld === '是'), [['COLI_IsOld', ], [], pivotByDate]);
const { data: isCusCommendData, } = pivotBy(data_detail.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));
const seriesNewData = seriesData.map(item => {
if (isCompare){
return {
...item,
_ylabel: date_picker_store.start_date_cp.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT) + item._ylabel
};
}
else{
return {
...item,
_ylabel: date_picker_store.start_date.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT) + item._ylabel
};
}
});
// console.log('seriesData====', seriesNewData);
if (this.regular_data.data_compare.length===0){
this.regular_data.data_compare=seriesNewData;
}
else{
let seriesCompareData = [];
const fistCompareDetail = this.regular_data.data_compare;
if (fistCompareDetail.length>seriesNewData.length){
seriesCompareData = fistCompareDetail;
for (let i = 0; i < seriesNewData.length; i++) {
seriesNewData[i][pivotByDate] = fistCompareDetail[i][pivotByDate];
}
seriesCompareData.push(...seriesNewData);
}
else{
seriesCompareData=seriesNewData;
for (let i = 0; i < fistCompareDetail.length; i++) {
fistCompareDetail[i][pivotByDate] = seriesNewData[i][pivotByDate];
}
seriesCompareData.push(...fistCompareDetail);
}
this.regular_data.detail_loading = false;
this.regular_data.pivotData = seriesCompareData; // { IsOldData, isCusCommendData, };
this.regular_data.pivotY = pivotByOrder;
this.regular_data.pivotX = pivotByDate;
}
}
else{
this.regular_data.detail_loading = false;
const dump_l = (json || []).filter(ele => ele.COLI_IsOld !== '' && ele.COLI_IsCusCommend !== '').length;
this.regular_data.total_data_tips = dump_l > 0 ? `包含 ${dump_l} 条同时勾选的数据` : '';
/** 使用明细数据画图 */
const data_detail = (json || []).map((ele) => ({
...ele,
key: ele.COLI_ID,
orderState: ele.OrderState,
applyDate: moment(ele.COLI_ApplyDate).format('YYYY-MM-DD'),
startDate: ele.COLI_OrderStartDate,
confirmDate: moment(ele.COLI_ConfirmDate).format('YYYY-MM-DD'),
}));
const { data: IsOldData, } = pivotBy(data_detail.filter(ele => ele.COLI_IsOld === '是'), [['COLI_IsOld', ], [], pivotByDate]);
const { data: isCusCommendData, } = pivotBy(data_detail.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));
this.regular_data.pivotData = seriesData; // { IsOldData, isCusCommendData, };
this.regular_data.pivotY = pivotByOrder;
this.regular_data.pivotX = pivotByDate;
}
this.regular_data.data_detail = json;
} else {
if (isCompare){
const result = [];
const firstCompareData = this.regular_data.data;
for (const item1 of firstCompareData) {
for (const item2 of json) {
if (item1.ItemName === item2.ItemName) {
result.push({
ItemName: item1.ItemName,
OrderNum: show_vs_tag(formatPercent((item1.OrderNum-item2.OrderNum)/(item2.OrderNum===0?1:item2.OrderNum)),
item1.OrderNum-item2.OrderNum,item1.OrderNum,item2.OrderNum),
SUCOrderNum: show_vs_tag(formatPercent((item1.SUCOrderNum-item2.SUCOrderNum)/(item2.SUCOrderNum===0?1:item2.SUCOrderNum)),
item1.SUCOrderNum-item2.SUCOrderNum,item1.SUCOrderNum,item2.SUCOrderNum),
OrderRate: show_vs_tag(formatPercent((item1.OrderRate-item2.OrderRate)/item2.OrderRate),
formatPercentToFloat(item1.OrderRate-item2.OrderRate),formatPercentToFloat(item1.OrderRate),formatPercentToFloat(item2.OrderRate)),
SUCRate: show_vs_tag(formatPercent((item1.SUCRate-item2.SUCRate)/(item2.SUCRate===0?1:item2.SUCRate)),
formatPercent(item1.SUCRate-item2.SUCRate),formatPercent(item1.SUCRate),formatPercent(item2.SUCRate)),
ML: show_vs_tag(formatPercent((item1.ML-item2.ML)/(item2.ML===0?1:item2.ML)),
(item1.ML-item2.ML).toFixed(2),item1.ML,item2.ML),
OrderMLRate: show_vs_tag(formatPercent((item1.OrderMLRate-item2.OrderMLRate)/item2.OrderMLRate),
formatPercentToFloat(item1.OrderMLRate-item2.OrderMLRate),formatPercentToFloat(item1.OrderMLRate),formatPercentToFloat(item2.OrderMLRate)),
PersonNum: show_vs_tag(formatPercent((item1.PersonNum-item2.PersonNum)/(item2.PersonNum===0?1:item2.PersonNum)),
item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum),
});
}
}
};
this.regular_data.data = result;
}
else{
this.regular_data.data = json;
}
this.regular_data.data = json;
}
this.regular_data.loading = false;
resolve();
});
})
.catch((error) => {
this.regular_data.loading = false;
console.log('fetch data failed', error);
});
});
}
handleChange_webcode_regular = (value) => {
@ -290,38 +140,18 @@ class CustomerStore {
regular_data = {
loading: false,
detail_loading: false,
data: [],
data_detail: [],
data_compare: [],
showCompareSum:false,
solidLineTime:'',
solidLineCompareTime:'',
solidLineDash:'老客户推荐',
isCompareLine:false,
total_data_tips: '',
webcode: 'ALL',
site_select_mode: 'multiple',// 站点是否多选
group_select_mode: 'multiple',// 是否多选分组
groups: ['1', '2', '28', '7'],
date_type: 'applyDate',
include_tickets: 0,
group_handleChange: this.handleChange_group_select_regular.bind(this),
onChange_datetype: this.onChange_datetype_regular.bind(this),
regular_customer_order: this.regular_customer_order.bind(this),
onChange_show_detail_table: this.onChange_show_detail_table_regular.bind(this),
handleChange_webcode: this.handleChange_webcode_regular.bind(this),
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map(kk => groupsMappedByKey[kk]),
WebCode: ['CHT','AH','JH','GH','ZWQD','GH_ZWQD_HW','GHKYZG','GHKYHW'].map(kk => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期'},
IncludeTickets: { key: '0', label: '不含门票' },
},
pivotData: [],
pivotY: 'SumOrder',
pivotX: 'applyDate',
};
// 老客户 end
@ -332,9 +162,9 @@ class CustomerStore {
const date_picker_store = this.rootStore.date_picker_store;
let url = '/service-tourdesign/RegularCusInChinaOrder';
url += '?Website=' + this.inchina_data.webcode.toString() + '&DEI_SNList=' + this.inchina_data.groups.toString();
if (String(this.inchina_data.date_type).toLowerCase() === 'applydate') {
if (this.inchina_data.date_type == 'applyDate') {
url += '&ApplydateCheck=1&EntrancedateCheck=0&ConfirmDateCheck=0';
} else if(String(this.inchina_data.date_type).toLowerCase() === 'confirmdate'){
} else if(this.inchina_data.date_type == 'ConfirmDate'){
url += '&ApplydateCheck=0&EntrancedateCheck=0&ConfirmDateCheck=1';
}else {
url += '&ApplydateCheck=0&EntrancedateCheck=1&ConfirmDateCheck=0';
@ -344,9 +174,9 @@ class CustomerStore {
} else {
url += '&IsDetail=0';
}
url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&ConfirmdateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
url += '&ConfirmdateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ConfirmdateEnd=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
@ -395,204 +225,9 @@ class CustomerStore {
inchina_customer_order: this.inchina_customer_order.bind(this),
onChange_show_detail_table: this.onChange_show_detail_table_inchina.bind(this),
handleChange_webcode: this.handleChange_webcode_inchina.bind(this),
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map(kk => groupsMappedByKey[kk]),
WebCode: undefined, // ['ALL'].map(kk => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期'},
},
};
// 在华客人 end
// 东道主项目 begin
host_case_data = {
loading: false,
summaryData: [], // 汇总数据
groupData: [], // 小组数据
counselorData: [], // 顾问数据
singleDetailData:[], // 单团详细数据
group_select_mode: 'multiple',
groups: ['1', '2', '28', '7'],
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map(kk => groupsMappedByKey[kk]),
},
};
getHostCaseData(groupBy) {
this.host_case_data.loading = true;
const date_picker_store = this.rootStore.date_picker_store;
let url = '/service-Analyse2/DDZCount';
url += '?DEI_SN=' + this.host_case_data.groups.toString();
url += '&ArriveDateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ArriveDateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += '&GroupBy=' + groupBy;
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
runInAction(() => {
switch(groupBy){
case "1":
this.host_case_data.summaryData = json.result?json.result:[];
break;
case "2":
this.host_case_data.counselorData = json.result?json.result:[];
break;
case "3":
this.host_case_data.groupData = json.result?json.result:[];
break;
case "4":
this.host_case_data.singleDetailData = json.result?json.result:[];
this.host_case_data.loading = false;
break;
}
});
})
.catch((error) => {
this.host_case_data.loading = false;
console.log('fetch data failed', error);
});
};
// 东道主项目 end
// 销售-老客户
sales_regular_data = {
loading: false,
// data: [],
// mergedData: [],
rawData: [],
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map(kk => groupsMappedByKey[kk]),
WebCode: ['CHT','AH','JH','GH','ZWQD','GH_ZWQD_HW','GHKYZG','GHKYHW'].map(kk => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期'},
IncludeTickets: { key: '0', label: '不含门票' },
},
// pivotData: {
// operatorName: { loading: false, data: [], rawData: [], mergedData: [], filterColValues: [] },
// country: { loading: false, data: [], rawData: [], mergedData: [], filterColValues: [] },
// },
pivotResult: [],
filterColValues: [],
rawDataArr: [],
};
get_sales_regular_data_vs = async (param, pivotRow = 'operatorName', pivotCol = 'hasOld') => {
this.sales_regular_data.loading = true;
const hasCompare = !isEmpty(param.DateDiff1);
const [result1, result2] = await Promise.all([
this.get_sales_regular_data(param),
hasCompare ? this.get_sales_regular_data({...param, Date1: param.DateDiff1, Date2: param.DateDiff2}) : { filterHasOld: []},
]);
// 合并顾问的账户
// let mergeDataBySalesAccount = [];
// if (pivotRow === 'operatorName') {
// const allSalesMerged = Array.from(new Set([...result1.mergeDataBySalesAccount.map(row=>row.operatorName), ...result2.mergeDataBySalesAccount.map(row=>row.operatorName)]));
// const salesM1 = groupBy(result1.mergeDataBySalesAccount, 'operatorName');
// const salesM2 = groupBy(result2.mergeDataBySalesAccount, 'operatorName');
// mergeDataBySalesAccount = allSalesMerged.reduce((r, sale) => {
// const _default = { operatorName: sale, rowLabel: salesM1?.[sale]?.rowLabel || salesM2?.[sale]?.rowLabel, children: salesM1?.[sale]?.children || [], key: sale};
// const operatorRow = {...(salesM1?.[sale]?.[0] || _default), vsData: salesM2?.[sale]?.[0] || {}};
// // 展开的两项: '老客户', '老客户推荐'
// const series1Children = salesM1?.[sale]?.[0]?.children || [];
// const series2Children = salesM2?.[sale]?.[0]?.children || [];
// const children = allTypes.reduce((r, type) => {
// const _default = { operatorName: type, rowLabel: type, key: type};
// const _typeRow = series1Children.find(sc => sc.operatorName === type) || _default;
// const _typeVSRow = series2Children.find(sc => sc.operatorName === type) || {};
// return r.concat({..._typeRow, vsData: _typeVSRow});
// }, []);
// operatorRow.children = children;
// return r.concat(operatorRow);
// }, []);
// }
this.sales_regular_data.loading = false;
this.sales_regular_data.rawData = [].concat(result1.filterHasOld, result2.filterHasOld);
this.sales_regular_data.rawDataArr = [result1, result2];
};
get_sales_regular_data = async (param) => {
const seriesKey = `${param.Date1}${param.Date2}`;
const rawData = await getDetailData({...param, });
const filterHasOld = rawData.filter(ele => (ele.IsOld === '1' || ele.isCusCommend === '1')).map(e => ({
...e,
seriesKey,
operatorNameB: e.operatorName.replace(/\([^)]*\)/gi, '').toLowerCase(),
}));
// 合并顾问的账户
// let mergeDataBySalesAccount = [];
// if (pivotRow === 'operatorName') {
// const { data: hasOldDataSales, } = pivotBy(filterHasOld, [['hasOld', 'operatorNameB'], [], []]);
// const { data: IsOldDataSales, } = pivotBy(filterHasOld.filter(ele => ele.IsOld === '1'), [['operatorNameB', 'IsOld_txt', ], [], []]);
// const { data: isCusCommendDataSales, } = pivotBy(filterHasOld.filter(ele => ele.isCusCommend === '1'), [['operatorNameB', 'isCusCommend_txt', ], [], []]);
// mergeDataBySalesAccount = hasOldDataSales.map((ele) => ({
// ...ele,
// operatorName: ele.operatorNameB,
// seriesKey,
// children: [].concat(
// IsOldDataSales.filter((ele1) => ele1.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.IsOld_txt, key: o.rowLabel, seriesKey,})),
// isCusCommendDataSales.filter((ele2) => ele2.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel, seriesKey,}))
// ),
// }));
// }
// console.log('IsOldDataSales====', IsOldDataSales, '\nisCusCommendDataSales', isCusCommendDataSales);
// console.log('mergeDataByRow====\n', mergeDataByRow);
// return { mergeDataBySales, mergeDataBySalesAccount, filterHasOld };
return { filterHasOld };
};
regular_data_pivot = (pivotRow, pivotCol) => {
// console.clear();
// this.sales_regular_data.rawData;
// console.log('regular_data_pivot -------------------------------------------------------------- rawData 000 ', toJS(this.sales_regular_data.rawData));
// console.log('regular_data_pivot ---- ', pivotRow, pivotCol);
const [result1, result2] = this.sales_regular_data.rawDataArr;
const allRows = Array.from(new Set([...result1.filterHasOld.map(row=>row[pivotRow]), ...result2.filterHasOld.map(row=>row[pivotRow])]));
// console.log(' ------ allRows', allRows);
const [pivot1, pivot2] = [result1, result2].map(_result => {
const dataColField = pivotCol.replace('_txt', '');
const rawData = pivotCol === 'hasOld' ? _result.filterHasOld : _result.filterHasOld.filter((ele) => ele[dataColField] === '1');
const { data: pivotResult } = pivotBy(rawData, [[pivotRow, pivotCol], [], []]);
return pivotResult;
});
const rows1 = groupBy(pivot1, pivotRow);
const rows2 = groupBy(pivot2, pivotRow);
const pivotResultWithCompare = isEmpty(pivot2) ? pivot1 : allRows.reduce((r, rowName) => {
const _default = { [pivotRow]: rowName, rowLabel: rows1?.[rowName]?.rowLabel || rows2?.[rowName]?.rowLabel, children: rows1?.[rowName], key: rowName};
const operatorRow = {...(rows1?.[rowName]?.[0] || _default), vsData: rows2?.[rowName]?.[0] || {}};
// 展开的两项: '老客户', '老客户推荐'
// const series1Children = rows1?.[rowName]?.[0]?.children || [];
// const series2Children = rows2?.[rowName]?.[0]?.children || [];
// const children = allTypes.reduce((r, type) => {
// const _default = { [pivotRow]: type, rowLabel: type, key: type};
// const _typeRow = series1Children.find(sc => sc[pivotRow] === type) || _default;
// const _typeVSRow = series2Children.find(sc => sc[pivotRow] === type) || {};
// return r.concat({..._typeRow, vsData: _typeVSRow});
// }, []);
// operatorRow.children = children;
return r.concat(operatorRow);
}, []);
// console.log(' ------ pivot1', pivot1, '\npivot2', pivot2, '\npivotResultWithCompare', pivotResultWithCompare);
const filterColValues = uniqWith(
allRows.map((rr) => ({ text: rr, value: rr })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)
).sort((a, b) => a.text.localeCompare(b.text, 'zh-CN'));
this.sales_regular_data.pivotResult = pivotResultWithCompare;
this.sales_regular_data.filterColValues = filterColValues;
};
setSearchValues(obj, values, target) {
this[target].groups = obj.DepartmentList;
this[target].webcode = obj.WebCode;
this[target].include_tickets = obj.IncludeTickets;
this[target].date_type = obj.DateType;
}
}

@ -1,7 +1,6 @@
import { makeAutoObservable, runInAction } from "mobx";
import * as config from "../config";
import { resultDataCb } from '../components/DateGroupRadio/date';
import { groupsMappedByKey } from './../libs/ht';
class DashboardStore {
constructor(rootStore) {
@ -34,7 +33,7 @@ class DashboardStore {
this.orders_data.loading = true;
const date_picker_store = this.rootStore.date_picker_store;
let url = "/service-web/QueryData/GetOrderCount";
url += "?WebCode=all&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "?WebCode=all&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
fetch(config.HT_HOST + url)
.then(response => response.json())
@ -79,7 +78,7 @@ class DashboardStore {
const date_picker_store = this.rootStore.date_picker_store;
let url = "/service-baseinfo/QueryWebData?type=orders_temp&db=1";
const website_code = "'" + this.ordersTemp_data.webcode.join("','") + "'";
url += "&WebSite=" + website_code + "&ApplyDateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&ApplyDateEnd=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&WebSite=" + website_code + "&ApplyDateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&ApplyDateEnd=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
fetch(config.HT_HOST + url)
.then(response => response.json())
.then(json => {
@ -100,7 +99,7 @@ class DashboardStore {
const date_picker_store = this.rootStore.date_picker_store;
let url = "/service-baseinfo/QueryWebData?type=orders_temp_detail&db=1";
const website_code = "'" + this.ordersTemp_data.webcode.join("','") + "'";
url += "&WebSite=" + website_code + "&ApplyDateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&ApplyDateEnd=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&WebSite=" + website_code + "&ApplyDateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&ApplyDateEnd=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
fetch(config.HT_HOST + url)
.then(response => response.json())
.then(json => {
@ -139,8 +138,8 @@ class DashboardStore {
} else {
url += "&ApplydateCheck=0&OrderStartdateCheck=1";
}
url += "&ApplydateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&ApplydateEnd=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&OrderStartdateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&OrderStartdateEnd=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&ApplydateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&ApplydateEnd=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
url += "&OrderStartdateStart=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&OrderStartdateEnd=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
fetch(config.HT_HOST + url)
.then(response => response.json())
.then(json => {
@ -198,17 +197,7 @@ class DashboardStore {
group_handleChange: this.handleChange_group_select.bind(this),
onChange_datetype: this.onChange_datetype.bind(this),
asyncFetch: this.get_CountYDOrder.bind(this),
mobileSearchValues: {
'DateType': { key: 'applyDate', value: 'applyDate', label: '预定日期'},
'DepartmentList': ["1", "2", "28", "7", "8", "9", "11", "12", "20", "21", "18"].map(ele => groupsMappedByKey[ele] ),
}
};
setMobileSearchValues(obj, values) {
this.mobile_data.date_type = obj.DateType;
this.mobile_data.groups = obj.DepartmentList;
}
// 移动成交 end
// 汇率变化 begin
@ -222,7 +211,7 @@ class DashboardStore {
const date_picker_store = this.rootStore.date_picker_store;
this.exchangeRate_data.loading = true;
let url = "/service-web/QueryData/GetCurrency?Currency=ALL";
url += "&Currdate1=" + Currdate1_start + "&Currdate2=" + Currdate1_end + "%2023:59:00";
url += "&Currdate1=" + Currdate1_start + "&Currdate2=" + Currdate1_end + "%2023:59:59";
fetch(config.HT_HOST + url)
.then(response => response.json())
.then(json => {

@ -1,51 +0,0 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt, cloneDeep } from '../utils/commons';
import { dataFieldAlias } from './../libs/ht';
class Trade {
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
}
/**
* 明细
*/
getDetailData = async (param, page) => {
this.detailData[page] = { loading: true, dataSource: [], originData: [] };
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
if (json.errcode === 0) {
runInAction(() => {
this.detailData[page].loading = false;
this.detailData[page].dataSource = json.result;
this.detailData[page].originData = json.result;
});
}
return json.result;
};
setSearchValues(body) {
this.searchValues = body;
}
timeLineKey = 'week';
setTimeLineKey(v) {
this.timeLineKey = v;
}
resetData = () => {
this.detailData = {
orders: { loading: false, dataSource: [], originData: [] },
trade: { loading: false, dataSource: [], originData: [] },
};
};
searchValues = {};
detailData = {
orders: { loading: false, dataSource: [], originData: [] },
trade: { loading: false, dataSource: [], originData: [] },
};
}
export default Trade;

@ -1,6 +1,5 @@
import {makeAutoObservable} from "mobx";
import moment from "moment";
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT } from '../config';
/**
* 管理搜索组件的状态
*/
@ -46,7 +45,7 @@ class DatePickerStore {
'DepartmentList': { 'key': 'ALL', 'label': '所有小组' },
'WebCode': { 'key': 'ALL', 'label': '所有来源' },
'IncludeTickets': { 'key': '1', 'label': '含门票' },
'DateType': { 'key': 'confirmDate', 'label': '确认日期' },
'DateType': { 'key': 'ConfirmDate', 'label': '确认日期' },
'year': this.start_date,
// 'months': [moment(), moment()],
'dates': [this.start_date, this.end_date],
@ -56,9 +55,9 @@ class DatePickerStore {
DepartmentList: 'ALL',
WebCode: 'ALL',
IncludeTickets: '1',
DateType: 'confirmDate',
Date1: this.start_date.format(DATE_FORMAT),
Date2: this.end_date.format(SMALL_DATETIME_FORMAT),
DateType: 'ConfirmDate',
Date1: this.start_date.format('YYYY-MM-DD'),
Date2: this.end_date.format('YYYY-MM-DD 23:59:59'),
};
setFormValues(data){
@ -68,11 +67,6 @@ class DatePickerStore {
setFormValuesToSub(data){
this.formValuesToSub = data;
}
siderBroken = false;
setSiderBroken(broken){
this.siderBroken = broken;
}
}
export default DatePickerStore;

@ -1,19 +1,19 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import { DATE_FORMAT } from './../config';
import moment from 'moment';
import { isEmpty, pick, sortBy, fixTo2Decimals, cloneDeep, unique } from '../utils/commons';
const modelMapper = {
'tourDays': { url: '/service-Analyse2/GetTradeApartByTourDays', keySort: true, dynamicsX: false },
'PML': { url: '/service-Analyse2/GetTradeApartByPML', keySort: true, dynamicsX: false },
'ConfirmDays': { url: '/service-Analyse2/GetTradeApartByConfirmDays', keySort: true, dynamicsX: false },
'ApplyDays': { url: '/service-Analyse2/GetTradeApartByApplyDays', keySort: true, dynamicsX: false },
'PersonNum': { url: '/service-Analyse2/GetTradeApartByPersonNum', keySort: true, dynamicsX: false },
'destination': { url: '/service-Analyse2/GetTradeApartByDestination', keySort: false, dynamicsX: true, },
'GlobalDestination': { url: '/service-Analyse2/GetTradeApartByGlobalDestination', keySort: false, dynamicsX: true, },
'destinationCountry': { url: '/service-Analyse2/GetTradeApartByDestinationCountry', keySort: false, dynamicsX: true, },
'guestCountry': { url: '/service-Analyse2/GetTradeApartByGuestCountry', keySort: false, dynamicsX: true, },
'tourDays': { url: '/service-Analyse2/GetTradeApartByTourDays', keySort: true },
'PML': { url: '/service-Analyse2/GetTradeApartByPML', keySort: true },
'ConfirmDays': { url: '/service-Analyse2/GetTradeApartByConfirmDays', keySort: true },
'ApplyDays': { url: '/service-Analyse2/GetTradeApartByApplyDays', keySort: true },
'PersonNum': { url: '/service-Analyse2/GetTradeApartByPersonNum', keySort: true },
'destination': { url: '/service-Analyse2/GetTradeApartByDestination', keySort: false },
'GlobalDestination': { url: '/service-Analyse2/GetTradeApartByGlobalDestination', keySort: false },
'destinationCountry': { url: '/service-Analyse2/GetTradeApartByDestinationCountry', keySort: false },
'guestCountry': { url: '/service-Analyse2/GetTradeApartByGuestCountry', keySort: false },
};
class Distribution {
constructor(appStore) {
@ -24,7 +24,7 @@ class Distribution {
/**
* 各个类型的分布
*/
getApartData = async (param, getDiff = undefined) => {
getApartData = async (param) => {
this.pageLoading = true;
const mkey = this.curTab;
this[mkey] = { loading: true, dataSource: [] };
@ -36,41 +36,25 @@ class Distribution {
const [DateToQ1, DateToQ2] = [moment(param.Date1).subtract(moment(param.Date2).diff(param.Date1, 'days') + 1, 'days'), moment(param.Date1).subtract(1, 'days')];
// 同比的参数: 去年同期
const [DateToY1, DateToY2] = [moment(param.Date1).subtract(1, 'year'), moment(param.Date2).subtract(1, 'year')];
const dynamicsX = getDiff ?? modelMapper[mkey].dynamicsX;
if (getDiff === undefined) {
param.DateToY1 = DateToY1.format(DATE_FORMAT);
param.DateToY2 = DateToY2.format(SMALL_DATETIME_FORMAT);
param.DateToQ1 = DateToQ1.format(DATE_FORMAT);
param.DateToQ2 = DateToQ2.format(SMALL_DATETIME_FORMAT);
}
const json = dynamicsX === false ? await req.fetchJSON(modelMapper[mkey].url, param) : await this.getApartDataStep(param);
param.DateToY1 = DateToY1.format(DATE_FORMAT);
param.DateToY2 = DateToY2.format(`${DATE_FORMAT} 23:59:59`);
param.DateToQ1 = DateToQ1.format(DATE_FORMAT);
param.DateToQ2 = DateToQ2.format(`${DATE_FORMAT} 23:59:59`);
const json = await req.fetchJSON(modelMapper[mkey].url, param);
if (json.errcode === 0) {
const dataLength = json.result.length;
const dataSource = calcDiff({ result: json.result, resultToY: json.resultToY, resultToQ: json.resultToQ }, modelMapper[mkey].keySort);
const pickResult = dataLength > 20 ? json.result.slice(0, 30) : json.result;
const dataSource = calcDiff({ result: pickResult, resultToY: json.resultToY, resultToQ: json.resultToQ }, modelMapper[mkey].keySort);
runInAction(() => {
this[mkey].loading = false;
this[mkey].originData = json.result;
this[mkey].dataSource = dataLength > 20 ? dataSource.slice(0,30) : dataSource;
this[mkey].dataSource = dataSource;
this.pageLoading = false;
});
}
return this[mkey];
};
getApartDataStep = async (param) => {
const mkey = this.curTab;
this[mkey] = { loading: true, dataSource: [] };
const xParam = cloneDeep(param);
delete xParam.DateToY1;
delete xParam.DateToY2;
delete xParam.DateToQ1;
delete xParam.DateToQ2;
const { result, ...jsonY } = await req.fetchJSON(modelMapper[mkey].url, { ...xParam });
const { result: resultToY } = await req.fetchJSON(modelMapper[mkey].url, { ...xParam, Date1: param.DateToY1, Date2: param.DateToY2 });
const { result: resultToQ } = await req.fetchJSON(modelMapper[mkey].url, { ...xParam, Date1: param.DateToQ1, Date2: param.DateToQ2 });
return { ...jsonY, result, resultToY, resultToQ };
};
/**
* 明细
*/
@ -85,7 +69,7 @@ class Distribution {
this.scatterDays = daysData;
});
}
return json.result;
return this.detailData;
};
resetData = () => {
@ -169,17 +153,6 @@ const calcDiff = ({ result, resultToY, resultToQ }, keySort) => {
: 0,
ConfirmOrderDiffY: resultMapped[row.key].ConfirmOrder - resultToYMapped[row.key].ConfirmOrder,
ConfirmOrderDiffQ: resultMapped[row.key].ConfirmOrder - resultToQMapped[row.key].ConfirmOrder,
SumOrderY: resultToYMapped?.[row.key]?.SumOrder || 0,
SumOrderToY: resultToYMapped?.[row.key]?.SumOrder
? fixTo2Decimals(((resultMapped[row.key].SumOrder - resultToYMapped[row.key].SumOrder) / resultToYMapped[row.key].SumOrder) * 100)
: 0,
SumOrderQ: resultToQMapped?.[row.key]?.SumOrder || 0,
SumOrderToQ: resultToQMapped?.[row.key]?.SumOrder
? fixTo2Decimals(((resultMapped[row.key].SumOrder - resultToQMapped[row.key].SumOrder) / resultToQMapped[row.key].SumOrder) * 100)
: 0,
SumOrderDiffY: resultMapped[row.key].SumOrder - resultToYMapped[row.key].SumOrder,
SumOrderDiffQ: resultMapped[row.key].SumOrder - resultToQMapped[row.key].SumOrder,
};
return { ...resultMapped[row.key], ...diff, resultToY: resultToYMapped[row.key], resultToQ: resultToQMapped[row.key] };
});

@ -85,20 +85,14 @@ class FinancialStore {
this.credit_card_data.groups = value;
};
setSearchValues(obj, values) {
this.credit_card_data.business_units = obj.businessUnits;
// this.credit_card_data.groups = obj.businessUnits;
this.bill_type_data.bill_types = obj.billtype;
}
// 请求信用卡账单
get_credit_card_bills() {
const date_picker_store = this.rootStore.date_picker_store;
let url = '/service-web/QueryData/GetCreditCardBills';
url += `?business_unit=${this.credit_card_data.business_units}&groups=${this.credit_card_data.groups.toString()}&billtype=${this.bill_type_data.bill_types}`;
url += '&billdate1=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&billdate2=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += `?business_unit=${this.credit_card_data.business_units.toString()}&groups=${this.credit_card_data.groups.toString()}&billtype=${this.bill_type_data.bill_types.toString()}`;
url += '&billdate1=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&billdate2=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
url += '&billdateOld1=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&billdateOld2=' + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += '&billdateOld1=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&billdateOld2=' + date_picker_store.end_date_cp.format(config.DATE_FORMAT) + '%2023:59:59';
}
fetch(config.HT_HOST + url)
.then((response) => response.json())
@ -116,10 +110,10 @@ class FinancialStore {
get_credit_card_bills_by_type() {
const date_picker_store = this.rootStore.date_picker_store;
let url = '/service-web/QueryData/GetCreditCardBillsByType';
url += `?business_unit=${this.credit_card_data.business_units}&groups=${this.credit_card_data.groups.toString()}`;
url += '&billdate1=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&billdate2=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += `?business_unit=${this.credit_card_data.business_units.toString()}&groups=${this.credit_card_data.groups.toString()}`;
url += '&billdate1=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&billdate2=' + date_picker_store.end_date.format(config.DATE_FORMAT) + '%2023:59:59';
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
url += '&billdateOld1=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&billdateOld2=' + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += '&billdateOld1=' + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + '&billdateOld2=' + date_picker_store.end_date_cp.format(config.DATE_FORMAT) + '%2023:59:59';
}
fetch(config.HT_HOST + url)
.then((response) => response.json())
@ -137,95 +131,27 @@ class FinancialStore {
* 服务人数页面 ----
*/
serviceModelMapper = {
'inbound': { url: '/service-Analyse2/inbound_person_num', keySort: true, dynamicsX: false },
'outbound': { url: '/service-Analyse2/outbound_person_num', keySort: true, dynamicsX: false },
'domestic': { url: '/service-Analyse2/domestic_person_num', keySort: true, dynamicsX: false },
};
servicePersonNum = { curTab: 'inbound', loading: false,
'inbound': { loading: false, dataSource: [], rawData: [], NoEmptyData: [], },
'outbound': { loading: false, dataSource: [], rawData: [], NoEmptyData: [], },
'domestic': { loading: false, dataSource: [], rawData: [], NoEmptyData: [], },
servicePersonNum = { curTab: 'inbound',
'inbound': { loading: false, dataSource: [], total: {} },
'outbound': { loading: false, dataSource: [], total: {} },
'domestic': { loading: false, dataSource: [], total: {} },
};
setCurTab(v) {
this.servicePersonNum.curTab = v;
}
resetPersonNumData = () => {
this.servicePersonNum.inbound = { loading: false, dataSource: [], rawData: [], NoEmptyData: [], };
this.servicePersonNum.outbound = { loading: false, dataSource: [], rawData: [], NoEmptyData: [], };
this.servicePersonNum.domestic = { loading: false, dataSource: [], rawData: [], NoEmptyData: [], };
};
setPersonNumTableDataSource = (notnull) => {
const mkey = this.servicePersonNum.curTab;
if (comm.isEmpty(this.servicePersonNum[mkey].dataSource)) {
return false;
}
this.servicePersonNum[mkey].dataSource = (notnull === false ? this.servicePersonNum[mkey].rawData : this.servicePersonNum[mkey].NoEmptyData);
};
handleRows = (json) => {
const mkey = this.servicePersonNum.curTab;
const sumFun = (_data, key='sum') => ['orgz', 'orgzPDays', 'hosts', 'hostsPDays'].reduce((r, skey) => ({ ...r, [skey]: _data.reduce((a, c) => a + (c[skey] || 0), 0) }), {
groupsKey: key, // `sumResult`,
groupsLabel: '合计',
});
const percentRow = (row, sumResult) => {
return ['orgz', 'orgzPDays', 'hosts', 'hostsPDays'].reduce((r, skey) => ({ ...r, [`${skey}Percent`]: sumResult[skey] ? comm.fixTo4Decimals(row[skey] / sumResult[skey]) : 0 }), row);
};
const sumResult = sumFun(json.result, 'sumResult');
const emptyRows = json.result.filter(ele => ele.groupsLabel === '' || ele.groupsLabel === '未知');
const sumEmptyResult = sumFun(emptyRows, 'sumEmptyResult');
const NotEmptyRows = json.result.filter(ele => ele.groupsLabel !== '' && ele.groupsLabel !== '未知');
const sumNotEmptyRows = sumFun(NotEmptyRows, 'sumNotEmptyRows');
const result = NotEmptyRows.map((row) => ({...row, ...percentRow(row, sumNotEmptyRows)})).sort(comm.sortBy('orgz')).reverse().map((row) => {
const newData = ['orgz', 'orgzPDays', 'hosts', 'hostsPDays'].reduce(
(r, skey) => ({ ...r, [skey]: row[`${skey}Percent`] ? row[skey] + comm.fixToInt(sumEmptyResult[skey] * row[`${skey}Percent`]) : row[skey] }),
row
);
return newData;
});
const sumAfter = sumFun(result, 'sumAfter');
const diffSum = ['orgz', 'orgzPDays', 'hosts', 'hostsPDays'].reduce((r, skey) => ({ ...r, [skey]: sumResult[skey]-sumAfter[skey]}), {});
const result0 = ['orgz', 'orgzPDays', 'hosts', 'hostsPDays'].reduce((r, skey) => ({...r, [skey]: (result?.[0]?.[skey] || 0)+(diffSum?.[skey] || 0)}), {});
result[0] = { ...result[0], ...result0 };
const totalRow = Object.keys(json.resultTotal).length > 1 ? { ...json.resultTotal, groupsKey: `total${mkey}` } : (mkey === 'domestic' ? sumResult : { groupsKey: 'empty'});
const IndividualServiceRow = {orgz: json.resultTotal.IndividualService, groupsKey: `individualService${mkey}`, groupsLabel: '单项服务人数', };
return {
sumResult,
result,
rawData: [].concat([IndividualServiceRow, totalRow], json.result.sort(comm.sortBy('orgz')).reverse()),
NoEmptyData: [].concat([IndividualServiceRow, totalRow], result),
};
};
/**
* 获取服务人数
*/
async getPersonNum(queryData) {
const mkey = this.servicePersonNum.curTab;
const url = this.serviceModelMapper[mkey].url;
this.servicePersonNum.loading = true;
this.servicePersonNum[this.servicePersonNum.curTab].loading = true;
const json = await fetchJSON(url, {...queryData, DateType: 'startDate'});
const { rawData, NoEmptyData } = this.handleRows(json);
const json = await fetchJSON('/inbound_person_num/test', queryData);
if (json.errcode === 0) {
runInAction(() => {
this.servicePersonNum.loading = false;
this.servicePersonNum[this.servicePersonNum.curTab].loading = false;
this.servicePersonNum[this.servicePersonNum.curTab].dataSource = rawData;
this.servicePersonNum[this.servicePersonNum.curTab].rawData = rawData;
this.servicePersonNum[this.servicePersonNum.curTab].NoEmptyData = NoEmptyData;
this.servicePersonNum[this.servicePersonNum.curTab].dataSource = [].concat([json.resultTotal], json.result);
this.servicePersonNum[this.servicePersonNum.curTab].total = json.resultTotal;
});
}
return json;

@ -1,202 +0,0 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique, cloneDeep, omit, fixTo2Decimals } from '../utils/commons';
import { groupsMappedByCode, dataFieldAlias } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import moment from 'moment';
const fetchHotelData = async (param) => {
const defaultParam = {
DEI_SN: '',
City: '',
OrderState: '',
BookingType: '-1',
RecommendedLevel: '-1',
Star: '-1',
ArriveDateCheck: '0',
ArriveDateStart: '',
ArriveDateEnd: '',
ConfirmDateCheck: '0',
ConfirmDateStart: '',
ConfirmDateEnd: '',
Compare: '0',
CompareDateStart: '',
CompareDateEnd: '',
Area: '-1',
};
const json = await fetchJSON('/service-Analyse2/HotelReservation', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result || [] : [];
};
const fetchCruiseData = async (param) => {
const defaultParam = {
DEI_SN: '',
OrderState: '', // 0: 不成行 1: 成行
ArriveDateStart: '',
ArriveDateEnd: '',
Compare: '',
CompareDateStart: '',
CompareDateEnd: '',
BookingType: '', // 0: 非单订三峡1: 单订三峡
ProductName: '',
Direction: '', // 1: 上水 2: 下水
VEI_SN: '-1', // 只要列出常用游船供应商选择
RoomNumStart: '0',
RoomNumEnd: '',
PersonNumStart: '0',
PersonNumEnd: '',
Country: '-1',
};
const json = await fetchJSON('/service-Analyse2/CruiseReservation', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result || [] : [];
};
const paramKeyMapped = {
'DateType': [
{ key: 'ArriveDateCheck', transform: (val) => (val === 'startDate' ? '1' : '0') },
{ key: 'ConfirmDateCheck', transform: (val) => (val === 'confirmDate' ? '1' : '0') },
// { key: 'ApplyDateCheck', transform: (val) => (val === 'applyDate' ? '1' : '0') },
],
'DepartmentList': { key: 'DEI_SN' },
'orderStatus': { key: 'OrderState' },
'Date1': [{ key: 'ArriveDateStart' }, { key: 'ConfirmDateStart' }],
'Date2': [{ key: 'ArriveDateEnd' }, { key: 'ConfirmDateEnd' }],
'DateDiff1': { key: 'CompareDateStart' },
'DateDiff2': { key: 'CompareDateEnd' },
'keyword': { key: 'ProductName' },
'agency': { key: 'VEI_SN' },
'cruiseDirection': { key: 'Direction' },
'cruiseBookType': { key: 'BookingType' },
'hotelStar': { key: 'Star' },
'hotelRecommandRate': { key: 'RecommendedLevel' },
'hotelBookType': { key: 'BookingType' },
'country': { key: 'Country' },
'countryArea': { key: 'Area', transform: (val) => (val === 'china' ? '1' : val === 'foreign' ? '0' : '-1') },
};
class HotelCruise {
constructor(appStore) {
this.appStore = appStore;
makeAutoObservable(this);
}
async getCruiseData(param = {}) {
this.cruise.loading = true;
this.cruise.dataSource = [];
const _queryParam = objectMapper(param, paramKeyMapped);
const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, Object.keys(paramKeyMapped));
queryParam.Compare = isEmpty(param.DateDiff1) ? '' : '1';
const res = await fetchCruiseData(queryParam);
const resCP =
queryParam.Compare === ''
? res
: (res || []).map((ele) => ({
...ele,
// 计算 增长率 = (当前值 - 上次值) / 上次值 * 100
TotalNumPercent: ele.CPTotalNum ? fixTo2Decimals(((ele.TotalNum - ele.CPTotalNum) / ele.CPTotalNum) * 100) : '-',
TotalPersonNumPercent: ele.CPTotalPersonNum ? fixTo2Decimals(((ele.TotalPersonNum - ele.CPTotalPersonNum) / ele.CPTotalPersonNum) * 100) : '-',
TotalProfitPercent: ele.CPTotalProfit ? fixTo2Decimals(((ele.TotalProfit - ele.CPTotalProfit) / ele.CPTotalProfit) * 100) : '-',
}));
const summaryRow = ['TotalNum', 'TotalPersonNum', 'TotalProfit', 'CPTotalNum', 'CPTotalPersonNum', 'CPTotalProfit'].reduce(
(r, skey) => ({
...r,
[skey]: resCP.reduce((a, c) => a + c[skey], 0),
}),
{ ProductName: '合计' }
);
const summaryDelta = ['TotalNum', 'TotalPersonNum', 'TotalProfit'].reduce(
(r, skey) => ({
...r,
[`${skey}Percent`]: queryParam.Compare === '' ? null : fixTo2Decimals(((summaryRow[skey] - summaryRow[`CP${skey}`]) / summaryRow[`CP${skey}`]) * 100),
}),
{}
);
runInAction(() => {
this.cruise.loading = false;
this.cruise.dataSource = resCP;
this.cruise.summaryRow = { ...summaryRow, ...summaryDelta };
});
return this.cruise;
}
async getHotelData(param = {}) {
this.hotel.loading = true;
this.hotel.dataSource = [];
const _queryParam = objectMapper(param, paramKeyMapped);
const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, Object.keys(paramKeyMapped));
queryParam.Compare = isEmpty(param.DateDiff1) ? '0' : '1';
const _res = await fetchHotelData(queryParam);
const res = (_res || []).map((ele) => ({ ...ele, RecommendRate_100: fixTo2Decimals(ele.RecommendRate * 100) + '%' }));
const resCP =
queryParam.Compare === '0'
? res
: (res || []).map((ele) => ({
...ele,
TotalNumPercent: ele.CPTotalNum ? fixTo2Decimals(((ele.TotalNum - ele.CPTotalNum) / ele.CPTotalNum) * 100) : '-',
RecomendNumPercent: ele.CPRecomendNum ? fixTo2Decimals(((ele.RecomendNum - ele.CPRecomendNum) / ele.CPRecomendNum) * 100) : '-',
RecommendRateDelta: fixTo2Decimals((ele.RecommendRate - (ele.CPRecommendRate || 0)) * 100),
CPRecommendRate_100: fixTo2Decimals(ele.CPRecommendRate * 100) + '%',
}));
const summaryRow = ['TotalNum', 'RecomendNum', ].reduce(
(r, skey) => ({
...r,
[skey]: resCP.reduce((a, c) => a + c[skey], 0),
[`CP${skey}`]: resCP.reduce((a, c) => a + c[`CP${skey}`], 0),
}),
{ CityName: '合计' }
);
summaryRow.RecommendRate = fixTo2Decimals(summaryRow.RecomendNum/summaryRow.TotalNum);
summaryRow.RecommendRate_100 = fixTo2Decimals(summaryRow.RecommendRate * 100) + '%';
summaryRow.CPRecommendRate = fixTo2Decimals(summaryRow.CPRecomendNum/summaryRow.CPTotalNum);
summaryRow.CPRecommendRate_100 = fixTo2Decimals(summaryRow.CPRecommendRate * 100) + '%';
const summaryDelta = ['TotalNum', 'RecomendNum', ].reduce(
(r, skey) => ({
...r,
[`${skey}Percent`]: queryParam.Compare === '0' ? null : fixTo2Decimals(((summaryRow[skey] - summaryRow[`CP${skey}`]) / summaryRow[`CP${skey}`]) * 100),
}),
{}
);
summaryDelta.RecommendRateDelta = queryParam.Compare === '0' ? undefined : fixTo2Decimals((summaryRow.RecommendRate - (summaryRow.CPRecommendRate || 0)) * 100);
const resfilter = resCP.map(row => ({text: row.CityName, value: row.CityName})); // .sort((a, b) => a.text.localeCompare(b.text));
runInAction(() => {
this.hotel.loading = false;
this.hotel.dataSource = resCP;
this.hotel.filters = resfilter;
this.hotel.summaryRow = { ...summaryRow, ...summaryDelta };
});
}
searchValues = {
date: moment(),
DateType: { key: 'confirmDate', label: '确认日期' },
WebCode: { key: '', label: '所有来源' },
// IncludeTickets: { key: '1', label: '含门票'},
DepartmentList: [{ key: '', label: '所有小组' }],
operator: '-1',
opisn: '-1',
};
searchValuesToSub = {};
setSearchValues(obj, values) {
this.searchValues = { ...this.searchValues, ...values };
this.searchValuesToSub = obj;
}
cruise = { loading: false, dataSource: [], summaryRow: {}, filters: [] };
hotel = { loading: false, dataSource: [], summaryRow: {}, filters: [] };
resetData = () => {
this.results.loading = false;
for (const key of Object.keys(this.results)) {
if (key !== 'loading') {
this.results[key] = [];
}
}
for (const key of Object.keys(this.hotel)) {
if (key !== 'loading') {
this.hotel[key] = [];
}
}
};
}
export default HotelCruise;

@ -14,11 +14,6 @@ import TradeStore from "./Trade";
import KPI from "./KPI";
import DictData from "./DictData";
import Distribution from "./Distribution";
import DataPivot from './DataPivot';
import MeetingData from './MeetingData2024';
import MeetingData2025 from './MeetingData2025';
import SalesCRMData from './SalesCRMData';
import HotelCruise from './HotelCruise';
class Index {
constructor() {
this.dashboard_store = new DashboardStore(this);
@ -36,11 +31,6 @@ class Index {
this.KPIStore = new KPI(this);
this.DictDataStore = new DictData(this);
this.DistributionStore = new Distribution(this);
this.DataPivotStore = new DataPivot(this);
this.MeetingDataStore = new MeetingData(this);
this.MeetingData2025Store = new MeetingData2025(this);
this.SalesCRMDataStore = new SalesCRMData(this);
this.HotelCruiseStore = new HotelCruise(this);
makeAutoObservable(this);
}

@ -1,355 +0,0 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request';
import { objectMapper, pick, price_to_number, } from '../utils/commons';
import { pivotBy } from '../libs/ht';
import moment from "moment";
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT } from '../config';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
/**
*
*/
const getOrderCountByType = async (param) => {
const paramBody = objectMapper(param, {
WebCode: 'WebCode',
OrderType: 'OrderType',
IncludeTickets: 'IncludeTickets',
DateType: 'DateType',
DepartmentList: 'DepartmentList', // { key: 'DepartmentList', transform: (v) => v.join(',') },
Date1: 'COLI_ApplyDate1',
Date2: 'COLI_ApplyDate2',
});
const url = '/service-web/QueryData/GetOrderCountByType';
const json = await fetchJSON(url, paramBody);
return json.errcode === 0 ? json : {};
};
const getAgentGroupInfoALL = async (param) => {
const paramBody = objectMapper(param, {
DateType: 'DateType',
DepartmentList: 'DepList', // { key: 'DepartmentList', transform: (v) => v.join(',') },
// Date1: 'OldDate1',
// Date2: 'OldDate2',
});
const url = '/service-web/QueryData/GetAgentGroupInfoALL';
const json = await fetchJSON(url, paramBody);
return json.errcode === 0 ? json : {};
};
const getDepartmentOrderMLByType = async (param) => {
const paramBody = objectMapper(param, {
DateType: 'DateType',
DepartmentList: 'DepartmentList', // { key: 'DepartmentList', transform: (v) => v.join(',') },
OrderType: 'OrderType', // 总览, 产品类型
});
const url = '/service-web/QueryData/GetDepartmentOrderMLByType';
const json = await fetchJSON(url, paramBody);
const { result1 } = json.errcode === 0 ? json : { result1: [] };
const total1 = ['COLI_CJCount', 'COLI_ML2',].reduce(
(r, col) => ({
...r,
[col]: result1.reduce((rr, row) => rr + row[col], 0),
}),
{}
);
return { total1, result1 };
};
const GHproductTypeListSetting = {
ja: ['日本', '东亚跨国'],
se: ['东南亚跨国', '泰国', '越南', '印度尼西亚', '水灯节', '柬埔寨', '老挝'],
in: ['印度', '印度次大陆跨国', '尼泊尔', '不丹', '斯里兰卡'],
};
const GHCountryListSetting = {
ja: ['日本', ],
se: ['泰国', '越南', '印度尼西亚', '水灯节', '柬埔寨', '老挝', '新加坡', '马来西亚', '菲律宾'],
in: ['印度', '印度次大陆跨国', '尼泊尔', '不丹', '斯里兰卡'],
};
const rowItem = (filterData) => {
const { data: dataByLineClass, summaryMix: summaryByLineClass } = pivotBy(filterData, [['COLI_LineClass'], [], []]);
const LineClass_Origin = dataByLineClass.filter((ele) => ele.COLI_LineClass.toLocaleLowerCase().indexOf('网前自然订单') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const LineClass_PPC = dataByLineClass.filter((ele) => ele.COLI_LineClass.toLocaleLowerCase().indexOf('ppc') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const { data: dataByWebCode, summaryMix: summaryByWebCode } = pivotBy(filterData, [['WebCode'], [], []]);
const toB = dataByWebCode.filter((ele) => ele.WebCode.toLocaleLowerCase().indexOf('to b') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const external = dataByWebCode.filter((ele) => ele.WebCode.toLocaleLowerCase().indexOf("站外渠道") !== -1).reduce((r, c) => r + c.SumOrder, 0);
const filterIsOldC = filterData.filter((ele) => ele.WebCode.toLocaleLowerCase().indexOf('to b') === -1);
const { data: dataByIsOld, summaryMix: summaryByIsOld } = pivotBy(filterIsOldC, [['IsOld', 'isCusCommend'], [], []]);
const isOld1 = dataByIsOld.filter((ele) => ele.rowLabel.indexOf('1') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const total = LineClass_Origin + LineClass_PPC + toB + isOld1 + external;
return { LineClass_Origin, LineClass_PPC, toB, external, isOld1, total };
};
// 日本+: 日本+东亚跨国
const dataJA = (rawData, yearData) => {
const productTypeList = GHproductTypeListSetting.ja;
const filterData = rawData.filter((ele) => productTypeList.some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1));
const filterDataYear = yearData.filter((ele) => productTypeList.some((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) !== -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
// 东南亚+: 东南亚跨国+泰国+越南+印尼+水灯节线路
const dataSE = (rawData, yearData) => {
const productTypeList = GHproductTypeListSetting.se;
const filterData = rawData.filter((ele) => productTypeList.some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1));
const filterDataYear = yearData.filter((ele) => productTypeList.some((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) !== -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
// 印度+: 印度+次大陆跨国+尼泊尔+不丹+斯里兰卡
const dataIN = (rawData, yearData) => {
const productTypeList = GHproductTypeListSetting.in;
const exceptProduct = ['印度尼西亚'];
const filterData = rawData
.filter((ele) => productTypeList.some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1))
.filter((ele) => exceptProduct.every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1));
const filterDataYear = yearData
.filter((ele) => productTypeList.some((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) !== -1))
.filter((ele) => exceptProduct.every((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) === -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
// 其他GH
const dataGHOther = (rawData, yearData) => {
const exceptProduct = Object.values(GHproductTypeListSetting).reduce((r, c) => r.concat(c), []);
const filterData = rawData.filter((ele) => exceptProduct.every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1));
const filterDataYear = yearData.filter((ele) => exceptProduct.every((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) === -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
const dataSales = (tKey, rawData, yearData, yearData2) => {
const targetList = GHCountryListSetting[tKey];
const tIndex = Object.keys(GHCountryListSetting).indexOf(tKey);
const exceptTargetList = Object.keys(GHCountryListSetting).reduce((r, c, i) => r.concat(i < tIndex ? GHCountryListSetting[c] : []), []);
// console.log(tIndex, tKey, 'exceptTargetList', exceptTargetList, 'targetList', targetList);
const filterRaw1 = rawData.filter((ele) => exceptTargetList.every((item) => !ele.destinationCountry.includes(item)));
// console.log(tKey, 'filterRaw1', filterRaw1);
const filterDataC = filterRaw1.filter((ele) => targetList.some((item) => ele.destinationCountry.includes(item)));
const filterDataT = tKey === 'se' ? filterRaw1.filter((ele) => ['泰国水灯节'].some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1)) : [];
const filterData = filterDataC.concat(filterDataT);
const CJCount = filterData.length; // filterData.reduce((r, c) => r + c.CJCount, 0);
const YJLY = filterData.reduce((r, c) => r + price_to_number(c.ML), 0);
const filterRaw2 = yearData.filter((ele) => exceptTargetList.every((item) => !ele.destinationCountry.includes(item)));
const filterDataYearC = filterRaw2.filter((ele) => targetList.some((item) => ele.destinationCountry.includes(item)));
const filterDataYearT = tKey === 'se' ? filterRaw2.filter((ele) => ['泰国水灯节'].some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1)) : [];
const filterDataYear = filterDataYearC.concat(filterDataYearT);
const rowYearData = { CJCount: filterDataYear.length, YJLY: filterDataYear.reduce((r, c) => r + price_to_number(c.ML), 0) };
// console.log(tKey, filterDataYear.map(ee => ee.destinationCountry), filterDataYear.map(ee => ee.productType), filterDataYear);
const filterDataYearRaw2 = yearData2.filter((ele) => exceptTargetList.every((item) => !ele.destinationCountry.includes(item)));
const filterDataYear2C = filterDataYearRaw2.filter((ele) => targetList.some((item) => ele.destinationCountry.includes(item)));
const filterDataYear2T = tKey === 'se' ? filterDataYearRaw2.filter((ele) => ['泰国水灯节'].some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1)) : [];
const filterDataYear2 = filterDataYear2C.concat(filterDataYear2T);
const rowYearData2 = { CJCount: filterDataYear2.length, YJLY: filterDataYear2.reduce((r, c) => r + price_to_number(c.ML), 0) };
const rowYear = {
YJLY: price_to_number(rowYearData.YJLY), CJCount: rowYearData.CJCount, GroupCount: rowYearData.CJCount,
YJLY2: price_to_number(rowYearData2.YJLY), CJCount2: rowYearData2.CJCount, GroupCount2: rowYearData2.CJCount,
};
const cols = ['YJLY', 'CJCount'].reduce((r, key) => ({ ...r, [key]: filterData.reduce((a, c) => a + price_to_number(c[key]), 0) }), {});
// console.log(tKey, filterData, filterDataYear, filterDataYear2);
return { ...cols, GroupCount:CJCount, CJCount, YJLY, rowYear, rawData: filterData, rawYearData: filterDataYear, rawYearData2: filterDataYear2 };
};
const dataSalesGHOther = (rawData, yearData, yearData2) => {
const exceptContry = Object.values(GHCountryListSetting).reduce((r, c) => r.concat(c), []);
// console.log('exceptContry', exceptContry);
// console.log('OOoo rawData', rawData.map(e => e.destinationCountry));
const filterData = rawData
.filter((ele) => ['泰国水灯节'].every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1))
.filter((ele) => exceptContry.every((item) => !ele.destinationCountry.includes(item)));
// console.log('OOoo', filterData.map(e => e.destinationCountry), filterData.map(e => e.productType));
const CJCount = filterData.length; // filterData.reduce((r, c) => r + c.CJCount, 0);
const YJLY = filterData.reduce((r, c) => r + price_to_number(c.ML), 0);
const filterDataYear = yearData
.filter((ele) => ['泰国水灯节'].every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1))
.filter((ele) => exceptContry.every((item) => !ele.destinationCountry.includes(item)));
const rowYearData = { CJCount: filterDataYear.length, YJLY: filterDataYear.reduce((r, c) => r + price_to_number(c.ML), 0) };
// console.log('OOoo year', filterDataYear.map(e => e.destinationCountry), filterDataYear.map(e => e.productType));
const filterDataYear2 = yearData2
.filter((ele) => ['泰国水灯节'].every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1))
.filter((ele) => exceptContry.every((item) => !ele.destinationCountry.includes(item)));
const rowYearData2 = { CJCount: filterDataYear2.length, YJLY: filterDataYear2.reduce((r, c) => r + price_to_number(c.ML), 0) };
// console.log('Oo', filterDataYear2.map(e => e.destinationCountry), filterDataYear2);
// console.log('Oo row', rowYearData2);
const rowYear = {
YJLY: price_to_number(rowYearData.YJLY), CJCount: rowYearData.CJCount, GroupCount: rowYearData.CJCount,
YJLY2: price_to_number(rowYearData2.YJLY), CJCount2: rowYearData2.CJCount, GroupCount2: rowYearData2.CJCount,
};
return { GroupCount:CJCount, CJCount, YJLY, rowYear, rawData: filterData, rawYearData: filterDataYear, rawYearData2: filterDataYear2 };
};
class MeetingData {
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
}
searchValues = {
DateType: { key: 'applyDate', value: 'applyDate', label: '提交日期' },
};
setSearchValues(body) {
this.searchValues = body;
}
GHTableData = [];
GHTableLoading = false;
/**
* 获取市场订单数据 ---------------------------------------------------------------------------------------------------
*/
dataGHOrder = async (param) => {
// console.log('dataGH', param);
this.GHTableLoading = true;
const defaultParam = { DateType: 'applyDate' };
// 本周
const CHData = await getDetailData({ ...param, ...defaultParam, 'DepartmentList': '1', 'WebCode': 'All' });
const exceptCHData = await getDetailData({ ...param, ...defaultParam, 'DepartmentList': '28,33', 'WebCode': 'All' });
const yearStart = moment().startOf("year").format(DATE_FORMAT);
/** 截至今年 - 行 */
const { ordercountTotal1: CHDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1', 'WebCode': 'All', OrderType: 'LineClass' });
const { ordercount1: exceptCHDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '28,33', 'WebCode': 'All', OrderType: 'Product' });
/** 截至今年 - 列 */
const { ordercount1: ColLineClassDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'All', OrderType: 'LineClass' });
const { ordercountTotal1: ColToBDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'GHTOBHW,GHTOBZG', OrderType: 'LineClass' });
const { ordercountTotal1: ColExternalDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'ZWQD', OrderType: 'LineClass' });
// 老客户
const yearDetail = await getDetailData({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'All' });
const { isOld1: isOld1Year } = rowItem(yearDetail);
const colYearRow = {
LineClass_Origin: ColLineClassDataYear.filter((ele) => ele.OrderType.toLocaleLowerCase().indexOf('网前自然订单') !== -1).reduce((r, c) => r + c.OrderCount, 0),
LineClass_PPC: ColLineClassDataYear.filter((ele) => ele.OrderType.toLocaleLowerCase().indexOf('ppc') !== -1).reduce((r, c) => r + c.OrderCount, 0),
toB: ColToBDataYear.OrderCount,
isOld1: isOld1Year,
external: ColExternalDataYear.OrderCount,
};
const rows = [
{ key: 'ch', label: '中国', ...rowItem(CHData), rowYear: CHDataYear.OrderCount },
{ key: 'ja', label: '日本+', ...dataJA(exceptCHData, exceptCHDataYear) },
{ key: 'se', label: '东南亚+', ...dataSE(exceptCHData, exceptCHDataYear) },
{ key: 'in', label: '印度+', ...dataIN(exceptCHData, exceptCHDataYear) },
{ key: 'other', label: '其他GH', ...dataGHOther(exceptCHData, exceptCHDataYear) },
];
const columnsSum = ['LineClass_Origin', 'LineClass_PPC', 'toB', 'external', 'isOld1', 'total', 'rowYear'].reduce(
(r, col) => ({
...r,
[col]: rows.reduce((rr, row) => rr + row[col], 0),
}),
{}
);
rows.push({ key: 'columnSum', label: '合计', ...columnsSum });
rows.push({ key: 'colYearRow', label: '截至', ...colYearRow });
runInAction(() => {
this.GHTableData = rows;
this.GHTableLoading = false;
});
};
GHSalesTableData = [];
GHSalesLoading = false;
/**
* 获取GH销售数据 ---------------------------------------------------------------------------------------------------
*/
dataGHSales = async (param) => {
this.GHSalesLoading = true;
const salesParam = { ...param, DateType: 'confirmDate', 'WebCode': 'All', };
// console.log(param);
const { total1: CHSalesDataWeek } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '1', OrderType:'ALL'});
const exceptCHSalesDataWeek = await getDetailData({ ...salesParam, 'DepartmentList': '28,33' });
const GHDataWeekConfirm = exceptCHSalesDataWeek.filter((ele) => Number(ele.orderState) === 1); // 成交的
const yearStart = moment().startOf("year").format(DATE_FORMAT);
const yearEnd = moment().endOf("year").format(SMALL_DATETIME_FORMAT);
/** 截至今年 - 成交 */
const { total1: CHDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1', OrderType:'ALL' });
const GHDataYear = await getDetailData({ ...salesParam, Date1: yearStart, 'DepartmentList': '28,33' });
const GHDataYearConfirm = GHDataYear.filter((ele) => Number(ele.orderState) === 1); // 成交的
/** 截至今年 - 走团 */
const { total1: CHStartDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart,Date2:yearEnd,DepartmentList: '1', OrderType:'ALL',DateType: 'startDate' });
const GHStartDataYear0 = await getDetailData({ ...salesParam, DateType: 'startDate', 'DepartmentList': '28,33', Date1: yearStart,Date2:yearEnd, });
const GHStartDataYear = GHStartDataYear0.filter((ele) => Number(ele.orderState) === 1); // 成交的
const rows = [
{
key: 'ch',
label: '中国',
YJLY: price_to_number(CHSalesDataWeek.COLI_ML2),
CJCount: (CHSalesDataWeek.COLI_CJCount),
rowYear: { YJLY: price_to_number(CHDataYear.COLI_ML2), CJCount: CHDataYear.COLI_CJCount, YJLY2: price_to_number(CHStartDataYear.COLI_ML2) },
},
{ key: 'ja', label: '日本+', ...dataSales('ja', GHDataWeekConfirm, GHDataYearConfirm, GHStartDataYear) },
{ key: 'se', label: '东南亚+', ...dataSales('se', GHDataWeekConfirm, GHDataYearConfirm, GHStartDataYear) },
{ key: 'in', label: '印度+', ...dataSales('in', GHDataWeekConfirm, GHDataYearConfirm, GHStartDataYear) },
{ key: 'other', label: '其他GH', ...dataSalesGHOther(GHDataWeekConfirm, GHDataYearConfirm, GHStartDataYear) },
];
const columnsSum = ['CJCount', 'YJLY'].reduce((r, col) => ({ ...r, [col]: rows.reduce((rr, row) => rr + row[col], 0) }), {});
const allYearData = rows.map(row => row.rowYear);
const rowYear = ['CJCount', 'YJLY', 'CJCount2', 'YJLY2', ].reduce((r, col) => ({ ...r, [col]: allYearData.reduce((rr, row) => rr + (row[col] || 0), 0) }), {});
rows.push({ key: 'columnSum', label: '合计', ...columnsSum, rowYear });
// console.log(rows, allYearData, rowYear);
runInAction(() => {
this.GHSalesTableData = rows;
this.GHSalesLoading = false;
});
};
GHServiceTableData = [];
GHServiceLoading = false;
/**
* 获取GH服务数据 ---------------------------------------------------------------------------------------------------
*/
dataGHService = async (param) => {
this.GHServiceLoading = true;
const serviceParam = { ...param, DateType: 'startDate', 'WebCode': 'All' };
// 走团数
const { ordercountTotal1: { OrderCount: GroupCount } } = await getOrderCountByType({ ...serviceParam, 'DepartmentList': '1', OrderType: 'Form' });
const exceptCHDataWeek = await getDetailData({ ...serviceParam, 'DepartmentList': '28,33' });
// 走团数 - 年
const yearStart = moment().startOf("year").format(DATE_FORMAT);
const { ordercountTotal1: { OrderCount: GroupCountYear } } = await getOrderCountByType({ ...serviceParam, 'DepartmentList': '1', OrderType: 'Form', Date1: yearStart, });
const exceptCHDataYear = await getDetailData({ ...serviceParam, Date1: yearStart, 'DepartmentList': '28,33' });
// 好评数
const { total1: { GoodCount } } = await getAgentGroupInfoALL({ ...serviceParam, 'DepartmentList': '1', });
const { total1: { GoodCount: GoodCountYear } } = await getAgentGroupInfoALL({ ...serviceParam, Date1: yearStart, 'DepartmentList': '1', });
const { total1: { GoodCount: GHGoodCountWeek } } = await getAgentGroupInfoALL({ ...serviceParam, 'DepartmentList': '28,33', });
const { total1: { GoodCount: GHGoodCountYear } } = await getAgentGroupInfoALL({ ...serviceParam, Date1: yearStart, 'DepartmentList': '28,33', });
const rows = [
{ key: 'ch', label: '中国', ...{GoodCount, GroupCount}, rowYear: { GroupCount: GroupCountYear, GoodCount: GoodCountYear } },
{ key: 'ja', label: '日本+', ...dataSales('ja', exceptCHDataWeek, exceptCHDataYear, []) },
{ key: 'se', label: '东南亚+', ...dataSales('se', exceptCHDataWeek, exceptCHDataYear, []) },
{ key: 'in', label: '印度+', ...dataSales('in', exceptCHDataWeek, exceptCHDataYear, []) },
{ key: 'other', label: '其他GH', ...dataSalesGHOther(exceptCHDataWeek, exceptCHDataYear, []) },
];
const GHRowWeek = { GoodCount: GHGoodCountWeek, GroupCount: 0 };
const columnsSum = ['GoodCount', 'GroupCount'].reduce((r, col) => ({ ...r, [col]: [...rows, GHRowWeek].reduce((rr, row) => rr + (row[col] || 0), 0) }), {});
const allYearData = rows.map((row) => row.rowYear).concat([{ GoodCount: GHGoodCountYear, GroupCount: 0 }]);
// console.log(allYearData);
const rowYear = ['GoodCount', 'GroupCount', ].reduce((r, col) => ({ ...r, [col]: allYearData.reduce((rr, row) => rr + (row[col] || 0), 0) }), {});
rows.push({ key: 'columnSum', label: '合计', ...columnsSum, rowYear });
// console.log(rows);
runInAction(() => {
this.GHServiceTableData = rows;
this.GHServiceLoading = false;
});
};
}
export default MeetingData;

@ -1,479 +0,0 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request';
import { objectMapper, pick, price_to_number, } from '../utils/commons';
import { pivotBy } from './../libs/ht';
import moment from "moment";
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT } from '../config';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
/**
*
*/
const getOrderCountByType = async (param) => {
const paramBody = objectMapper(param, {
WebCode: 'WebCode',
OrderType: 'OrderType',
IncludeTickets: 'IncludeTickets',
DateType: 'DateType',
DepartmentList: 'DepartmentList', // { key: 'DepartmentList', transform: (v) => v.join(',') },
Date1: 'COLI_ApplyDate1',
Date2: 'COLI_ApplyDate2',
});
const url = '/service-web/QueryData/GetOrderCountByType';
const json = await fetchJSON(url, paramBody);
return json.errcode === 0 ? json : {};
};
const getAgentGroupInfoALL = async (param) => {
const paramBody = objectMapper(param, {
DateType: 'DateType',
DepartmentList: 'DepList', // { key: 'DepartmentList', transform: (v) => v.join(',') },
Date1: 'OldDate1',
Date2: 'OldDate2',
});
const url = '/service-web/QueryData/GetAgentGroupInfoALL';
const json = await fetchJSON(url, paramBody);
return json.errcode === 0 ? json : {};
};
const getDepartmentOrderMLByType = async (param) => {
const paramBody = objectMapper(param, {
DateType: 'DateType',
DepartmentList: 'DepartmentList', // { key: 'DepartmentList', transform: (v) => v.join(',') },
OrderType: 'OrderType', // 总览, 产品类型
});
const url = '/service-web/QueryData/GetDepartmentOrderMLByType';
const json = await fetchJSON(url, paramBody);
const { result1 } = json.errcode === 0 ? json : { result1: [] };
const total1 = ['COLI_CJCount', 'COLI_ML2',].reduce(
(r, col) => ({
...r,
[col]: result1.reduce((rr, row) => rr + row[col], 0),
}),
{}
);
return { total1, result1 };
};
const GHproductTypeListSetting = {
ja: ['日本', '东亚跨国'],
se: ['东南亚跨国', '泰国', '越南', '印度尼西亚', '水灯节', '柬埔寨', '老挝'],
in: ['印度', '印度次大陆跨国', '尼泊尔', '不丹', '斯里兰卡'],
};
const GHCountryListSetting = {
ja: ['日本', ],
se: ['泰国', '越南', '印度尼西亚', '水灯节', '柬埔寨', '老挝', '新加坡', '马来西亚', '菲律宾'],
in: ['印度', '印度次大陆跨国', '尼泊尔', '不丹', '斯里兰卡'],
};
const rowItem = (filterData) => {
const { data: dataByLineClass, summaryMix: summaryByLineClass } = pivotBy(filterData, [['COLI_LineClass'], [], []]);
const LineClass_Origin = dataByLineClass.filter((ele) => ele.COLI_LineClass.toLocaleLowerCase().indexOf('网前自然订单') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const LineClass_PPC = dataByLineClass.filter((ele) => ele.COLI_LineClass.toLocaleLowerCase().indexOf('ppc') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const { data: dataByWebCode, summaryMix: summaryByWebCode } = pivotBy(filterData, [['WebCode'], [], []]);
const toB = dataByWebCode.filter((ele) => ele.WebCode.toLocaleLowerCase().indexOf('to b') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const external = dataByWebCode.filter((ele) => ele.WebCode.toLocaleLowerCase().indexOf("站外渠道") !== -1).reduce((r, c) => r + c.SumOrder, 0);
const filterIsOldC = filterData.filter((ele) => ele.WebCode.toLocaleLowerCase().indexOf('to b') === -1);
const { data: dataByIsOld, summaryMix: summaryByIsOld } = pivotBy(filterIsOldC, [['IsOld', 'isCusCommend'], [], []]);
const isOld1 = dataByIsOld.filter((ele) => ele.rowLabel.indexOf('1') !== -1).reduce((r, c) => r + c.SumOrder, 0);
const total = LineClass_Origin + LineClass_PPC + toB + isOld1 + external;
return { LineClass_Origin, LineClass_PPC, toB, external, isOld1, total };
};
// 日本+: 日本+东亚跨国
const dataJA = (rawData, yearData) => {
const productTypeList = GHproductTypeListSetting.ja;
const filterData = rawData.filter((ele) => productTypeList.some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1));
const filterDataYear = yearData.filter((ele) => productTypeList.some((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) !== -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
// 东南亚+: 东南亚跨国+泰国+越南+印尼+水灯节线路
const dataSE = (rawData, yearData) => {
const productTypeList = GHproductTypeListSetting.se;
const filterData = rawData.filter((ele) => productTypeList.some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1));
const filterDataYear = yearData.filter((ele) => productTypeList.some((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) !== -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
// 印度+: 印度+次大陆跨国+尼泊尔+不丹+斯里兰卡
const dataIN = (rawData, yearData) => {
const productTypeList = GHproductTypeListSetting.in;
const exceptProduct = ['印度尼西亚'];
const filterData = rawData
.filter((ele) => productTypeList.some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1))
.filter((ele) => exceptProduct.every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1));
const filterDataYear = yearData
.filter((ele) => productTypeList.some((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) !== -1))
.filter((ele) => exceptProduct.every((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) === -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
// 其他GH
const dataGHOther = (rawData, yearData) => {
const exceptProduct = Object.values(GHproductTypeListSetting).reduce((r, c) => r.concat(c), []);
const filterData = rawData.filter((ele) => exceptProduct.every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1));
const filterDataYear = yearData.filter((ele) => exceptProduct.every((item) => ele.OrderType.toLocaleLowerCase().indexOf(item) === -1));
const rowYear = filterDataYear.reduce((r, c) => r + c.OrderCount, 0);
return { ...rowItem(filterData), rowYear };
};
const dataSales = (tKey, rawData, yearData, yearData2) => {
const targetList = GHCountryListSetting[tKey];
const tIndex = Object.keys(GHCountryListSetting).indexOf(tKey);
const exceptTargetList = Object.keys(GHCountryListSetting).reduce((r, c, i) => r.concat(i < tIndex ? GHCountryListSetting[c] : []), []);
// console.log(tIndex, tKey, 'exceptTargetList', exceptTargetList, 'targetList', targetList);
const filterRaw1 = rawData.filter((ele) => exceptTargetList.every((item) => !ele.destinationCountry.includes(item)));
// console.log(tKey, 'filterRaw1', filterRaw1);
const filterDataC = filterRaw1.filter((ele) => targetList.some((item) => ele.destinationCountry.includes(item)));
const filterDataT = tKey === 'se' ? filterRaw1.filter((ele) => ['泰国水灯节'].some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1)) : [];
const filterData = filterDataC.concat(filterDataT);
const CJCount = filterData.length; // filterData.reduce((r, c) => r + c.CJCount, 0);
const YJLY = filterData.reduce((r, c) => r + price_to_number(c.ML), 0);
const filterRaw2 = yearData.filter((ele) => exceptTargetList.every((item) => !ele.destinationCountry.includes(item)));
const filterDataYearC = filterRaw2.filter((ele) => targetList.some((item) => ele.destinationCountry.includes(item)));
const filterDataYearT = tKey === 'se' ? filterRaw2.filter((ele) => ['泰国水灯节'].some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1)) : [];
const filterDataYear = filterDataYearC.concat(filterDataYearT);
const rowYearData = { CJCount: filterDataYear.length, YJLY: filterDataYear.reduce((r, c) => r + price_to_number(c.ML), 0) };
// console.log(tKey, filterDataYear.map(ee => ee.destinationCountry), filterDataYear.map(ee => ee.productType), filterDataYear);
const filterDataYearRaw2 = yearData2.filter((ele) => exceptTargetList.every((item) => !ele.destinationCountry.includes(item)));
const filterDataYear2C = filterDataYearRaw2.filter((ele) => targetList.some((item) => ele.destinationCountry.includes(item)));
const filterDataYear2T = tKey === 'se' ? filterDataYearRaw2.filter((ele) => ['泰国水灯节'].some((item) => ele.productType.toLocaleLowerCase().indexOf(item) !== -1)) : [];
const filterDataYear2 = filterDataYear2C.concat(filterDataYear2T);
const rowYearData2 = { CJCount: filterDataYear2.length, YJLY: filterDataYear2.reduce((r, c) => r + price_to_number(c.ML), 0) };
const rowYear = {
YJLY: price_to_number(rowYearData.YJLY), CJCount: rowYearData.CJCount, GroupCount: rowYearData.CJCount,
YJLY2: price_to_number(rowYearData2.YJLY), CJCount2: rowYearData2.CJCount, GroupCount2: rowYearData2.CJCount,
};
const cols = ['YJLY', 'CJCount'].reduce((r, key) => ({ ...r, [key]: filterData.reduce((a, c) => a + price_to_number(c[key]), 0) }), {});
// console.log(tKey, filterData, filterDataYear, filterDataYear2);
return { ...cols, GroupCount:CJCount, CJCount, YJLY, rowYear, rawData: filterData, rawYearData: filterDataYear, rawYearData2: filterDataYear2 };
};
const dataSalesGHOther = (rawData, yearData, yearData2) => {
const exceptContry = Object.values(GHCountryListSetting).reduce((r, c) => r.concat(c), []);
// console.log('exceptContry', exceptContry);
// console.log('OOoo rawData', rawData.map(e => e.destinationCountry));
const filterData = rawData
.filter((ele) => ['泰国水灯节'].every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1))
.filter((ele) => exceptContry.every((item) => !ele.destinationCountry.includes(item)));
// console.log('OOoo', filterData.map(e => e.destinationCountry), filterData.map(e => e.productType));
const CJCount = filterData.length; // filterData.reduce((r, c) => r + c.CJCount, 0);
const YJLY = filterData.reduce((r, c) => r + price_to_number(c.ML), 0);
const filterDataYear = yearData
.filter((ele) => ['泰国水灯节'].every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1))
.filter((ele) => exceptContry.every((item) => !ele.destinationCountry.includes(item)));
const rowYearData = { CJCount: filterDataYear.length, YJLY: filterDataYear.reduce((r, c) => r + price_to_number(c.ML), 0) };
// console.log('OOoo year', filterDataYear.map(e => e.destinationCountry), filterDataYear.map(e => e.productType));
const filterDataYear2 = yearData2
.filter((ele) => ['泰国水灯节'].every((item) => ele.productType.toLocaleLowerCase().indexOf(item) === -1))
.filter((ele) => exceptContry.every((item) => !ele.destinationCountry.includes(item)));
const rowYearData2 = { CJCount: filterDataYear2.length, YJLY: filterDataYear2.reduce((r, c) => r + price_to_number(c.ML), 0) };
// console.log('Oo', filterDataYear2.map(e => e.destinationCountry), filterDataYear2);
// console.log('Oo row', rowYearData2);
const rowYear = {
YJLY: price_to_number(rowYearData.YJLY), CJCount: rowYearData.CJCount, GroupCount: rowYearData.CJCount,
YJLY2: price_to_number(rowYearData2.YJLY), CJCount2: rowYearData2.CJCount, GroupCount2: rowYearData2.CJCount,
};
return { GroupCount:CJCount, CJCount, YJLY, rowYear, rawData: filterData, rawYearData: filterDataYear, rawYearData2: filterDataYear2 };
};
class MeetingData {
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
}
searchValues = {
DateType: { key: 'applyDate', value: 'applyDate', label: '提交日期' },
};
setSearchValues(body) {
this.searchValues = body;
}
GHTableData = [];
GHTableLoading = false;
/**
* 获取市场订单数据 ---------------------------------------------------------------------------------------------------
*/
dataGHOrder = async (param) => {
// console.log('dataGH', param);
this.GHTableLoading = true;
const defaultParam = { DateType: 'applyDate' };
// 本周
const CHData = await getDetailData({ ...param, ...defaultParam, 'DepartmentList': '1', 'WebCode': 'All' });
const exceptCHData = await getDetailData({ ...param, ...defaultParam, 'DepartmentList': '28,33', 'WebCode': 'All' });
const yearStart = moment().startOf("year").format(DATE_FORMAT);
/** 截至今年 - 行 */
const [
{ ordercountTotal1: CHDataYear },
{ ordercount1: exceptCHDataYear },
] = await Promise.all([
getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1', 'WebCode': 'All', OrderType: 'LineClass' }),
getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '28,33', 'WebCode': 'All', OrderType: 'Product' }),
]);
// const { ordercountTotal1: CHDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1', 'WebCode': 'All', OrderType: 'LineClass' });
// const { ordercount1: exceptCHDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '28,33', 'WebCode': 'All', OrderType: 'Product' });
/** 截至今年 - 列 */
const [
{ ordercount1: ColLineClassDataYear },
{ ordercountTotal1: ColToBDataYear },
{ ordercountTotal1: ColExternalDataYear },
] = await Promise.all([
getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'All', OrderType: 'LineClass' }),
getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'GHTOBHW,GHTOBZG', OrderType: 'LineClass'}),
getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'ZWQD', OrderType: 'LineClass'}),
]);
// const { ordercount1: ColLineClassDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'All', OrderType: 'LineClass' });
// const { ordercountTotal1: ColToBDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'GHTOBHW,GHTOBZG', OrderType: 'LineClass' });
// const { ordercountTotal1: ColExternalDataYear } = await getOrderCountByType({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'ZWQD', OrderType: 'LineClass' });
// 老客户
const yearDetail = await getDetailData({ ...param, ...defaultParam, Date1: yearStart, 'DepartmentList': '1,2,28,7,33', 'WebCode': 'All' });
const { isOld1: isOld1Year } = rowItem(yearDetail);
const colYearRow = {
LineClass_Origin: ColLineClassDataYear.filter((ele) => ele.OrderType.toLocaleLowerCase().indexOf('网前自然订单') !== -1).reduce((r, c) => r + c.OrderCount, 0),
LineClass_PPC: ColLineClassDataYear.filter((ele) => ele.OrderType.toLocaleLowerCase().indexOf('ppc') !== -1).reduce((r, c) => r + c.OrderCount, 0),
toB: ColToBDataYear.OrderCount,
isOld1: isOld1Year,
external: ColExternalDataYear.OrderCount,
};
const rows = [
{ key: 'ch', label: '中国', ...rowItem(CHData), rowYear: CHDataYear.OrderCount },
{ key: 'ja', label: '日本+', ...dataJA(exceptCHData, exceptCHDataYear) },
{ key: 'se', label: '东南亚+', ...dataSE(exceptCHData, exceptCHDataYear) },
{ key: 'in', label: '印度+', ...dataIN(exceptCHData, exceptCHDataYear) },
{ key: 'other', label: '其他GH', ...dataGHOther(exceptCHData, exceptCHDataYear) },
];
const columnsSum = ['LineClass_Origin', 'LineClass_PPC', 'toB', 'external', 'isOld1', 'total', 'rowYear'].reduce(
(r, col) => ({
...r,
[col]: rows.reduce((rr, row) => rr + row[col], 0),
}),
{}
);
rows.push({ key: 'columnSum', label: '合计', ...columnsSum });
rows.push({ key: 'colYearRow', label: '截至', ...colYearRow });
runInAction(() => {
this.GHTableData = rows;
this.GHTableLoading = false;
});
};
GHSalesTableData = [];
GHSalesLoading = false;
/**
* 获取GH销售数据 ---------------------------------------------------------------------------------------------------
*/
dataGHSales = async (param) => {
this.GHSalesLoading = true;
const salesParam = { ...param, DateType: 'confirmDate', WebCode: 'CHT,AH,GH,GHKYZG,GHKYHW,ZWQD,GH_ZWQD_HW,JH,HTravel', OrderType:'ALL', }; // WebCode: 不含分销, 这个参数值不直接使用, 不含分销=总数-分销
const partnerParam = { WebCode: 'GHTOBHW,GHTOBZG' };
const [
{ total1: CHSalesDataTotal },
{ total1: CHPartnerSalesData },
{ total1: AHSalesDataTotal },
{ total1: AHpartnerSalesData },
{ total1: GHSalesDataTotal },
{ total1: GHpartnerSalesData },
] = await Promise.all([
getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', ...partnerParam }),
getDepartmentOrderMLByType({...salesParam, DepartmentList: '28', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, DepartmentList: '28', ...partnerParam }),
getDepartmentOrderMLByType({...salesParam, DepartmentList: '33', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, DepartmentList: '33', ...partnerParam }),
]);
// 不含分销 = 总额 - 分销
const CHSalesData = {'COLI_CJCount': CHSalesDataTotal.COLI_CJCount-CHPartnerSalesData.COLI_CJCount, 'COLI_ML2': CHSalesDataTotal.COLI_ML2-CHPartnerSalesData.COLI_ML2};
const AHSalesData = {'COLI_CJCount': AHSalesDataTotal.COLI_CJCount-AHpartnerSalesData.COLI_CJCount, 'COLI_ML2': AHSalesDataTotal.COLI_ML2-AHpartnerSalesData.COLI_ML2};
const GHSalesData = {'COLI_CJCount': GHSalesDataTotal.COLI_CJCount-GHpartnerSalesData.COLI_CJCount, 'COLI_ML2': GHSalesDataTotal.COLI_ML2-GHpartnerSalesData.COLI_ML2};
const partnerSalesData = {'COLI_CJCount': CHPartnerSalesData.COLI_CJCount+AHpartnerSalesData.COLI_CJCount, 'COLI_ML2': CHPartnerSalesData.COLI_ML2+AHpartnerSalesData.COLI_ML2};
const totalSalesData = {
'COLI_CJCount': CHSalesDataTotal.COLI_CJCount + AHSalesDataTotal.COLI_CJCount + GHSalesDataTotal.COLI_CJCount,
'COLI_ML2': CHSalesDataTotal.COLI_ML2 + AHSalesDataTotal.COLI_ML2 + GHSalesDataTotal.COLI_ML2,
};
const yearStart = moment().startOf("year").format(DATE_FORMAT);
const yearEnd = moment().endOf("year").format(SMALL_DATETIME_FORMAT);
/** 截至今年 - 成交 */
const [
{ total1: CHSalesYearTotal },
{ total1: CHPartnerSalesYear },
{ total1: AHSalesYearTotal },
{ total1: AHpartnerSalesYear },
{ total1: GHSalesYearTotal },
{ total1: GHpartnerSalesYear },
] = await Promise.all([
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2', ...partnerParam }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '28', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '28', ...partnerParam }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '33', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '33', ...partnerParam }),
]);
const CHDataYear = {'COLI_CJCount': CHSalesYearTotal.COLI_CJCount-CHPartnerSalesYear.COLI_CJCount, 'COLI_ML2': CHSalesYearTotal.COLI_ML2-CHPartnerSalesYear.COLI_ML2};
const AHDataYear = {'COLI_CJCount': AHSalesYearTotal.COLI_CJCount-AHpartnerSalesYear.COLI_CJCount, 'COLI_ML2': AHSalesYearTotal.COLI_ML2-AHpartnerSalesYear.COLI_ML2};
const GHDataYear = {'COLI_CJCount': GHSalesYearTotal.COLI_CJCount-GHpartnerSalesYear.COLI_CJCount, 'COLI_ML2': GHSalesYearTotal.COLI_ML2-GHpartnerSalesYear.COLI_ML2};
const partnerDataYear = {'COLI_CJCount': CHPartnerSalesYear.COLI_CJCount+AHpartnerSalesYear.COLI_CJCount, 'COLI_ML2': CHPartnerSalesYear.COLI_ML2+AHpartnerSalesYear.COLI_ML2};
const totalDataYear = {
'COLI_CJCount': CHSalesYearTotal.COLI_CJCount + AHSalesYearTotal.COLI_CJCount + GHSalesYearTotal.COLI_CJCount,
'COLI_ML2': CHSalesYearTotal.COLI_ML2 + AHSalesYearTotal.COLI_ML2 + GHSalesYearTotal.COLI_ML2,
};
/** 截至今年 - 走团 */
const [
{ total1: CHStartDataYearTotal },
{ total1: CHPartnerStartDataYear },
{ total1: AHStartDataYearTotal },
{ total1: AHpartnerStartDataYear },
{ total1: GHStartDataYearTotal },
{ total1: GHpartnerStartDataYear },
] = await Promise.all([
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '1,2', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '1,2', ...partnerParam }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '28', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '28', ...partnerParam }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '33', WebCode: 'ALL', }),
getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '33', ...partnerParam }),
]);
const CHStartDataYear = {'COLI_CJCount': CHStartDataYearTotal.COLI_CJCount-CHPartnerStartDataYear.COLI_CJCount, 'COLI_ML2': CHStartDataYearTotal.COLI_ML2-CHPartnerStartDataYear.COLI_ML2};
const AHStartDataYear = {'COLI_CJCount': AHStartDataYearTotal.COLI_CJCount-AHpartnerStartDataYear.COLI_CJCount, 'COLI_ML2': AHStartDataYearTotal.COLI_ML2-AHpartnerStartDataYear.COLI_ML2};
const GHStartDataYear = {'COLI_CJCount': GHStartDataYearTotal.COLI_CJCount-GHpartnerStartDataYear.COLI_CJCount, 'COLI_ML2': GHStartDataYearTotal.COLI_ML2-GHpartnerStartDataYear.COLI_ML2};
const partnerStartDataYear = {'COLI_CJCount': CHPartnerStartDataYear.COLI_CJCount+AHpartnerStartDataYear.COLI_CJCount, 'COLI_ML2': CHPartnerStartDataYear.COLI_ML2+AHpartnerStartDataYear.COLI_ML2};
const totalStartDataYear = {
'COLI_CJCount': CHStartDataYearTotal.COLI_CJCount + AHStartDataYearTotal.COLI_CJCount + GHStartDataYearTotal.COLI_CJCount,
'COLI_ML2': CHStartDataYearTotal.COLI_ML2 + AHStartDataYearTotal.COLI_ML2 + GHStartDataYearTotal.COLI_ML2,
};
const rows = [
{
key: 'ch',
label: 'CH(不含分销)',
YJLY: price_to_number(CHSalesData.COLI_ML2),
CJCount: (CHSalesData.COLI_CJCount),
rowYear: { YJLY: price_to_number(CHDataYear.COLI_ML2), CJCount: CHDataYear.COLI_CJCount, YJLY2: price_to_number(CHStartDataYear.COLI_ML2) },
},
{
key: 'ah',
label: 'HT(不含分销)',
YJLY: price_to_number(AHSalesData.COLI_ML2),
CJCount: (AHSalesData.COLI_CJCount),
rowYear: { YJLY: price_to_number(AHDataYear.COLI_ML2), CJCount: AHDataYear.COLI_CJCount, YJLY2: price_to_number(AHStartDataYear.COLI_ML2) },
},
{
key: 'partner',
label: '分销',
YJLY: price_to_number(partnerSalesData.COLI_ML2),
CJCount: (partnerSalesData.COLI_CJCount),
rowYear: { YJLY: price_to_number(partnerDataYear.COLI_ML2), CJCount: partnerDataYear.COLI_CJCount, YJLY2: price_to_number(partnerStartDataYear.COLI_ML2) },
},
{
key: 'gh',
label: 'GH(不含分销)',
YJLY: price_to_number(GHSalesData.COLI_ML2),
CJCount: (GHSalesData.COLI_CJCount),
rowYear: { YJLY: price_to_number(GHDataYear.COLI_ML2), CJCount: GHDataYear.COLI_CJCount, YJLY2: price_to_number(GHStartDataYear.COLI_ML2) },
},
{
key: 'total',
label: '合计',
YJLY: price_to_number(totalSalesData.COLI_ML2),
CJCount: (totalSalesData.COLI_CJCount),
rowYear: { YJLY: price_to_number(totalDataYear.COLI_ML2), CJCount: totalDataYear.COLI_CJCount, YJLY2: price_to_number(totalStartDataYear.COLI_ML2) },
},
];
runInAction(() => {
this.GHSalesTableData = rows;
this.GHSalesLoading = false;
});
};
GHServiceTableData = [];
GHServiceLoading = false;
/**
* 获取GH服务数据 ---------------------------------------------------------------------------------------------------
*/
dataGHService = async (param) => {
this.GHServiceLoading = true;
const serviceParam = { ...param, DateType: 'startDate', 'WebCode': 'All' };
// 走团数
const [
{ ordercountTotal1: { OrderCount: CHGroupCount } },
{ ordercountTotal1: { OrderCount: AHGroupCount } },
{ ordercountTotal1: { OrderCount: GHGroupCount } },
] = await Promise.all([
getOrderCountByType({ ...serviceParam, 'DepartmentList': '1,2', OrderType: 'Form'}),
getOrderCountByType({ ...serviceParam, 'DepartmentList': '28', OrderType: 'Form'}),
getOrderCountByType({ ...serviceParam, 'DepartmentList': '33', OrderType: 'Form'}),
]);
// 走团数 - 年
// * 走团: 整团数, 而非各地接社/目的地的总和
const yearStart = moment().startOf("year").format(DATE_FORMAT);
const [
{ ordercountTotal1: { OrderCount: CHGroupCountYear } },
{ ordercountTotal1: { OrderCount: AHGroupCountYear } },
{ ordercountTotal1: { OrderCount: GHGroupCountYear } },
] = await Promise.all([
getOrderCountByType({ ...serviceParam, 'DepartmentList': '1,2', OrderType: 'Form', Date1: yearStart, }),
getOrderCountByType({ ...serviceParam, 'DepartmentList': '28', OrderType: 'Form', Date1: yearStart, }),
getOrderCountByType({ ...serviceParam, 'DepartmentList': '33', OrderType: 'Form', Date1: yearStart, }),
]);
// 好评数
const [
{ total1: { GoodCount: CHGoodCount } },
{ total1: { GoodCount: AHGoodCount } },
{ total1: { GoodCount: GHGoodCount } },
] = await Promise.all([
getAgentGroupInfoALL({ ...serviceParam, 'DepartmentList': '1,2', }),
getAgentGroupInfoALL({ ...serviceParam, 'DepartmentList': '28', }),
getAgentGroupInfoALL({ ...serviceParam, 'DepartmentList': '33', }),
]);
// 好评数 - 年
const [
{ total1: { GoodCount: CHGoodCountYear, } },
{ total1: { GoodCount: AHGoodCountYear, } },
{ total1: { GoodCount: GHGoodCountYear, } },
] = await Promise.all([
getAgentGroupInfoALL({ ...serviceParam, Date1: yearStart, 'DepartmentList': '1,2', }),
getAgentGroupInfoALL({ ...serviceParam, Date1: yearStart, 'DepartmentList': '28', }),
getAgentGroupInfoALL({ ...serviceParam, Date1: yearStart, 'DepartmentList': '33', }),
]);
const rows = [
{ key: 'ch', label: 'CH', GoodCount: CHGoodCount, GroupCount: CHGroupCount, rowYear: { GroupCount: CHGroupCountYear, GoodCount: CHGoodCountYear } },
{ key: 'ah', label: 'HT', GoodCount: AHGoodCount, GroupCount: AHGroupCount, rowYear: { GroupCount: AHGroupCountYear, GoodCount: AHGoodCountYear } },
{ key: 'Gh', label: 'GH', GoodCount: GHGoodCount, GroupCount: GHGroupCount, rowYear: { GroupCount: GHGroupCountYear, GoodCount: GHGoodCountYear } },
];
// const GHRowWeek = { GoodCount: GHGoodCountWeek, GroupCount: 0 };
// const columnsSum = ['GoodCount', 'GroupCount'].reduce((r, col) => ({ ...r, [col]: [...rows, GHRowWeek].reduce((rr, row) => rr + (row[col] || 0), 0) }), {});
// const allYearData = rows.map((row) => row.rowYear).concat([{ GoodCount: GHGoodCountYear, GroupCount: 0 }]);
// // console.log(allYearData);
// const rowYear = ['GoodCount', 'GroupCount', ].reduce((r, col) => ({ ...r, [col]: allYearData.reduce((rr, row) => rr + (row[col] || 0), 0) }), {});
// rows.push({ key: 'columnSum', label: '合计', ...columnsSum, rowYear });
// console.log(rows);
runInAction(() => {
this.GHServiceTableData = rows;
this.GHServiceLoading = false;
});
};
}
export default MeetingData;

@ -73,21 +73,6 @@ class OrdersStore {
this.include_tickets = value;
};
searchValues = {
DateType: { key: 'applyDate', label: '提交日期'},
WebCode: { key: 'All', label: '所有来源'},
IncludeTickets: { key: '1', label: '含门票'},
DepartmentList: groupsMappedByCode.GH,
};
setSearchValues(obj, values) {
this.groups = obj.DepartmentList;
this.webcode = obj.WebCode;
this.include_tickets = obj.IncludeTickets;
this.date_type = obj.DateType;
this.searchValues = values;
}
// 切换标签页
onChange_Tabs_sub(ordertype, ordertype_sub, active_key) {
this.active_tab_key_sub = active_key;
@ -227,9 +212,9 @@ class OrdersStore {
let url = "/service-web/QueryData/GetOrderCount";
url += `?OrderType=${ordertype}&OrderType_val=${ordertype_sub}&IncludeTickets=${this.include_tickets}`;
url += `&DepartmentList=${this.groups.toString()}&DateType=${this.date_type}`;
url += "&WebCode=" + this.webcode + "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&WebCode=" + this.webcode + "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) + "%2023:59:59";
}
fetch(config.HT_HOST + url)
.then(response => response.json())
@ -256,10 +241,10 @@ class OrdersStore {
const date_picker_store = this.rootStore.date_picker_store;
let url = "/service-web/QueryData/GetOrderCount"; // ?WebCode=cht&COLI_ApplyDate1=2022-08-01&COLI_ApplyDate2=2022-08-31&COLI_ApplyDateold1=2021-08-01&COLI_ApplyDateold2=2021-08-31';
url += "?WebCode=" + this.webcode + "&IncludeTickets=" + this.include_tickets;
url += "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT) ;
url += "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
url += `&DepartmentList=${this.groups.toString()}&DateType=${this.date_type}`;
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT) ;
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) + "%2023:59:59";
}
fetch(config.HT_HOST + url)
.then(response => response.json())
@ -284,9 +269,9 @@ class OrdersStore {
let url = "/service-web/QueryData/GetOrderCountByType";
url += "?WebCode=" + this.webcode + "&OrderType=" + order_type + "&IncludeTickets=" + this.include_tickets;
url += `&DepartmentList=${this.groups.toString()}&DateType=${this.date_type}`;
url += "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) + "%2023:59:59";
}
fetch(config.HT_HOST + url)
.then(response => response.json())
@ -311,9 +296,9 @@ class OrdersStore {
}
url += `?WebCode=${this.webcode}&OrderType=${ordertype}&OrderType_val=${ordertype_sub}&SubOrderType=${sub_type}` + "&IncludeTickets=" + this.include_tickets;
url += `&DepartmentList=${this.groups.toString()}&DateType=${this.date_type}`;
url += "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT);
url += "&COLI_ApplyDate1=" + date_picker_store.start_date.format(config.DATE_FORMAT) + "&COLI_ApplyDate2=" + date_picker_store.end_date.format(config.DATE_FORMAT) + "%2023:59:59";
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT);
url += "&COLI_ApplyDateold1=" + date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "&COLI_ApplyDateold2=" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) + "%2023:59:59";
}
fetch(config.HT_HOST + url)
.then(response => response.json())

@ -32,24 +32,8 @@ class SaleStore {
type_data_sub = []; // 类型的子维度数据
date_title = 'date_title'; // 日期段,只用于显示,防止日期选择控件的变化导致页面刷新
searchValues = {
DateType: { key: 'confirmDate', label: '确认日期'},
WebCode: { key: 'All', label: '所有来源'},
IncludeTickets: { key: '1', label: '含门票'},
DepartmentList: [groupsMappedByCode.GH],
operator: '',
};
setSearchValues(obj, values) {
this.groups = obj.DepartmentList;
this.webcode = obj.WebCode;
this.include_tickets = obj.IncludeTickets;
this.date_type = obj.DateType;
this.searchValues.operator = obj.operator;
}
salesTrade = {
groupType: 'dept', loading: false, tableDataSource: [],
groupType: 'dept', loading: false,
operator: [], dept: [], overview: [],
operatorMapped: {}, pickSales: [], pickSalesData: [],
};
@ -84,7 +68,6 @@ class SaleStore {
// 切换标签页
onChange_Tabs(active_key) {
this.active_tab_key = active_key;
this.type_data = {};
}
// 下单日期或者出发日期
@ -113,9 +96,9 @@ class SaleStore {
this.date_title = `${date1_start}~${date1_end}`;
let url = '/service-web/QueryData/GetDepartmentOrderML';
url += `?DepartmentList=${this.groups.toString()}&DateType=${this.date_type}&WebCode=${this.webcode}&IncludeTickets=${this.include_tickets}`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:00`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:59`;
if (date2_start && date2_end) {
url += `&OldDate1=${date2_start}&OldDate2=${date2_end}%2023:59:00`;
url += `&OldDate1=${date2_start}&OldDate2=${date2_end}%2023:59:59`;
this.date_title += ` ${date2_start}~${date2_end}`;
}
fetch(config.HT_HOST + url)
@ -155,9 +138,9 @@ class SaleStore {
this.date_title = `${date1_start}~${date1_end}`;
let url = '/service-web/QueryData/GetDepartmentOrderMLByType';
url += `?DepartmentList=${this.groups.toString()}&DateType=${this.date_type}&OrderType=${this.active_tab_key}&WebCode=${this.webcode}&IncludeTickets=${this.include_tickets}`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:00`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:59`;
if (date2_start && date2_end) {
url += `&OldDate1=${date2_start}&OldDate2=${date2_end}%2023:59:00`;
url += `&OldDate1=${date2_start}&OldDate2=${date2_end}%2023:59:59`;
this.date_title += ` ${date2_start}~${date2_end}`;
}
fetch(config.HT_HOST + url)
@ -167,42 +150,35 @@ class SaleStore {
// eslint-disable-next-line no-constant-condition
if (false) {
} else {
const result1 = comm.isEmpty(this.searchValues.operator) ? json.result1 : json.result1.filter(row => (this.searchValues.operator.split(',')).includes(String(row.OPI_SN)));
const result2 = comm.isEmpty(this.searchValues.operator) ? json.result2 : json.result2.filter(row => (this.searchValues.operator.split(',')).includes(String(row.OPI_SN)));
const allOPI1 = comm.uniqWith(result1.map(rr => ({ text: rr.OPI_Name, value: rr.OPI_SN })), (a, b) => JSON.stringify(a) === JSON.stringify(b)).sort((a, b) => a.text.localeCompare(b.text));
if (this.active_tab_key === 'All') {
result.columns = [
{
title: '账户名',
title: '',
children: [
{
title: '',
dataIndex: 'OPI_Name',
},
],
sorter: (a, b) => (a?.OPI_Name || '').localeCompare(b.OPI_Name, 'zh-CN'),
filters: allOPI1,
onFilter: (value, record) => record.OPI_SN === value,
filterSearch: true,
},
{
title: '毛利',
children: [{ title: result1.reduce((a, b) => a + comm.price_to_number(b.COLI_ML), 0), dataIndex: 'COLI_ML' }],
children: [{ title: json.result1.reduce((a, b) => a + comm.price_to_number(b.COLI_ML), 0), dataIndex: 'COLI_ML' }],
sorter: (a, b) => comm.price_to_number(b.COLI_ML) - comm.price_to_number(a.COLI_ML),
},
{
title: '成行率',
children: [{ title: comm.formatPercent(result1.reduce((a, b) => a + b.COLI_CJCount, 0) / result1.reduce((a, b) => a + b.COLI_OrderCount, 0)), dataIndex: 'COLI_CJrate' }],
children: [{ title: comm.formatPercent(json.result1.reduce((a, b) => a + b.COLI_CJCount, 0) / json.result1.reduce((a, b) => a + b.COLI_OrderCount, 0)), dataIndex: 'COLI_CJrate' }],
sorter: (a, b) => parseInt(b.COLI_CJrate) - parseInt(a.COLI_CJrate),
},
{
title: '成团数',
children: [{ title: result1.reduce((a, b) => a + b.COLI_CJCount, 0), dataIndex: 'COLI_CJCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.COLI_CJCount, 0), dataIndex: 'COLI_CJCount' }],
sorter: (a, b) => b.COLI_CJCount - a.COLI_CJCount,
},
{
title: '订单数',
children: [{ title: result1.reduce((a, b) => a + b.COLI_OrderCount, 0), dataIndex: 'COLI_OrderCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.COLI_OrderCount, 0), dataIndex: 'COLI_OrderCount' }],
sorter: (a, b) => b.COLI_OrderCount - a.COLI_OrderCount,
},
{
@ -222,7 +198,7 @@ class SaleStore {
},
];
result.dataSource = result1;
result.dataSource = json.result1;
} else if (this.active_tab_key === 'ResponseRateByWL') {
result.columns = [
{
@ -233,55 +209,49 @@ class SaleStore {
dataIndex: 'OPI_Name',
},
],
sorter: (a, b) => (a?.OPI_Name || '').localeCompare(b.OPI_Name, 'zh-CN'),
filters: allOPI1,
onFilter: (value, record) => record.OPI_SN === value,
filterSearch: true,
},
{
title: '报价次数',
children: [{ title: result1.reduce((a, b) => a + b.PriceTime, 0), dataIndex: 'PriceTime' }],
children: [{ title: json.result1.reduce((a, b) => a + b.PriceTime, 0), dataIndex: 'PriceTime' }],
sorter: (a, b) => b.PriceTime - a.PriceTime,
},
{
title: '邮件发送次数',
children: [{ title: result1.reduce((a, b) => a + b.mailSendTime, 0), dataIndex: 'mailSendTime' }],
children: [{ title: json.result1.reduce((a, b) => a + b.mailSendTime, 0), dataIndex: 'mailSendTime' }],
sorter: (a, b) => b.mailSendTime - a.mailSendTime,
},
{
title: 'WhatsApp客人会话次数',
children: [{ title: result1.reduce((a, b) => a + b.WhatsAppGuestChatCount, 0), dataIndex: 'WhatsAppGuestChatCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.WhatsAppGuestChatCount, 0), dataIndex: 'WhatsAppGuestChatCount' }],
sorter: (a, b) => b.WhatsAppGuestChatCount - a.WhatsAppGuestChatCount,
},
{
title: 'WhatsApp外联会话次数',
children: [{ title: result1.reduce((a, b) => a + b.WhatsAppWLChatCount, 0), dataIndex: 'WhatsAppWLChatCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.WhatsAppWLChatCount, 0), dataIndex: 'WhatsAppWLChatCount' }],
sorter: (a, b) => b.WhatsAppWLChatCount - a.WhatsAppWLChatCount,
},
{
title: 'WhatsApp新增客户数',
children: [{ title: result1.reduce((a, b) => a + b.WhatsAppNewGuestCount, 0), dataIndex: 'WhatsAppNewGuestCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.WhatsAppNewGuestCount, 0), dataIndex: 'WhatsAppNewGuestCount' }],
sorter: (a, b) => b.WhatsAppNewGuestCount - a.WhatsAppNewGuestCount,
},
{
title: '微信客人会话次数',
children: [{ title: result1.reduce((a, b) => a + b.WXGuestChatCount, 0), dataIndex: 'WXGuestChatCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.WXGuestChatCount, 0), dataIndex: 'WXGuestChatCount' }],
sorter: (a, b) => b.WXGuestChatCount - a.WXGuestChatCount,
},
{
title: '微信外联会话次数',
children: [{ title: result1.reduce((a, b) => a + b.WXWLChatCount, 0), dataIndex: 'WXWLChatCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.WXWLChatCount, 0), dataIndex: 'WXWLChatCount' }],
sorter: (a, b) => b.WXWLChatCount - a.WXWLChatCount,
},
{
title: '微信新增客户数',
children: [{ title: result1.reduce((a, b) => a + b.WXNewGuestCount, 0), dataIndex: 'WXNewGuestCount' }],
children: [{ title: json.result1.reduce((a, b) => a + b.WXNewGuestCount, 0), dataIndex: 'WXNewGuestCount' }],
sorter: (a, b) => b.WXNewGuestCount - a.WXNewGuestCount,
},
];
result.dataSource = result1
.sort(comm.sortBy('PriceTime'))
.reverse();
result.dataSource = json.result1;
} else if (this.active_tab_key === 'ResponseRateWhatsApp') {
result.columns = [
{
@ -292,10 +262,6 @@ class SaleStore {
dataIndex: 'OPI_Name',
},
],
sorter: (a, b) => (a?.OPI_Name || '').localeCompare(b.OPI_Name, 'zh-CN'),
filters: allOPI1,
onFilter: (value, record) => record.OPI_SN === value,
filterSearch: true,
},
{
title: '首次回复率',
@ -361,13 +327,11 @@ class SaleStore {
sorter: (a, b) => b.COLI_ConfirmTimeAVG - a.COLI_ConfirmTimeAVG,
},
];
result.dataSource = result1
.sort(comm.sortBy('COLI_ConfirmTimeAVG'))
.reverse();
result.dataSource = json.result1;
} else {
const diffDateFlagYes = !comm.isEmpty(date_moment.start_date_cp);
// if (this.active_tab_key == "Country")
const mergeDiffData = calcDiff({result1, result2});
const mergeDiffData = calcDiff({result1: json.result1, result2: json.result2});
// 获取类型的项目,去掉重复,作为列名
const type_name_arr = [];
mergeDiffData.map((item) => {
@ -384,7 +348,7 @@ class SaleStore {
const total_data_value = items.length ? items.reduce((a, b) => a + b.COLI_YJLY, 0) : ''; // 记录累加
const total_data_value_diff = items.length ? items.reduce((a, b) => a + b.COLI_YJLY2, 0) : ''; // 记录累加
if (comm.empty(type_data[op_sn])) {
type_data[op_sn] = [{ key: item.OPI_SN }, { T_name: item.OPI_Name }, { T_OPI: item.OPI_SN }, { T_total: total_data_value }, { V_total: total_data_value_diff }];
type_data[op_sn] = [{ key: item.OPI_SN }, { T_name: item.OPI_Name }, { T_total: total_data_value }, { V_total: total_data_value_diff }];
}
const _diff = comm.objectMapper(comm.pick(item, ['COLI_YJLY_diff', 'COLI_YJLY_vs', 'COLI_YJLY2']), {
COLI_YJLY2: { key: `v_${item.SubTypeSN}` },
@ -416,12 +380,7 @@ class SaleStore {
totalDiff.diff = (totalDiff.val-totalDiff.diffVal);
totalDiff.vs = comm.fixTo2Decimals(((totalDiff.val-totalDiff.diffVal)/totalDiff.diffVal)*100)+'%';
result.columns.push(
{ title: '顾问', children: [{ title: '', dataIndex: 'T_name', render: (text, record) => <NavLink to={`/sale_sub/${this.active_tab_key}`}>{text}</NavLink> }],
sorter: (a, b) => (a?.T_name || '').localeCompare(b.T_name, 'zh-CN'),
filters: allOPI1,
onFilter: (value, record) => record.T_OPI === value,
filterSearch: true,
},
{ title: '顾问', children: [{ title: '', dataIndex: 'T_name', render: (text, record) => <NavLink to={`/sale_sub/${this.active_tab_key}`}>{text}</NavLink> }] },
{
title: '合计',
children: [
@ -446,17 +405,13 @@ class SaleStore {
vs: comm.fixTo2Decimals(((total_data_value-total_data_value_diff)/total_data_value_diff)*100)+'%',
};
result.columns.push({
title: item.SubTypeName, _val: total_data_value,
title: item.SubTypeName,
children: [{ title: diffDateFlagYes ? comm.show_vs_tag(columnDiff.vs, columnDiff.diff, total_data_value, total_data_value_diff) : total_data_value, dataIndex: data_index }],
sorter: (a, b) => b[`TV_${item.SubTypeSN}`] - a[`TV_${item.SubTypeSN}`],
});
return item;
});
result.columns = [].concat(
comm.cloneDeep(result.columns).slice(0, 2),
comm.cloneDeep(result.columns).slice(2).sort(comm.sortBy('_val')).reverse()
);
result.dataSource = type_data_arr.sort(comm.sortBy('T_total')).reverse();
result.dataSource = type_data_arr;
}
}
runInAction(() => {
@ -480,9 +435,9 @@ class SaleStore {
const date2_end = comm.empty(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
let url = '/service-web/QueryData/GetDepartmentOrderMLByType_sub';
url += `?DepartmentList=${this.groups.toString()}&DateType=${this.date_type}&subType=${type_sub}&subTypeVal=-1&WebCode=${this.webcode}&IncludeTickets=${this.include_tickets}`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:00`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:59`;
if (date2_start && date2_end) {
url += `&OldDate1=${date2_start}&OldDate2=${date2_end}%2023:59:00`;
url += `&OldDate1=${date2_start}&OldDate2=${date2_end}%2023:59:59`;
this.date_title += ` ${date2_start}~${date2_end}`;
}
fetch(config.HT_HOST + url)
@ -496,7 +451,6 @@ class SaleStore {
{
title: '',
dataIndex: 'OPI_Name',
sorter: (a, b) => (a?.OPI_Name || '').localeCompare(b.OPI_Name, 'zh-CN'),
},
{
title: '毛利',
@ -557,36 +511,15 @@ class SaleStore {
});
}
setTableDataSource = (merge = false) => {
if (comm.isEmpty(this.salesTrade[this.salesTrade.groupType]) || comm.isEmpty(this.salesTrade.operator)) {
return false;
}
this.salesTrade.tableDataSource = [].concat(this.salesTrade[this.salesTrade.groupType], merge === false ? this.salesTrade.operator : this.salesTrade.operatorAccount);
};
async fetchOperatorTradeData(groupType, queryData) {
this.salesTrade.loading = true;
const param1 = Object.assign({}, queryData, {groupType, groupDateType: 'year' });
const yearData = await this.fetchTradeDataAll(param1);
const { mergeRows: yearMergeRows } = yearData.result1;
const yData = parseSaleData(yearMergeRows, ['groupsKey', 'groupDateType']);
const param2 = Object.assign({}, queryData, {groupType, groupDateType: 'month' });
const monthData = await this.fetchTradeDataAll(param2);
const { salesTradeDataMapped, accountMergeYearMonth } = await this.handleYearMonthData(groupType, queryData, yearData, monthData);
runInAction(() => {
this.salesTrade.loading = false;
this.salesTrade[groupType] = Object.values(salesTradeDataMapped).sort(comm.sortBy('yearML')).reverse();
this.salesTrade[`${groupType}Account`] = accountMergeYearMonth.sort(comm.sortBy('yearML')).reverse();
this.salesTrade[`${groupType}Mapped`] = Object.values(salesTradeDataMapped).reduce((r, v) => ({...r, [v.groupsKey]: Object.values(v.mData)}), {});
});
}
handleYearMonthData = async (groupType, queryData, yearData, monthData) => {
const { mergeRows: yearMergeRows, mergeLabelsRows: yearMergeLabelsRows } = yearData.result1;
const { mergeRows: monthMergeRows, mergeLabelsRows: monthMergeLabelsRows } = monthData.result1;
const yData = parseSaleData(yearMergeRows, ['groupsKey', 'groupDateType']);
const { mergeRows: monthMergeRows } = monthData.result1;
const mData = parseSaleData(monthMergeRows, ['groupsKey', 'groupDateType']);
const mergeYearMonth = Object.keys(yData).map(ykey => ({
...yData[ykey],
@ -596,17 +529,6 @@ class SaleStore {
yearML: Object.values(yData[ykey].data)[0]?.SumML || 0, // 整理排序用
}));
const accountYData = parseSaleData(yearMergeLabelsRows, ['groupsKey', 'groupDateType']);
const accountMData = parseSaleData(monthMergeLabelsRows, ['groupsKey', 'groupDateType']);
const accountMergeYearMonth = Object.keys(accountYData).map(ykey => ({
...accountYData[ykey],
mData: accountMData[ykey].data,
yData: Object.values(accountYData[ykey].data)[0],
data: undefined,
yearML: Object.values(accountYData[ykey].data)[0]?.SumML || 0, // 整理排序用
}));
const accountSalesTradeDataMapped = accountMergeYearMonth.reduce((ro, vo) => ({...ro, [vo.groupsKey]: vo}), {});
const kpiObjects = mergeYearMonth.map(v => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel }));
const kpiData = await this.getKPISettings(groupType, queryData, kpiObjects);
const salesTradeDataMapped = mergeYearMonth.reduce((ro, vo) => ({...ro, [vo.groupsKey]: vo}), {});
@ -620,16 +542,18 @@ class SaleStore {
if (comm.isEmpty(salesTradeDataMapped[ele.object_id].mData[`month_${padM}`])) {
salesTradeDataMapped[ele.object_id].mData[`month_${padM}`] = {};
}
// set
Object.assign(salesTradeDataMapped[ele.object_id].mData[`month_${padM}`], _monthMLKPI);
return km;
});
return ele;
});
return { salesTradeDataMapped, accountSalesTradeDataMapped, accountMergeYearMonth };
};
runInAction(() => {
this.salesTrade.loading = false;
this.salesTrade[groupType] = Object.values(salesTradeDataMapped).sort(comm.sortBy('yearML')).reverse();
this.salesTrade[`${groupType}Mapped`] = Object.values(salesTradeDataMapped).reduce((r, v) => ({...r, [v.groupsKey]: Object.values(v.mData)}), {});
});
}
async getKPISettings(curObject, queryData, objects) {
const getkpiParam = comm.objectMapper(queryData, {

@ -1,211 +0,0 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortDescBy, groupBy, pick, unique } from '../utils/commons';
import { groupsMappedByCode } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import moment from 'moment';
const fetchResultsData = async (param) => {
const defaultParam = {
WebCode: 'All',
DepartmentList: '',
opisn: -1,
Date1: '',
Date2: '',
groupType: '',
groupDateType: '',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_results', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result : [];
};
const fetchProcessData = async (param) => {
const defaultParam = {
WebCode: 'All',
DepartmentList: '',
opisn: -1,
Date1: '',
Date2: '',
groupType: '',
groupDateType: '',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_process', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result : [];
};
const fetchRiskDetailData = async (param) => {
const defaultParam = {
opisn: -1,
DateType: '',
WebCode: 'All',
DepartmentList: '',
Date1: '',
Date2: '',
IncludeTickets: '1',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_process_detail', {...defaultParam, ...param});
return json.errcode === 0 ? json.result : [];
};
class SalesCRMData {
constructor(appStore) {
this.appStore = appStore;
makeAutoObservable(this);
}
async get90n180Data(param = {}) {
const retProps = param?.retLabel || '';
let retKey = param.groupDateType === '' ? (param.groupType === 'overview' ? 'dataSource' : 'details') : 'byDate';
retKey = param.opisn ? `operator_${param.opisn}`: retKey;
if (param.opisn ) {
if (!isEmpty(this.results.details)) {
const _this_opi_row = this.results.details.filter(ele => ele.groupsKey === param.opisn);
this.results[retKey] = _this_opi_row;
return;
}
if (!isEmpty(this.results[retKey])) {
return;
}
}
this.results.loading = true;
const date90=this.searchValues.date90;
const date180=this.searchValues.date180;
const [result90, result180] = await Promise.all([
fetchResultsData({ ...this.searchValuesToSub, ...date90, ...param }),
fetchResultsData({ ...this.searchValuesToSub, ...date180, ...param }),
]);
const _90O = groupBy(result90, 'groupsKey');
const _180O = groupBy(result180, 'groupsKey');
const result2 = unique(Object.keys(_90O).concat(Object.keys(_180O))).map((key) => {
return {
...pick(_90O[key]?.[0] || _180O[key][0], ['groupsKey', 'groupsLabel', 'groupType']),
...(retProps && retKey === 'dataSource' ? { groupsLabel: retProps, retProps } : { retProps }),
key: `${param.groupType}-${key}`,
result90: _90O[key]?.[0] || {},
result180: _180O[key]?.[0] || {},
};
});
// console.log(result2, '+++++ +++', retKey);
// console.log(this.results[retKey]?.length);
runInAction(() => {
this.results.loading = false;
this.results[retKey] = [].concat((this.results[retKey] || []), result2);
});
return this.results;
}
async getResultData(param = {}) {
let retKey = param.groupDateType === '' ? 'byOperator' : 'byDate';
retKey = param.opisn ? `operator_byDate_${param.opisn}`: retKey;
if (!isEmpty(this.results[retKey])) {
return;
}
this.results.loading = true;
this.results[retKey] = [];
const res = await fetchResultsData({ ...this.searchValuesToSub, ...param });
runInAction(() => {
this.results.loading = false;
this.results[retKey] = retKey === 'byOperator' ? res.filter(ele => ele.SumML > 0).sort(sortDescBy('SumML')) : res;
});
return this.results;
};
async getProcessData(param = {}) {
// const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate';
let retKey = param.groupDateType === '' ? (param.groupType !== 'operator' ? 'dataSource' : 'details') : 'byDate';
retKey = param.opisn ? `operator_${param.opisn}`: retKey;
if (param.opisn) {
if (!isEmpty(this.process.details)) {
const _this_opi_row = this.process.details.filter(ele => ele.groupsKey === param.opisn);
this.process[retKey] = _this_opi_row;
return;
}
if (!isEmpty(this.process[retKey])) {
return;
}
}
this.process.loading = true;
this.process[retKey] = [];
const res = await fetchProcessData({ ...this.searchValuesToSub, ...param });
runInAction(() => {
this.process.loading = false;
this.process[retKey] = [].concat(this.process[retKey], res);
});
}
async getRiskDetailData(param = {}) {
this.risk.loading = true;
this.risk.dataSource = [];
const res = await fetchRiskDetailData({ ...this.searchValuesToSub, ...param });
runInAction(() => {
this.risk.loading = false;
this.risk.dataSource = res;
this.risk.byLostType = groupBy(res, 'lost_type');
});
}
searchValues = {
date: moment(),
Date1: moment().startOf("week").subtract(7, "days"),
Date2: moment().endOf("week").subtract(7, "days"),
DateType: { key: 'applyDate', label: '提交日期'},
WebCode: { key: 'All', label: '所有来源' },
// IncludeTickets: { key: '1', label: '含门票'},
DepartmentList: [groupsMappedByCode.GH],
operator: '-1',
IncludeTickets: { key: '0', value: '0', label: '不含门票' },
opisn: '-1',
date90: {
Date1: moment().subtract(90, 'days').format(DATE_FORMAT),
Date2: moment().subtract(30, 'days').format(SMALL_DATETIME_FORMAT),
},
date180: {
Date1: moment().subtract(180, 'days').format(DATE_FORMAT),
Date2: moment().subtract(50, 'days').format(SMALL_DATETIME_FORMAT),
}
};
searchValuesToSub = {
date: moment().format(DATE_FORMAT),
Date1: moment().startOf("week").subtract(7, "days").format(DATE_FORMAT),
Date2: moment().endOf("week").subtract(7, "days").format(SMALL_DATETIME_FORMAT),
DateType: 'applyDate',
DepartmentList: groupsMappedByCode.GH.value,
WebCode: 'All',
operator: '-1',
opisn: '-1',
};
setSearchValues(obj, values) {
this.searchValues = { ...this.searchValues, ...values };
if (values.date) {
this.searchValues.date90 = {
Date1: (values.date.clone()).subtract(90, 'days').format(DATE_FORMAT),
Date2: (values.date.clone()).subtract(30, 'days').format(SMALL_DATETIME_FORMAT),
};
this.searchValues.date180 = {
Date1: (values.date.clone()).subtract(180, 'days').format(DATE_FORMAT),
Date2: (values.date.clone()).subtract(50, 'days').format(SMALL_DATETIME_FORMAT),
};
}
this.searchValuesToSub = {...this.searchValuesToSub, ...obj};
}
results = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] };
process = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] };
risk = { loading: false, dataSource: [], byLostType: {}, };
resetData = (rootKey = '') => {
if (rootKey === '') {
return false;
}
this[rootKey].loading = false;
for (const key of Object.keys(this[rootKey])) {
if (key !== 'loading') {
this[rootKey][key] = [];
}
}
};
}
export default SalesCRMData;

@ -1,9 +1,7 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request';
import moment from 'moment';
import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt, cloneDeep } from '../utils/commons';
import { dataFieldAlias } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
class Trade {
constructor(rootStore) {
@ -16,27 +14,19 @@ class Trade {
*/
async fetchSummaryData(queryData) {
this.summaryData.loading = true;
const curQueryData = cloneDeep(queryData);
curQueryData.groupType = curQueryData?.groupType || 'overview';
curQueryData.groupDateType = 'year';
if (isEmpty(curQueryData.DateDiff1)) {
curQueryData.DateDiff1 = moment(curQueryData.Date1).subtract(1, 'year').format(DATE_FORMAT);
curQueryData.DateDiff2 = moment(curQueryData.Date2).subtract(1, 'year').format(SMALL_DATETIME_FORMAT);
}
const multiData = await this.fetchTradeDataAll((curQueryData));
queryData.groupType = queryData?.groupType || 'overview';
queryData.groupDateType = 'year';
const multiData = await this.fetchTradeDataAll(cloneDeep(queryData));
const { summary, traditional, biz } = multiData.result1;
const { summary: summary2, traditional: traditional2, biz: biz2 } = multiData.result2;
// console.log(JSON.stringify(summary), 'mmmmmmmmmmm', multiData);
// console.log(JSON.stringify(summary), 'mmmmmmmmmmm');
const summaryData = {
loading: false,
dataSource: [
{
title: '成团',
col: 6,
value: summary?.[0]?.ConfirmOrder,
originVal: summary?.[0]?.ConfirmOrder || 0,
valueSuffix: undefined,
// valueSuffix: summary?.[0]?.ConfirmRates ? ` / ${summary?.[0]?.ConfirmRates} %` : undefined,
originVal: (summary?.[0]?.ConfirmOrder || 0),
valueSuffix: summary?.[0]?.ConfirmRates ? ` / ${summary?.[0]?.ConfirmRates} %` : undefined,
// VSrate: summary?.[0]?.ConfirmOrderrate,
KPIrate: summary?.[0]?.[dataFieldAlias.ConfirmOrder.nestkey.p],
// hasKPI: !isEmpty(summary?.[0]?.[dataFieldAlias.ConfirmOrder.nestkey.p]),
@ -45,83 +35,39 @@ class Trade {
kpiVal: summary?.[0]?.[dataFieldAlias.ConfirmOrder.nestkey.v],
traditional: { title: '传统', value: traditional?.[0]?.ConfirmOrder },
biz: { title: '商务', value: biz?.[0]?.ConfirmOrder },
...(summary2?.[0]
? {
diff: {
label: summary2[0]?.groupDateVal || '对比',
value: summary2[0]?.ConfirmOrder || 0,
VSrate: summary2[0]?.ConfirmOrder ? fixTo2Decimals(((summary[0]?.ConfirmOrder - summary2[0]?.ConfirmOrder) / summary2[0]?.ConfirmOrder) * 100) : null,
},
}
: {}),
},
{
title: '毛利',
col: 8,
originVal: summary?.[0]?.SumML || 0,
value:
dataFieldAlias.SumML.formatter(summary?.[0]?.SumML || 0) +
'=' +
dataFieldAlias.SumML.formatter(traditional?.[0]?.SumML || 0) +
'+' +
dataFieldAlias.SumML.formatter(biz?.[0]?.SumML || 0),
originVal: (summary?.[0]?.SumML || 0),
value: dataFieldAlias.SumML.formatter(summary?.[0]?.SumML || 0) + '=' + dataFieldAlias.SumML.formatter((traditional?.[0]?.SumML || 0)) + '+' + dataFieldAlias.SumML.formatter((biz?.[0]?.SumML || 0)),
KPIrate: summary?.[0]?.[dataFieldAlias.SumML.nestkey.p],
hasKPI: false,
childrenVisible: true,
kpiVal: summary?.[0]?.[dataFieldAlias.SumML.nestkey.v],
traditional: { title: '传统', value: traditional?.[0]?.SumML || 0 },
biz: { title: '商务', value: biz?.[0]?.SumML || 0 },
...(summary2?.[0]
? {
diff: {
label: summary2[0]?.groupDateVal || '对比',
value: dataFieldAlias.SumML.formatter(summary2[0]?.SumML || 0),
VSrate: summary2[0]?.SumML ? fixTo2Decimals(((summary?.[0]?.SumML - summary2[0]?.SumML) / summary2[0]?.SumML) * 100) : null,
},
}
: {}),
traditional: { title: '传统', value: (traditional?.[0]?.SumML || 0) },
biz: { title: '商务', value: (biz?.[0]?.SumML || 0) },
},
{
title: '完成率',
col: 5,
originVal: summary?.[0]?.[dataFieldAlias.SumML.nestkey.p] || 0,
originVal: (summary?.[0]?.[dataFieldAlias.SumML.nestkey.p] || 0),
value: `${summary?.[0]?.[dataFieldAlias.SumML.nestkey.p] || ''}%`,
hasKPI: false,
childrenVisible: false,
kpiVal: 0, // summary?.[0]?.[dataFieldAlias.SumML.nestkey.p],
traditional: { title: '传统', value: traditional?.[0]?.[dataFieldAlias.SumML.nestkey.p] || 0 },
biz: { title: '商务', value: biz?.[0]?.[dataFieldAlias.SumML.nestkey.p] || 0 },
...(summary2?.[0]
? {
diff: {
label: summary2[0]?.groupDateVal || '对比',
value: `${summary2[0]?.[dataFieldAlias.SumML.nestkey.p] || '-'}%`,
VSrate: null,
},
}
: {}),
kpiVal: 0 , // summary?.[0]?.[dataFieldAlias.SumML.nestkey.p],
traditional: { title: '传统', value: traditional?.[0]?.[dataFieldAlias.SumML.nestkey.p] || 0, },
biz: { title: '商务', value: biz?.[0]?.[dataFieldAlias.SumML.nestkey.p] || 0, },
},
{
title: '人数',
col: 5,
originVal: summary?.[0]?.SumPersonNum || 0,
originVal: (summary?.[0]?.SumPersonNum || 0),
value: summary?.[0]?.SumPersonNum,
// VSrate: summary?.[0]?.SumPersonNumrate,
// KPIrate: summary?.[0]?.[dataFieldAlias.SumPersonNum.nestkey.p],
hasKPI: false, // // !isEmpty(summary?.[0]?.[dataFieldAlias.SumPersonNum.nestkey.p]),,
childrenVisible: true,
// kpiVal: summary?.[0]?.[dataFieldAlias.SumPersonNum.nestkey.v],
traditional: { title: '传统', value: traditional?.[0]?.SumPersonNum },
biz: { title: '商务', value: biz?.[0]?.SumPersonNum },
...(summary2?.[0]
? {
diff: {
label: summary2[0]?.groupDateVal || '对比',
value: summary2[0]?.SumPersonNum || '',
VSrate: summary2[0]?.SumPersonNum ? fixTo2Decimals(((summary?.[0]?.SumPersonNum - summary2[0]?.SumPersonNum) / summary2[0]?.SumPersonNum) * 100) : null,
},
}
: {}),
traditional: { title: '传统', value: traditional?.[0]?.SumPersonNum, },
biz: { title: '商务', value: biz?.[0]?.SumPersonNum, },
},
],
};
@ -141,59 +87,15 @@ class Trade {
queryData.groupType = queryData?.groupType || 'overview';
Object.assign(queryData, { groupDateType: this.timeLineKey });
const multiData = await this.fetchTradeDataAll(cloneDeep(queryData));
const { traditional, biz, summaryRows: summaryRows1, } = multiData.result1;
// const { summaryRows: summaryRows2, mergeRows: mergeRows2 } = multiData.result2;
const { traditional, biz } = multiData.result1;
// console.log(biz, 'mmmmmmmm', queryData, multiData);
const mergeData = [].concat(traditional, biz);
const dateKeyData = groupBy(mergeData, ele => ele.groupDateVal);
const sortByDateKey = Object.values(sortKeys(dateKeyData)).reduce( (a, b) => a.concat(b), []);
runInAction(() => {
this.timeData.loading = false;
this.timeData.dataSource = sortByDateKey;
this.timeData.origin = { summaryRows: summaryRows1 || [] }; // multiData.result1;
});
}
/**
* 有对比的时间轴
*/
async fetchTradeDataDiffByDate(queryData = {}) {
this.timeDiffData.loading = true;
queryData = Object.assign({}, this.searchPayloadHome, queryData); // queryData || this.searchPayloadHome;
queryData.groupType = queryData?.groupType || 'overview';
Object.assign(queryData, { groupDateType: this.timeLineKey });
const multiData = await this.fetchTradeDataAll(cloneDeep(queryData));
const { mergeRows: mergeRows1 } = multiData.result1;
const { mergeRows: mergeRows2 } = multiData.result2;
// console.log(biz, 'mmmmmmmm', queryData, multiData);
// 为了图表的X轴一致
const allDateKey1 = [...new Set(mergeRows1.reduce((rv, vk) => {
rv.push(vk.groupDateVal);
return rv;
}, []))].sort();
const allDateKey2 = [...new Set(mergeRows2.reduce((rv, vk) => {
rv.push(vk.groupDateVal);
return rv;
}, []))].sort();
const allLabelDateKeyMapped = {
...allDateKey2.reduce((obj, k, i) => ({...obj, [k]: allDateKey1[i] || `_${k}`}), {})
};
const mergeKeyDateRows = [].concat(
mergeRows1 || [],
(mergeRows2 || []).map((row, ri) => {
return {
...row,
groupsLabel: `${row.groupsLabel} @${moment(queryData.DateDiff1).year()}`,
groupDateVal: allLabelDateKeyMapped[row.groupDateVal],
rawGroupDateVal: row.groupDateVal,
};
})
).sort(sortBy('groupDateVal'));
runInAction(() => {
this.timeDiffData.loading = false;
this.timeDiffData.dataSource = mergeKeyDateRows;
this.timeData.origin = multiData.result1;
});
}
@ -338,26 +240,18 @@ class Trade {
this.targetTableProps.dataSource = [].concat(Object.values(finalTargetData.targetGuest), Object.values(finalTargetData.targetCountry)); // [finalTargetData.targetTotal], // todo: 总数是重复的
};
searchValues = {};
setSearch(body, form) {
setStateSearch(body) {
this.searchPayloadHome = body;
this.searchValues = form;
}
timeLineKey = 'month';
timeLineKey = 'week';
setTimeLineKey(v) {
this.timeLineKey = v;
}
groupKey = 'overview';
setGroupKey(v) {
this.groupKey = v;
}
resetData = () => {
this.summaryData = { loading: false, dataSource: [], kpi: {}, };
this.timeData = { loading: false, dataSource: [], origin: {}, diff: {} };
this.timeDiffData = { loading: false, dataSource: [], origin: {}, };
this.timeData = { loading: false, dataSource: [], origin: {} };
this.BuData = { loading: false, dataSource: [] };
this.sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] };
this.topData = {};
@ -367,8 +261,7 @@ class Trade {
searchPayloadHome = {};
summaryData = { loading: false, dataSource: [], kpi: {}, };
timeData = { loading: false, dataSource: [], origin: {}, diff: {} };
timeDiffData = { loading: false, dataSource: [], origin: {}, };
timeData = { loading: false, dataSource: [], origin: {} };
BuData = { loading: false, dataSource: [] };
sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] };
topData = {};
@ -382,7 +275,6 @@ class Trade {
{key: 'transactions', title: '营收', dataIndex: 'transactions', render: (v) => dataFieldAlias.transactions.formatter(v)},
], dataSource: [] };
}
export const parseMergeItem = ({traditional, biz}) => {
return ['result1', 'result2'].reduce((res, resKey) => {
const mergeItem = {
@ -392,8 +284,7 @@ export const parseMergeItem = ({traditional, biz}) => {
const mergeRes = [].concat(traditional[resKey], biz[resKey]);
const groupsData = mergeRes.reduce((r, v) => {
if (v.groupsKey ) {
const groupsKeyLower = v.groupsKey.toLowerCase();
(r[groupsKeyLower] || (r[groupsKeyLower] = [])).push(v);
(r[v.groupsKey] || (r[v.groupsKey] = [])).push(v);
}
return r;
}, {});
@ -416,8 +307,8 @@ export const parseMergeItem = ({traditional, biz}) => {
// ConfirmRatesKPIrates: 0, // todo:
}));
// 按对象汇总
const TMapped = traditional[resKey].reduce((r, v) => ({...r, [(v.groupsKey.toLowerCase())]: v}), {});
const BMapped = biz[resKey].reduce((r, v) => ({...r, [(v.groupsKey.toLowerCase())]: v}), {});
const TMapped = traditional[resKey].reduce((r, v) => ({...r, [v.groupsKey]: v}), {});
const BMapped = biz[resKey].reduce((r, v) => ({...r, [v.groupsKey]: v}), {});
const summary = Object.keys(groupsData).map(groupsKey => {
const _groupsKey = groupsKey; // groupsData[groupsKey]?.[0]?.groupsKey || '';
return ['ConfirmOrder', 'SumOrder', 'SumML', 'transactions', 'SumPersonNum'].reduce(
@ -436,9 +327,9 @@ export const parseMergeItem = ({traditional, biz}) => {
}));
// 按每一行
// const ByDate = sortKeys(groupBy(mergeRes, ele => ele.groupDateVal));
const allRowsKeysData = groupBy(mergeRes, ele => `${ele.groupsKey.toLowerCase()}@${ele.groupDateVal}`);
const allRowsKeysData = groupBy(mergeRes, ele => `${ele.groupsKey}@${ele.groupDateVal}`);
const mergeRows = Object.keys(allRowsKeysData).map(rkey => {
const _groupsKey = (allRowsKeysData[rkey]?.[0]?.groupsKey || '').toLowerCase();
const _groupsKey = allRowsKeysData[rkey]?.[0]?.groupsKey || '';
const sumFields = ['ConfirmOrder', 'SumOrder', 'SumML', 'transactions', 'SumPersonNum'].reduce(
(r, skey) => ({ ...r,
[skey]: allRowsKeysData[rkey].reduce((a, c) => a + c[skey], 0),
@ -457,32 +348,7 @@ export const parseMergeItem = ({traditional, biz}) => {
OrderKPIrates: row.OrderKPIvalue ? fixTo2Decimals((row.SumOrder/row.OrderKPIvalue)*100) : 0,
// ConfirmRatesKPIrates: 0, // todo:
}));
// 合并账户
const allRowsLabelsData = groupBy(mergeRes, ele => `${ele.groupsLabel.replace(/\([^)]*\)/gi, '').toLowerCase()}@${ele.groupDateVal}`);
const mergeLabelsRows = Object.keys(allRowsLabelsData).map(rkey => {
const _groupsLabel = rkey.split('@')[0].toLowerCase();
const _groupsKey = (allRowsLabelsData[rkey]?.[0]?.groupsKey || '').toLowerCase();
const sumFields = [
'ConfirmOrder', 'SumOrder', 'SumML', 'transactions', 'SumPersonNum',
'ConfirmOrderKPIvalue', 'OrderKPIvalue', 'MLKPIvalue',
].reduce(
(r, skey) => ({ ...r,
[skey]: allRowsLabelsData[rkey].reduce((a, c) => a + c[skey], 0),
// [`${skey}_arr`]: [ _groupsKey ? TMapped?.[_groupsKey]?.[skey] || 0 : 0, _groupsKey ? BMapped?.[_groupsKey]?.[skey] || 0 : 0,],
}),
allRowsLabelsData[rkey]?.[0] || {}
);
const pickFields = pick(allRowsLabelsData[rkey], Object.keys(allRowsLabelsData[rkey]).filter(_k => _k.endsWith('KPIvalue') ));
return {...pickFields, ...sumFields, groupsLabel: _groupsLabel, groupsKey: _groupsLabel };
}, {}).map(row => ({...row,
ConfirmRates: row.SumOrder ? fixTo2Decimals((row.ConfirmOrder/row.SumOrder)*100) : 0,
MLKPIrates: row.MLKPIvalue ? fixTo2Decimals((row.SumML/row.MLKPIvalue)*100) : 0,
OrderValue: row.SumOrder ? fixToInt((row.SumML/row.SumOrder)) : 0,
ConfirmOrderKPIrates: row.ConfirmOrderKPIvalue ? fixTo2Decimals((row.ConfirmOrder/row.ConfirmOrderKPIvalue)*100) : 0,
OrderKPIrates: row.OrderKPIvalue ? fixTo2Decimals((row.SumOrder/row.OrderKPIvalue)*100) : 0,
// ConfirmRatesKPIrates: 0, // todo:
}));
return Object.assign(res, { [resKey]: Object.assign({}, mergeItem, { mergeRows, summary, summaryRows, mergeLabelsRows }) });
return Object.assign(res, { [resKey]: Object.assign({}, mergeItem, { mergeRows, summary, summaryRows }) });
}, {});
};

@ -20,19 +20,6 @@ if (!String.prototype.padStart) {
};
}
if (!Object.fromEntries) {
Object.fromEntries = function(entries) {
const obj = {};
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry && entry.length === 2) {
obj[entry[0]] = entry[1];
}
}
return obj;
};
}
export function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
@ -72,14 +59,6 @@ export function formatPercent(number) {
return Math.round(number * 100) + "%";
}
export function formatPercentToFloat(number) {
return parseFloat((number * 100).toFixed(2)) + "%";
}
export function percentToDecimal(number) {
return parseFloat(number) / 100;
}
export function formatDate(date) {
if (isEmpty(date)) {
return "NaN";
@ -264,7 +243,7 @@ export function unique(arr) {
const x = new Set(arr);
return [...x];
}
export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index);
export function getWeek(date) {
// 参数时间戳
const week = moment(date).day();
@ -298,36 +277,17 @@ export function set_array_index(result) {
}
/**
* 数组排序
* 排序
*/
export const sortBy = (key) => {
return (a, b) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0);
};
export const sortDescBy = (key) => {
return (a, b) => (a[key] < b[key]) ? 1 : ((b[key] < a[key]) ? -1 : 0);
};
/**
* Object排序keys
*/
export const sortKeys = (obj) =>
Object.keys(obj)
.sort()
.reduce((a, k2) => ({...a, [k2]: obj[k2]}), {});
/**
* 数组排序, 给定排序数组
* @param {array} items 需要排序的数组
* @param {array} keyName 排序的key
* @param {array} keyOrder 给定排序
* @returns
*/
export const sortArrayByOrder = (items, keyName, keyOrder) => {
return items.sort((a, b) => {
return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]);
});
};
/**
* 合并Object, 递归地
*/
@ -368,7 +328,7 @@ export function merge(...objects) {
*/
export function groupBy(array, callback) {
return array.reduce((groups, item) => {
const key = typeof callback === 'function' ? callback(item) : item[callback];
const key = callback(item);
if (!groups[key]) {
groups[key] = [];
@ -393,20 +353,6 @@ export function pick(object, keys) {
}, {});
}
/**
* 返回对象的副本经过筛选以省略指定的键
* @param {*} object
* @param {string[]} keysToOmit
* @returns
*/
export function omit(object, keysToOmit) {
return Object.fromEntries(
Object.entries(object).filter(
([key]) => !keysToOmit.includes(key)
)
);
}
/**
* 深拷贝
*/
@ -493,7 +439,6 @@ export function objectMapper(input, keyMap) {
if (map) {
let value = input[key];
if (map.transform) value = map.transform(value);
if (typeof map === 'string') mappedObj[map] = value;
mappedObj[map.key || key] = value;
}
}
@ -568,30 +513,3 @@ export function flush(collection) {
export const numberFormatter = (number) => {
return new Intl.NumberFormat().format(number);
};
/**
* @example
* const obj = { a: { b: 'c' } };
* const keyArr = ['a', 'b'];
* getNestedValue(obj, keyArr); // Returns: 'c'
*/
export const getNestedValue = (obj, keyArr) => {
return keyArr.reduce((acc, curr) => {
return acc && acc.hasOwnProperty(curr) ? acc[curr] : undefined;
// return acc && acc[curr];
}, obj);
};
/**
* 计算笛卡尔积
*/
export const cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => {
let result = [];
if(index === arr.length){
return [prefix];
}
arr[index].forEach(item => {
result = result.concat(cartesianProductArray(arr, sep, index+1, prefix ? `${prefix}${sep}${item}` : `${item}`));
});
return result;
};

@ -1,56 +1,142 @@
import { useContext, useEffect } from 'react';
import { Row, Col, Typography, Space, Table, Divider } from 'antd';
import React, { useContext, useEffect } from 'react';
import { Row, Col, Typography, Space, DatePicker, Button, Select, Table, Divider } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { stores_Context } from '../config';
import * as config from '../config';
import { observer } from 'mobx-react';
import 'moment/locale/zh-cn';
import moment from 'moment';
import zhCNlocale from 'antd/es/date-picker/locale/zh_CN';
import { utils, writeFileXLSX } from 'xlsx';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import GroupSelect from './../components/search/GroupSelect';
const AgentGroupCount = () => {
const { customerServicesStore, date_picker_store } = useContext(stores_Context);
const { customerServicesStore } = useContext(stores_Context);
const agentList = customerServicesStore.agentList;
const agentGroupList = customerServicesStore.agentGroupList;
const agentGroupListColumns = customerServicesStore.agentGroupListColumns;
const { inProgress } = customerServicesStore;
const { startDate, endDate, dateType, inProgress } = customerServicesStore;
useEffect(() => {
customerServicesStore.fetchAllAgent();
}, []);
const handleSearchClick = () => {
customerServicesStore.fetchAgentGroupCount();
};
const renderAgentItem = (agent) => {
return (
<Select.Option key={agent.CAV_VEI_SN} value={agent.CAV_VEI_SN}>
{agent.VEI2_CompanyBN}
</Select.Option>
);
};
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"} >
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...customerServicesStore.searchValues,
},
shows: ['agency', 'departureDateType', 'DepartmentList', 'countryArea', 'dates'],
fieldProps: {
DepartmentList: { show_all: true, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
years: { hide_vs: true },
departureDateType: { disabledKeys: ['applyDate'] },
},
<Row gutter={{ sm: 16, lg: 32 }} justify="end">
<Col md={24} lg={8} xxl={8}>
<Select
value={customerServicesStore.selectedAgent}
style={{ width: '95%' }}
showSearch
onSearch={(value) => {
console.log('search:', value);
}}
filterOption={(input, option) => {
return option.children.indexOf(input) > -1;
}}
onChange={(value) => customerServicesStore.selectAgent(value)}
>
<Select.Option key="0" value="">
所有地接社
</Select.Option>
{agentList.map(renderAgentItem)}
</Select>
</Col>
<Col md={24} lg={8} xxl={8}>
<GroupSelect value={customerServicesStore.selectedTeam}
onChange={(value) => customerServicesStore.selectTeam(value)}
style={{ width: '95%' }} show_all={true}
/>
</Col>
<Col md={24} lg={8} xxl={8}>
<Select
value={customerServicesStore.selectedCountry}
style={{ width: '95%' }}
onChange={(value) => customerServicesStore.selectCountry(value)}
>
<Select.Option key="ALL" value="">
所有国家
</Select.Option>
<Select.Option key="china" value="china">
国内
</Select.Option>
<Select.Option key="foreign" value="foreign">
国外
</Select.Option>
</Select>
</Col>
<Col md={24} lg={8} xxl={8}>
<Select
value={dateType}
style={{ width: '95%' }}
onChange={(value) => customerServicesStore.selectDateType(value)}
>
<Select.Option key="startDate" value="startDate">
走团日期
</Select.Option>
<Select.Option key="ConfirmDate" value="ConfirmDate">
成团日期
</Select.Option>
</Select>
</Col>
<Col md={24} lg={8} xxl={8}>
<DatePicker.RangePicker
format={config.DATE_FORMAT}
locale={zhCNlocale}
allowClear={false}
value={[startDate, endDate]}
onChange={(dates) => {
customerServicesStore.selectDateRange(dates[0], dates[1]);
}}
onSubmit={(_err, obj, form) => {
customerServicesStore.setSearchValues(obj, form);
customerServicesStore.fetchAgentGroupCount();
ranges={{
'本周': [moment().startOf('week'), moment().endOf('week')],
'上周': [moment().startOf('week').subtract(7, 'days'), moment().endOf('week').subtract(7, 'days')],
'本月': [moment().startOf('month'), moment().endOf('month')],
'上个月': [
moment().subtract(1, 'months').startOf('month'),
moment(new Date()).subtract(1, 'months').endOf('month'),
],
'近30天': [moment().subtract(30, 'days'), moment()],
'近三个月': [moment().subtract(2, 'month').startOf('month'), moment().endOf('month')],
'今年': [moment().startOf('year').subtract(1, 'month'), moment().endOf('year').subtract(1, 'month')],
'去年': [
moment().subtract(1, 'year').startOf('year').subtract(1, 'month'),
moment().subtract(1, 'year').endOf('year').subtract(1, 'month'),
],
}}
/>
</Col>
<Col md={24} lg={8} xxl={8}>
<Button
type="primary"
icon={<SearchOutlined />}
loading={inProgress}
onClick={() => {
handleSearchClick();
}}
>
统计
</Button>
</Col>
</Row>
<Row>
<Col span={24}>
<Typography.Title level={3}>地接社团信息</Typography.Title>
<Divider orientation="right" plain>
<TableExportBtn label={'地接社团信息'} {...{ columns: agentGroupListColumns, dataSource: agentGroupList }} />
</Divider>
<Table
sticky
id="agentGroupList"
dataSource={agentGroupList}
columns={agentGroupListColumns}
@ -58,8 +144,20 @@ const AgentGroupCount = () => {
rowKey={(record) => record.key}
loading={inProgress}
pagination={false}
scroll={{ x: 1000 }}
scroll={{ x: '100%' }}
/>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(
document.getElementById('agentGroupList').getElementsByTagName('table')[0]
);
writeFileXLSX(wb, '地接社团信息.xlsx');
}}
>
导出excel
</a>
</Divider>
</Col>
</Row>
</Space>

@ -1,72 +1,111 @@
import { useContext, useEffect } from 'react';
import { Row, Col, Typography, Space, Table, List } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import { NavLink, useParams } from 'react-router-dom';
import React, {useContext, useEffect} from 'react';
import {Row, Col, Typography, Space, DatePicker, Button, Select, Table, List} from 'antd';
import {
SearchOutlined,
} from '@ant-design/icons';
import {stores_Context} from '../config';
import * as config from "../config";
import {observer} from 'mobx-react';
import { NavLink, useParams } from "react-router-dom";
import 'moment/locale/zh-cn';
import SearchForm from './../components/search/SearchForm';
import moment from "moment";
import zhCNlocale from 'antd/es/date-picker/locale/zh_CN';
import GroupSelect from './../components/search/GroupSelect';
const AgentGroupList = () => {
const { agentId } = useParams();
const { customerServicesStore, date_picker_store } = useContext(stores_Context);
const {agentId} = useParams();
const { customerServicesStore } = useContext(stores_Context);
useEffect(() => {
customerServicesStore.fetchGroupListByAgentId(agentId);
}, []);
customerServicesStore.fetchGroupListByAgentId(agentId);
}, []);
const groupList = customerServicesStore.groupList;
const groupListColumns = customerServicesStore.groupListColumns;
const { startDate, endDate, dateType, inProgress } = customerServicesStore;
const {startDate, endDate, dateType, inProgress} = customerServicesStore;
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={{ md: 24 }} justify="end">
<Col span={24}>
return (
<>
<Space direction="vertical" style={{width: '100%'}}>
<Row gutter={{md: 24}} justify="end">
<Col span={8}>
<NavLink to={'/agent/group/count'}>返回</NavLink>
</Col>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...customerServicesStore.searchValues,
},
shows: ['departureDateType', 'DepartmentList', 'dates'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
departureDateType: { disabledKeys: ['applyDate'] },
},
}}
onSubmit={(_err, obj, form, str) => {
customerServicesStore.setSearchValues(obj, form);
customerServicesStore.fetchGroupListByAgentId(agentId);
}}
<Col span={4}>
<GroupSelect value={customerServicesStore.selectedTeam}
onChange={(value) => customerServicesStore.selectTeam(value)}
style={{ width: '95%' }} show_all={true}
/>
</Col>
</Col>
<Col span={4}>
<Select value={dateType} style={{ width: "95%" }} onChange={(value) => customerServicesStore.selectDateType(value)}>
<Select.Option key="1" value="startDate">
走团日期
</Select.Option>
<Select.Option key="2" value="ConfirmDate">
成团日期
</Select.Option>
</Select>
</Col>
<Col span={4}>
<DatePicker.RangePicker
format={config.DATE_FORMAT} locale={zhCNlocale}
allowClear={false}
value={[startDate, endDate]}
onChange={(dates) => {customerServicesStore.selectDateRange(dates[0], dates[1]);}}
ranges={{
'本周': [moment().startOf('week'), moment().endOf('week')],
'上周': [moment().startOf('week').subtract(7, 'days'), moment().endOf('week').subtract(7, 'days')],
'本月': [moment().startOf('month'), moment().endOf('month')],
'上个月': [moment().subtract(1, 'months').startOf('month'), moment(new Date()).subtract(1, 'months').endOf('month')],
'近30天': [moment().subtract(30, 'days'), moment()],
'近三个月': [moment().subtract(2, 'month').startOf('month'), moment().endOf('month')],
'今年': [moment().startOf('year').subtract(1, 'month'), moment().endOf('year').subtract(1, 'month')],
'去年': [moment().subtract(1, 'year').startOf('year').subtract(1, 'month'), moment().subtract(1, 'year').endOf('year').subtract(1, 'month')],
}}
/>
</Col>
<Col span={4}>
<Button
type="primary"
icon={<SearchOutlined />}
loading={inProgress}
onClick={() => {
customerServicesStore.fetchGroupListByAgentId(agentId);
}}>
统计
</Button>
</Col>
</Row>
<Row>
<Col span={24}>
<Typography.Title level={3}>{customerServicesStore.agentCompany}</Typography.Title>
<Table
sticky
dataSource={groupList}
columns={groupListColumns}
size="small"
rowKey={(record) => record.key}
loading={inProgress}
pagination={false}
scroll={{ x: 1000 }}
scroll={{ x: "100%" }}
expandable={{
expandedRowRender: (record) => (
<List itemLayout="horizontal">
<List
itemLayout="horizontal"
>
<List.Item>
<List.Item.Meta title="经过城市" description={record.PassCity} />
<List.Item.Meta
title='经过城市'
description={record.PassCity}
/>
</List.Item>
<List.Item>
<List.Item.Meta title="评论内容" description={record.ECI_Content} />
<List.Item.Meta
title='评论内容'
description={record.ECI_Content}
/>
</List.Item>
</List>
),
@ -74,9 +113,9 @@ const AgentGroupList = () => {
/>
</Col>
</Row>
</Space>
</>
);
</Space>
</>
);
};
export default observer(AgentGroupList);

@ -9,7 +9,6 @@ import BillTypeSelect from "../components/search/BillTypeSelect";
import GroupSelect from "../components/search/GroupSelect";
import Business_unit from "../components/search/BusinessSelect";
import DatePickerCharts from "../components/search/DatePickerCharts";
import SearchForm from './../components/search/SearchForm';
import { Line, Pie } from "@ant-design/charts";
import * as comm from "../utils/commons";
import * as config from "../config";
@ -267,32 +266,36 @@ const Credit_card_bill = () => {
return (
<div>
<Row gutter={16} style={{ margin: '-16px -8px', position: 'relative', top: 0, zIndex: 10 }}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
// ...sale_store.searchValues,
},
shows: ['billtype', 'businessUnits', 'dates', ],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
years: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
financial_store.setSearchValues(obj, form);
financial_store.get_credit_card_bills();
financial_store.get_credit_card_bills_by_type();
financial_store.set_bill_filtered(false);
}}
/>
</Col>
<Row>
<Col span={11}>
<h2>信用卡账单</h2>
</Col>
<Col span={10}>
<Row>
<Col span={12}>
<BillTypeSelect store={bill_type_data} show_all={true} />
<Business_unit store={credit_card_data} show_all={true} />
</Col>
<Col span={12}>
<DatePickerCharts />
</Col>
</Row>
</Col>
<Col span={1}></Col>
<Col span={2}>
<Button
type="primary"
icon={<SearchOutlined />}
size="large"
loading={credit_card_data.loading}
onClick={() => {
financial_store.get_credit_card_bills();
financial_store.get_credit_card_bills_by_type();
financial_store.set_bill_filtered(false);
}}>
统计
</Button>
</Col>
</Row>
<Row>
@ -312,7 +315,7 @@ const Credit_card_bill = () => {
</span>
}
key="summarized_data">
<Table id="table_by_type" dataSource={credit_card_bills_by_type.dataSource} columns={credit_card_bills_by_type.columns} pagination={false} size="small" scroll={{x: 600}} />
<Table id="table_by_type" dataSource={credit_card_bills_by_type.dataSource} columns={credit_card_bills_by_type.columns} pagination={false} size="small" />
<Divider orientation="right">
<a
onClick={() => {
@ -331,7 +334,7 @@ const Credit_card_bill = () => {
</span>
}
key="detail_data">
<Table id="table_by_detail" dataSource={credit_card_bills.dataSource} columns={credit_card_bills.columns} pagination={false} onChange={credit_card_data.set_table_handleChange} size="small" scroll={{x: 800}} />
<Table id="table_by_detail" dataSource={credit_card_bills.dataSource} columns={credit_card_bills.columns} pagination={false} onChange={credit_card_data.set_table_handleChange} size="small" />
<Divider orientation="right">
<a
onClick={() => {

@ -1,181 +0,0 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Row, Col, Table, Space, Typography, Divider } from 'antd';
import SearchForm from './../components/search/SearchForm';
import { VSTag, TableExportBtn } from './../components/Data';
import { CruiseAgency } from './../libs/ht';
const { Text } = Typography;
export default observer((props) => {
const { date_picker_store: searchFormStore } = useContext(stores_Context);
const { HotelCruiseStore, date_picker_store } = useContext(stores_Context);
const { loading, dataSource, summaryRow } = HotelCruiseStore.cruise;
const { formValues, siderBroken } = searchFormStore;
const tableSorter = (a, b, colName) => a[colName] - b[colName];
const tableExportDataRow = (col1, col2) => [col1, col2].filter((r) => r).join(' VS ');
const tableProps = {
size: 'small',
bordered: true,
pagination: false,
columns: [
{ title: '产品',
sorter: (a, b) => a.ProductName.localeCompare(b.ProductName, 'zh-CN'),children: [{ title: summaryRow.ProductName, dataIndex: 'ProductName', key: 'ProductName' }] },
{
title: '房间数',
sorter: (a, b) => tableSorter(a, b, 'TotalNum'),
children: [
{
title: (
<>
<Space direction={'vertical'}>
<Text strong>
{summaryRow.TotalNum}
{summaryRow.TotalNumPercent ? <Text type="secondary"> VS {summaryRow.CPTotalNum}</Text> : null}
</Text>
{summaryRow.TotalNumPercent && <VSTag diffPercent={summaryRow.TotalNumPercent} />}
</Space>
</>
),
titleX: tableExportDataRow(summaryRow.TotalNum, summaryRow.CPTotalNum),
dataExport: (v, r) => tableExportDataRow(r.TotalNum, r.CPTotalNum),
dataIndex: 'TotalNum',
key: 'TotalNum',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPTotalNum ? <Text type="secondary"> VS {r.CPTotalNum}</Text> : null}
</Text>
{r.CPTotalNum && <VSTag diffPercent={r.TotalNumPercent} />}
</Space>
</>
),
},
],
},
{
title: '人数',
sorter: (a, b) => tableSorter(a, b, 'TotalPersonNum'),
children: [
{
title: (
<>
<Space direction={'vertical'}>
<Text strong>
{summaryRow.TotalPersonNum}
{summaryRow.TotalPersonNumPercent ? <Text type="secondary"> VS {summaryRow.CPTotalPersonNum}</Text> : null}
</Text>
{summaryRow.TotalPersonNumPercent && <VSTag diffPercent={summaryRow.TotalPersonNumPercent} />}
</Space>
</>
),
titleX: tableExportDataRow(summaryRow.TotalPersonNum, summaryRow.CPTotalPersonNum),
dataExport: (v, r) => tableExportDataRow(r.TotalPersonNum, r.CPTotalPersonNum),
dataIndex: 'TotalPersonNum',
key: 'TotalPersonNum',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPTotalPersonNum ? <Text type="secondary"> VS {r.CPTotalPersonNum}</Text> : null}
</Text>
{r.CPTotalNum && <VSTag diffPercent={r.TotalPersonNumPercent} />}
</Space>
</>
),
},
],
},
{
title: '总利润',
sorter: (a, b) => tableSorter(a, b, 'TotalProfit'),
children: [
{
title: (
<>
<Space direction={'vertical'}>
<Text strong>
{summaryRow.TotalProfit}
{summaryRow.TotalProfitPercent ? <Text type="secondary"> VS {summaryRow.CPTotalProfit}</Text> : null}
</Text>
{summaryRow.TotalProfitPercent && <VSTag diffPercent={summaryRow.TotalProfitPercent} />}
</Space>
</>
),
titleX: tableExportDataRow(summaryRow.TotalProfit, summaryRow.CPTotalProfit),
dataExport: (v, r) => tableExportDataRow(r.TotalProfit, r.CPTotalProfit),
dataIndex: 'TotalProfit',
key: 'TotalProfit',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPTotalProfit ? <Text type="secondary"> VS {r.CPTotalProfit}</Text> : null}
</Text>
{r.CPTotalNum && <VSTag diffPercent={r.TotalProfitPercent} />}
</Space>
</>
),
},
],
},
// { title: '', dataIndex: '', key: '' },
// { title: '', dataIndex: '', key: '' },
// { title: '', dataIndex: '', key: '' },
],
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={24} xxl={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...HotelCruiseStore.searchValues,
},
// 'countryArea',
shows: [
'DepartmentList',
'orderStatus',
'dates',
'keyword',
'cruiseDirection',
'agency',
'cruiseBookType',
'country', // 'roomsRange', 'personRange'
],
sort: { keyword: 101, agency: 110, cruiseDirection: 102, country: 104 },
fieldProps: {
keyword: { placeholder: '产品名', col: 4 },
DepartmentList: { show_all: true, mode: 'multiple' },
orderStatus: { show_all: true },
cruiseDirection: { show_all: true },
agency: { defaultOptions: CruiseAgency, autoGet: false },
// years: { hide_vs: false },
},
}}
onSubmit={(_err, obj, form) => {
HotelCruiseStore.setSearchValues(obj, form);
HotelCruiseStore.getCruiseData(obj);
}}
/>
</Col>
</Row>
<section>
<Divider orientation="right" >
<TableExportBtn label={'游船'} {...{ columns: tableProps.columns, dataSource }} />
</Divider>
<Table {...tableProps} {...{ loading, dataSource }} rowKey={(record) => record.ProductName} />
</section>
</>
);
});

@ -22,24 +22,11 @@ class Dashboard extends Component {
<Col span={24}>
<ExchangeRate />
</Col>
<Col {...{
gutter: { xs: 8, sm: 8, md: 16, lg: 16 },
lg: { span: 12 },
md: { span: 24 },
sm: { span: 24 },
xs: { span: 24 },
}}>
<Col span={12}>
<OrdersTempTable />
</Col>
<Col {...{
gutter: { xs: 8, sm: 8, md: 16, lg: 16 },
lg: { span: 12 },
md: { span: 24 },
sm: { span: 24 },
xs: { span: 24 },
}}>
<Col span={12}>
<MobileDeal />
</Col>
</Row>

@ -1,736 +0,0 @@
import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { useParams, useLocation } from 'react-router-dom';
import { stores_Context } from '../config';
import { Row, Col, Spin, Table, Select, Typography, Card, Button, Space, Divider, Alert } from 'antd';
import { cloneDeep, groupBy, isEmpty, omit, pick, sortBy, unique, cartesianProductArray } from '../utils/commons';
import { dataFieldAlias, pivotBy } from '../libs/ht';
import SearchForm from '../components/search/SearchForm';
import { Line } from '@ant-design/plots';
import DateGroupRadio from '../components/DateGroupRadio';
import { TableExportBtn } from './../components/Data';
const { Text } = Typography;
const filterFields = [
{ key: 'SourceType', label: '来源类型' },
{ key: 'productType', label: '产品类型' },
{ key: 'country', label: '国籍' },
{ key: 'CLI_NO', label: '线路' },
// { key: 'destination', label: '' },
{ key: 'COLI_LineClass', label: '页面渠道' },
{ key: 'guestGroupType', label: '客群类别' },
{ key: 'travelMotivation', label: '出行目的' },
{ key: 'startMonth', label: '出行日期-月份' },
{ key: 'startYearMonth', label: '出行日期-年月' },
{ key: 'applyMonth', label: '预订日期-月份' },
{ key: 'applyYearMonth', label: '预订日期-年月' },
{ key: 'operatorName', label: '顾问' },
{ key: 'WebCode', label: '来源站点' },
{ key: 'IsOld_txt', label: '是否老客户' },
{ key: 'isCusCommend_txt', label: '是否老客户推荐' },
{ key: 'hasOld_txt', label: '老客户(推荐)' },
{ key: 'HotelStar', label: '酒店星级' },
{ key: 'destinationCountry', label: '目的地国籍' },
{ key: 'destinations', label: '目的地城市' },
{ key: 'RTXF_WB_range', label: '人天消费(外币)' },
{ key: 'PPPriceRange', label: '人均天/单(外币)' },
// { key: 'unitPPPriceRange', label: '()' },
// { key: 'SumML_ctxt1', label: '[1W]' },
// { key: 'SumML_ctxt1_5', label: '[1.5W]' },
// { key: 'SumML_ctxt2', label: '[2W]' },
// { key: 'SumML_ctxt3', label: '[3W]' },
// { key: 'SumML_ctxt4', label: '[4W]' },
{ key: 'SumML_ctxt', label: '毛利' },
];
const filterFieldsMapped = filterFields.reduce((r, v) => ({ ...r, [v.key]: v }), {});
/** 预设的选项, 只有行 */
const quickOptions = [
{ label: ' 来源站点 ', fields: [['WebCode'], []] },
{ label: '[ 产品×客群 ]', fields: [['productType', 'guestGroupType'], []] },
{ label: '[ 国籍×客群 ]', fields: [['country', 'guestGroupType'], []] },
{ label: '[ 客群×目的地国籍 ]', fields: [['guestGroupType', 'destinationCountry'], []] },
// { label: '[ × ]×[ ]', fields: [['country', 'guestGroupType'], []] },
];
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const pageSetting = {
orders: {
xField: 'applyDate',
yField: 'SumOrder',
tableColumns: [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' },
// { key: 'ConfirmOrder', title: '', dataIndex: 'ConfirmOrder', width: '5em' },
// { key: 'ConfirmPersonNum', title: '', dataIndex: 'ConfirmPersonNum', width: '5em' },
// { key: 'ConfirmRates', title: '', dataIndex: 'ConfirmRates_txt', width: '5em' },
// { key: 'SumML', title: '', dataIndex: 'SumML_txt', width: '5em' },
],
childrenColumns: [
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'tourdays', title: '团天数', dataIndex: 'tourdays', width: '5em' },
{ key: 'confirmTourdays', title: '✅团天数', dataIndex: 'confirmTourdays', width: '5em' },
{ key: 'SumPersonNum', title: '人数', dataIndex: 'SumPersonNum', width: '5em' },
{ key: 'ConfirmPersonNum', title: '✅人数', dataIndex: 'ConfirmPersonNum', width: '5em' },
],
searchInitial: { DateType: { key: 'applyDate', value: 'applyDate', label: '提交日期' } },
},
trade: {
xField: 'confirmDate',
yField: 'SumML',
yFieldAlias: 'SumML_txt',
tableColumns: [
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt
],
childrenColumns: [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
// { key: 'ResumeOrder', title: '', dataIndex: 'ResumeOrder', width: '5em', render: (_, r) => `${r.ResumeConfirmOrder}/${r.ResumeOrder}` },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'OrderValue', title: '单个订单价值', dataIndex: 'OrderValue', width: '5em' },
{ key: 'PPPriceRange', title: '人均天(外币)', dataIndex: 'PPPriceRange', width: '5em' },
{ key: 'AvgPPPrice', title: '人均天/单(外币)-平均', dataIndex: 'AvgPPPrice', width: '5em' },
// { key: 'unitPPPrice', title: '()', dataIndex: 'unitPPPrice', width: '5em' },
// { key: 'unitPPPriceRange', title: '()', dataIndex: 'unitPPPriceRange', width: '5em' },
{ key: 'confirmDays', title: '成团周期(天)', dataIndex: 'confirmDays', width: '5em' },
{ key: 'applyDays', title: '预定周期(天)', dataIndex: 'applyDays', width: '5em' },
{ key: 'tourdays', title: '团天数', dataIndex: 'tourdays', width: '5em' },
],
searchInitial: { DateType: { key: 'confirmDate', value: 'confirmDate', label: '确认日期' } },
},
};
export default observer((props) => {
const { page } = useParams();
const { pathname } = useLocation();
const { date_picker_store: searchFormStore, orders_store, DataPivotStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { originData } = DataPivotStore.detailData[page];
const { xField: defaultDateType, yField: defaultValKey, yFieldAlias, tableColumns, childrenColumns, searchInitial } = pageSetting[page];
const [curXfield, setCurXfield] = useState(defaultDateType);
const [loading, setLoading] = useState(false);
const [rawData, setRawData] = useState(originData || []);
const [dataBeforePick, setDataBeforePick] = useState([]);
const [dataBeforeXChange, setDataBeforeXChange] = useState([]);
const [dataSource, setDataSource] = useState([]);
// const [dataSourceMapped, setDataSourceMapped] = useState({});
const [pivotRow, setPivotRow] = useState({});
const [pivotRowDataSource, setPivotRowDataSource] = useState([]);
const [pivotDataSource, setPivotDataSource] = useState([]);
const [pivotTableDataSource, setPivotTableDataSource] = useState([]);
const [pivotTableColumnSummary, setPivotTableColumnSummary] = useState({}); // ,
const [pivotDateColumns, setPivotDateColumns] = useState([[], []]);
const [pivotDateColumnsValues, setPivotDateColumnsValues] = useState([[], []]);
const [showPassCountryTips, setShowPassCountryTips] = useState(false);
useEffect(() => {
calcDataByDate();
resetX();
resetItemFilter();
return () => {};
}, [pivotDateColumns]);
useEffect(() => {
if (lineChartX === 'day') {
setDataBeforeXChange(dataSource);
}
return () => {};
}, [dataSource]);
useEffect(() => {
setCurXfield(formValuesToSub.DateType);
setLineConfig({...lineConfig, xField: formValuesToSub.DateType});
return () => {};
}, [formValues]);
const detailRefresh = async (obj) => {
setLoading(true);
DataPivotStore.getDetailData({
...(obj || formValuesToSub),
}, page).then((resData) => {
setLoading(false);
setRawData(resData);
calcDataByDate(resData);
resetX();
resetItemFilter();
});
};
/**
* 走势的数据
* 汇总
*/
const calcDataByDate = (_rawData) => {
// console.log(';;;;;', pivotDateColumns);
const { data, columnValues, summaryRows, summaryColumns, pivotKeys, summaryMix } = pivotBy(_rawData || rawData, [].concat(pivotDateColumns, [curXfield]));
// console.log('data====', data, '\ncolumnValues', columnValues, '\nsummaryRows', summaryRows, '\nsummaryColumns', summaryColumns, '\nsummaryMix', summaryMix);
setShowPassCountryTips(pivotKeys.includes('destinationCountry'));
setDataBeforePick(data.sort(sortBy(curXfield)));
// 线,
// const sortMixData = cloneDeep(summaryMix).sort(sortBy(defaultValKey)).reverse();
// setPivotDataSource(sortMixData);
// const sortRank = sortMixData.map(ele => ele.rowLabel);
// 线
setDataSource(data.sort(sortBy(curXfield)));
//
// .map(ele => ({...ele, rowLabel: `${sortRank.indexOf(ele.rowLabel)+1}).${ele.rowLabel}` }))
//
const sortRowData = cloneDeep(summaryRows).sort(sortBy(defaultValKey)).reverse();
setPivotTableDataSource(sortRowData);
setPivotRow({});
setPivotRowDataSource([]);
//
const _col1 = pivotDateColumns[1][0] || '';
const _sortByDateOrVal = (_col1.includes('Month') || _col1.includes('Date')) ? _col1 : defaultValKey;
let sortColData = summaryColumns.sort(sortBy(_sortByDateOrVal));
sortColData = _sortByDateOrVal === defaultValKey ? sortColData.reverse() : sortColData;
const colDataMapped = isEmpty(pivotDateColumns[1]) ? sortColData[0] : sortColData.reduce((r, v) => ({...r, [v[pivotDateColumns[1][0]]]: v}), {});
setPivotTableColumnSummary(colDataMapped);
//
const _r = (pivotDateColumns[0].map(eleR => unique(sortRowData.map(ele => ele[eleR]))));
const _c = (pivotDateColumns[1].map(eleC => unique(sortColData.map(ele => ele[eleC]))));
// console.log('_r', _r, '_c', _c);
setPivotDateColumnsValues([_r, _c, columnValues[2]]);
};
const line_config = {
// data: dataSource,
padding: 'auto',
xField: curXfield,
yField: defaultValKey,
seriesField: 'rowLabel',
// xAxis: {
// type: 'timeCat',
// },
yAxis: {
min: 0,
maxTickInterval: 5,
},
meta: {
...cloneDeep(dataFieldAlias),
},
// smooth: true,
label: {}, //
legend: {
position: 'right-top',
// title: {
// text: ' ' + dataSource.reduce((a, b) => a + b.SumOrder, 0),
// },
itemMarginBottom: 12, //
},
tooltip: {
customItems: (originalItems) => {
return originalItems.map(ele => ({...ele, valueR: ele.data[defaultValKey]})).sort(sortBy('valueR')).reverse();
},
},
};
const [lineConfig, setLineConfig] = useState(cloneDeep(line_config));
//
// const [leftFields, setLeftFields] = useState(filterFields);
const [rightFields, setRightFields] = useState(filterFields);
const [rowFields, setRowFields] = useState([]);
const [columnFields, setColumnFields] = useState([]);
const [rowSelection, setRowSelection] = useState([]);
const [columnSelection, setColumnSelection] = useState();
//
const quickOpt = (i) => {
const { fields: pivotFields } = quickOptions[i];
const [row, col] = pivotFields;
setRowSelection(Object.values(pick(filterFieldsMapped, row)));
!isEmpty(col) ? setColumnSelection(filterFieldsMapped[col[0]]) : setColumnSelection([]);
setRowFields(row);
setColumnFields(col);
resetItemFilter();
setPivotDateColumns(pivotFields);
};
const resetFields = () => {
setRowFields([]);
setColumnFields([]);
setRowSelection([]);
setColumnSelection([]);
resetItemFilter();
setPivotDateColumns([[], []]);
};
const handleRowsPick = (v) => {
setRowSelection(v);
const pickKeys = v.map((ele) => ele.key);
setRowFields(pickKeys);
// const leftFieldsMapped = leftFields.reduce((r, v) => ({ ...r, [v.key]: v }), {});
const _left = omit(filterFieldsMapped, pickKeys);
setRightFields(isEmpty(v) ? filterFields : Object.values(_left));
// setPivotDateColumns([].concat(pickKeys, columnFields));
setPivotDateColumns([pickKeys, columnFields]);
resetItemFilter();
};
const handleColsPick = (val) => {
setColumnSelection(val);
const pickKeys = isEmpty(val) ? [] : Array.isArray(val) ? val.map((ele) => ele.key) : [val.key];
setColumnFields(pickKeys);
const afterLeft = Object.values(omit(filterFieldsMapped, rowFields));
setRightFields(afterLeft); //
// const rightFieldsMapped = rightFields.reduce((r, v) => ({ ...r, [v.key]: v }), {});
// const _left = omit(rightFieldsMapped, pickKeys);
// setRightFields(isEmpty(val) ? afterLeft : Object.values(_left)); //
// setPivotDateColumns([].concat(rowFields, pickKeys));
setPivotDateColumns([rowFields, pickKeys]);
resetItemFilter();
};
//
const [rowsItemValues, setRowsItemValues] = useState();
const [columnsItemValues, setColumnsItemValues] = useState();
const [rowsFilter, setRowsFilter] = useState();
const [columnsFilter, setColumnsFilter] = useState();
const resetItemFilter = () => {
setRowsItemValues(null);
setColumnsItemValues(null);
setRowsFilter(null);
setColumnsFilter(null);
};
const handleFieldsItemPick = (v, columnsIndex, columnsName, actionSeries) => {
const _curFilter = { [columnsName]: actionSeries === 'row' ? v : [v] };
// console.log('handleFieldsItemPick', v, columnsIndex, columnsName, actionSeries, _curFilter);
const _rowsF = actionSeries === 'row' ? { ...rowsFilter, ..._curFilter } : rowsFilter;
const _columnsF = actionSeries === 'col' ? { ...columnsFilter, ..._curFilter } : columnsFilter;
actionSeries === 'row' ? setRowsFilter(_rowsF) : setColumnsFilter(_columnsF);
const currentFilterMerge = {
rows: _rowsF || {},
columns: _columnsF || {},
};
setRowsItemValues(currentFilterMerge.rows);
setColumnsItemValues(currentFilterMerge.columns);
const rowsFilterFields = Object.keys(currentFilterMerge.rows).filter((ele) => currentFilterMerge.rows[ele].length);
const dataMappedByRows = groupBy(dataBeforePick, (row) => rowsFilterFields.map((kk) => `${row[kk]}`).join('=@='));
const rowsFilterKey = isEmpty(rowsFilterFields)
? []
: cartesianProductArray(
Object.values(currentFilterMerge.rows)
.map((kv) => kv.map((kf) => kf.key))
.filter((s) => s.length),
'=@='
);
const afterRowsFilter = isEmpty(rowsFilterFields) ? dataBeforePick : rowsFilterKey.reduce((r, _key) => r.concat(dataMappedByRows[_key]), []);
const columnsFilterFields = Object.keys(currentFilterMerge.columns).filter((ele) => currentFilterMerge.rows[ele].length);
const allFilterValues = [].concat(
rowsFilterFields.reduce((r, v) => r.concat(currentFilterMerge.rows[v]), []),
columnsFilterFields.reduce((r, v) => r.concat(currentFilterMerge.columns[v]), [])
);
const dataMapped = groupBy(afterRowsFilter, (row) => row[columnsName]);
const pickData = isEmpty(v) ? afterRowsFilter : Array.isArray(v) ? v.reduce((r, v) => r.concat(dataMapped[v.value]), []) : dataMapped[v.value];
setDataSource(allFilterValues.length === 0 ? dataBeforePick : pickData.sort(sortBy(curXfield)));
resetX();
};
//
const [lineChartX, setLineChartX] = useState('day');
const [avgLine1, setAvgLine1] = useState(0);
const orderCountDataMapper = { data1: 'data1', data2: undefined };
const orderCountDataFieldMapper = { 'dateKey': curXfield, 'valueKey': defaultValKey, 'seriesKey': 'rowLabel', _f: 'sum' };
const resetX = () => {
setLineChartX('day');
setAvgLine1(0);
};
const onChangeXDateFieldGroup = (value, data, avg1) => {
const { xField, yField, seriesField } = lineConfig;
const groupByDate = data.reduce((r, v) => {
(r[v[xField]] || (r[v[xField]] = [])).push(v);
return r;
}, {});
const _data = Object.keys(groupByDate).reduce((r, _d) => {
const xAxisGroup = groupByDate[_d].reduce((a, v) => {
(a[v[seriesField]] || (a[v[seriesField]] = [])).push(v);
return a;
}, {});
Object.keys(xAxisGroup).map((_group) => {
const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row[yField], 0);
r.push({ ...xAxisGroup[_group][0], [yField]: summaryVal });
return _group;
});
return r;
}, []);
// .map((row) => ({ [xField]: row[xField], [yField]: row[yField], [seriesField]: row[seriesField], rowX: row.dateRange[0] }));
setLineChartX(value);
setDataSource(_data);
setAvgLine1(avg1);
};
const targetTableProps = {
loading: false,
// sticky: true,
scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
...pivotDateColumns[0].map((ele) => ({ key: ele, title: filterFieldsMapped[ele].label, dataIndex: ele, width: '6em', fixed: 'left' })),
...(isEmpty(pivotDateColumns[1]) ? [].concat(cloneDeep(tableColumns), childrenColumns) : tableColumns),
...pivotDateColumns[1].map((ele) => ({
key: ele,
title: filterFieldsMapped[ele].label,
align: 'left',
className: 'p-s1',
children: cloneDeep(pivotDateColumnsValues[1][0] || []).map((col) => ({
key: col,
title: `${col || '(空)'}: ${pivotTableColumnSummary[col]?.[defaultValKey]}`,
dataIndex: ['columns', col, defaultValKey || yFieldAlias],
width: '6em',
})),
})),
],
};
const detailsTableProps = {
loading: false,
// sticky: true,
scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
{
title: '订单号',
dataIndex: 'o_id',
key: 'o_id',
},
{
title: '来源站点',
dataIndex: 'WebCode',
key: 'WebCode',
},
{
title: '页面渠道',
dataIndex: 'COLI_LineClass',
key: 'COLI_LineClass',
},
{
title: '来源类型',
dataIndex: 'SourceType',
key: 'SourceType',
},
{
title: '客群类别',
dataIndex: 'guestGroupType',
key: 'guestGroupType',
},
{
title: '成行',
dataIndex: 'orderState',
key: 'orderState',
render: (text, record) => <span>{text === '1' ? '是' : '否'}</span>,
sorter: (a, b) => b.orderState - a.orderState,
},
// {
// title: "(//)",
// dataIndex: "COLI_PersonNum",
// key: "COLI_PersonNum",
// render: (text, record) => (
// <span>
// {record.COLI_PersonNum}/{record.COLI_ChildNum}/{record.COLI_BabyNum}
// </span>
// ),
// },
{
title: '预计利润',
dataIndex: 'ML',
key: 'ML',
},
{
title: '预定时间',
dataIndex: 'applyDate',
key: 'applyDate',
},
{
title: '出发日期',
dataIndex: 'startDate',
key: 'startDate',
},
],
};
return (
<div key={pathname}>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...orders_store.searchValues,
...searchInitial,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'], // 'country'
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
detailRefresh(obj);
}}
/>
</Col>
</Row>
{/* extra={<Button type="link">重置</Button>} */}
<Card
size={'small'}
title={'透视选项'}
extra={
<Row gutter={3}>
<Col flex={'0 0 auto'}>
<Text type={'secondary'}>预设:</Text>
</Col>
{quickOptions.map((ele, elei) => (
<Col key={ele.label}>
<Button key={ele.label} onClick={() => quickOpt(elei)} type={'primary'} ghost size={'small'}>
{ele.label}
</Button>
</Col>
))}
<Col>
<Button key={'reset-quick'} onClick={resetFields} type={'primary'} ghost size={'small'}>
重置
</Button>
</Col>
</Row>
}
>
<Row gutter={16}>
<Col span={24}>
{/* todo: 拖拽的操作 */}
{/* <div className='p-s1' style={{border: '1px solid #d9d9d9 '}}>
{filterFields.map((tag) => (
<Tag key={tag.key} checked={selectedTags.indexOf(tag.key) > -1} onChange={(checked) => handleChange(tag.key, checked)} color={'orange'}>
{tag.label}
</Tag>
))}
</div> */}
</Col>
{/* 行: */}
<Col md={12} sm={24} xs={24}>
<Row gutter={8} align={'middle'}>
<Col flex={'4em'} align={'end'}>
<Text strong>: </Text>
</Col>
<Col flex={'auto'}>
<Select
labelInValue
mode={'multiple'}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleRowsPick(v)}
value={rowSelection}
maxTagCount={2}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{filterFields.map((ele) => (
<Select.Option key={ele.key} value={ele.key}>
{ele.label}
</Select.Option>
))}
</Select>
</Col>
<Col span={24}>
{rowFields.length > 0
? cloneDeep(pivotDateColumnsValues)[0]
// .slice(0, rowFields.length)
.map((_colArr, _colIndex) => (
<Row gutter={8} key={filterFieldsMapped[pivotDateColumns[0][_colIndex]]?.key || _colIndex}>
<Col flex={'5em'} align={'end'}>
<Text strong>{filterFieldsMapped[pivotDateColumns[0][_colIndex]]?.label}: </Text>
</Col>
<Col flex={'auto'}>
<Select
size={'small'}
labelInValue
// mode={_colIndex === 0 ? 'multiple' : null}
mode={'multiple'}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleFieldsItemPick(v, _colIndex, pivotDateColumns[0][_colIndex], 'row')}
value={rowsItemValues?.[pivotDateColumns[0][_colIndex]]}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{pivotDateColumnsValues[0][_colIndex].map((ele) => (
<Select.Option key={ele} value={ele}>
{ele}
</Select.Option>
))}
</Select>
</Col>
</Row>
))
: null}
</Col>
</Row>
</Col>
{/* 列: */}
<Col md={12} sm={24} xs={24} style={{ borderLeft: '1px solid #d9d9d9' }}>
<Row gutter={8} align={'middle'}>
<Col flex={'4em'} align={'end'}>
<Text strong>: </Text>
</Col>
<Col flex={'auto'}>
<Select
labelInValue
// mode={'multiple'}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleColsPick(v)}
value={columnSelection}
maxTagCount={2}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{rightFields.map((ele) => (
<Select.Option key={ele.key} value={ele.key}>
{ele.label}
</Select.Option>
))}
</Select>
</Col>
<Col span={24}>
<Row gutter={16} align={'bottom'} className="mt-1">
<Col span={24}>{showPassCountryTips && <Alert message="途径的国家将会重复计算" type="warning" showIcon />}</Col>
</Row>
{/* {columnFields.length > 0
? cloneDeep(pivotDateColumnsValues)
.slice(rowFields.length)
.map((_colArr, _colIndex) => (
<>
<Row gutter={8} key={_colArr.join('_')}>
<Col flex={'5em'} align={'end'}>
<Text strong>{filterFieldsMapped[pivotDateColumns[_colIndex + rowFields.length]]?.label || _colIndex + rowFields.length}: </Text>
</Col>
<Col flex={'auto'}>
<Select
size={'small'}
labelInValue
mode={_colIndex + rowFields.length === 0 ? 'multiple' : null}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleFieldsItemPick(v, _colIndex + rowFields.length, pivotDateColumns[_colIndex + rowFields.length], 'col')}
// value={sale_store.salesTrade.pickSales}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{pivotDateColumnsValues[_colIndex + rowFields.length].map((ele) => (
<Select.Option key={ele} value={ele}>
{ele}
</Select.Option>
))}
</Select>
</Col>
</Row>
</>
))
: null} */}
</Col>
</Row>
</Col>
</Row>
</Card>
<section>
<Row gutter={16} justify={'space-between'} className="mb-1">
<Col flex={'auto'}>
<h3>
走势: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</h3>
</Col>
<Col style={{ textAlign: 'right' }} align={'end'}>
<DateGroupRadio
visible={true}
dataRaw={{ data1: dataBeforeXChange }}
onChange={onChangeXDateFieldGroup}
value={lineChartX}
dataMapper={orderCountDataMapper}
fieldMapper={orderCountDataFieldMapper}
/>
</Col>
</Row>
<Spin spinning={loading}>
<Line {...lineConfig} data={dataSource} />
</Spin>
</section>
<section>
<Spin spinning={loading}>
<h3>
透视汇总表: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</h3>
<Divider orientation="right">
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}`).join('×')}
{...{ columns: targetTableProps.columns, dataSource: pivotTableDataSource }}
/>
</Divider>
<Table
{...targetTableProps}
dataSource={pivotTableDataSource}
components={{ body: { cell: TdCell } }}
onRow={(record) => {
return {
//
onClick: (event) => {
setPivotRow(record);
const thisDetail = rawData.filter((ele) => {
return record.keys.includes(String(ele.key));
});
setPivotRowDataSource(thisDetail);
},
};
}}
/>
</Spin>
</section>
<section>
<Spin spinning={loading}>
<h3>
点击上方表格行查看单行数据的订单明细:{' '}
<span style={{ fontSize: 'smaller' }}>{pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}: ${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('; ')}</span>
</h3>
<Divider orientation="right">
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }}
/>
</Divider>
<Table {...detailsTableProps} dataSource={pivotRowDataSource} components={{ body: { cell: TdCell } }} />
</Spin>
</section>
</div>
);
});

@ -1,54 +1,109 @@
import { useContext, useEffect } from 'react';
import { Row, Col, Typography, Space, Table, Divider } from 'antd';
import React, {useContext, useEffect} from 'react';
import { Row, Col, Typography, Space, DatePicker, Button, Select, Table, Divider } from 'antd';
import {
SearchOutlined,
} from '@ant-design/icons';
import { stores_Context } from '../config';
import * as config from "../config";
import { observer } from 'mobx-react';
import 'moment/locale/zh-cn';
import { utils, writeFileXLSX } from 'xlsx';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import moment from "moment";
import zhCNlocale from 'antd/es/date-picker/locale/zh_CN';
import { utils, writeFileXLSX } from "xlsx";
import GroupSelect from './../components/search/GroupSelect';
const DestinationGroupCount = () => {
const { customerServicesStore, date_picker_store } = useContext(stores_Context);
const {customerServicesStore} = useContext(stores_Context);
const destinationGroupCount = customerServicesStore.destinationGroupCount;
const destinationGroupCountColumns = customerServicesStore.destinationGroupCountColumns;
const { startDate, endDate, dateType, inProgress } = customerServicesStore;
const {startDate, endDate, dateType, inProgress} = customerServicesStore;
useEffect(() => {
customerServicesStore.selectCountry('china');
}, []);
const handleSearchClick = () => {
customerServicesStore.fetchDestinationGroupCount();
};
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
countryArea: { key: 'china', label: '国内' },
...customerServicesStore.searchValues,
},
shows: ['departureDateType', 'DepartmentList', 'countryArea', 'orderStatus', 'dates'],
fieldProps: {
DepartmentList: { show_all: true, mode: 'multiple' },
orderStatus: { show_all: true },
countryArea: { show_all: false },
years: { hide_vs: true },
departureDateType: { disabledKeys: ['applyDate'] },
},
}}
onSubmit={(_err, obj, form) => {
customerServicesStore.setSearchValues(obj, form);
customerServicesStore.fetchDestinationGroupCount();
<Row gutter={{ sm: 16, lg: 32 }} justify="end">
<Col md={24} lg={8} xxl={8}>
<GroupSelect value={customerServicesStore.selectedTeam}
onChange={(value) => customerServicesStore.selectTeam(value)}
style={{ width: '95%' }} show_all={true}
/>
</Col>
<Col md={24} lg={8} xxl={8}>
<Select value={customerServicesStore.selectedCountry} style={{ width: "95%" }} onChange={(value) => customerServicesStore.selectCountry(value)}>
<Select.Option key="china" value="china">
国内
</Select.Option>
<Select.Option key="foreign" value="foreign">
国外
</Select.Option>
</Select>
</Col>
<Col md={24} lg={8} xxl={8}>
<Select value={customerServicesStore.selectedOrderStatus} style={{ width: "95%" }} onChange={(value) => customerServicesStore.selectStatus(value)}>
<Select.Option key="所有" value="-1">
所有
</Select.Option>
<Select.Option key="已成行" value="1">
已成行
</Select.Option>
<Select.Option key="未成行" value="0">
未成行
</Select.Option>
</Select>
</Col>
<Col md={24} lg={8} xxl={8}>
<Select value={dateType} style={{ width: "95%" }} onChange={(value) => customerServicesStore.selectDateType(value)}>
<Select.Option key="startDate" value="startDate">
走团日期
</Select.Option>
<Select.Option key="ConfirmDate" value="ConfirmDate">
成团日期
</Select.Option>
</Select>
</Col>
<Col md={24} lg={8} xxl={8}>
<DatePicker.RangePicker
format={config.DATE_FORMAT} locale={zhCNlocale}
allowClear={false}
value={[startDate, endDate]}
onChange={(dates) => { customerServicesStore.selectDateRange(dates[0], dates[1]); }}
ranges={{
'本周': [moment().startOf('week'), moment().endOf('week')],
'上周': [moment().startOf('week').subtract(7, 'days'), moment().endOf('week').subtract(7, 'days')],
'本月': [moment().startOf('month'), moment().endOf('month')],
'上个月': [moment().subtract(1, 'months').startOf('month'), moment(new Date()).subtract(1, 'months').endOf('month')],
'近30天': [moment().subtract(30, 'days'), moment()],
'近三个月': [moment().subtract(2, 'month').startOf('month'), moment().endOf('month')],
'今年': [moment().startOf('year').subtract(1, 'month'), moment().endOf('year').subtract(1, 'month')],
'去年': [moment().subtract(1, 'year').startOf('year').subtract(1, 'month'), moment().subtract(1, 'year').endOf('year').subtract(1, 'month')],
}}
/>
</Col>
<Col md={24} lg={8} xxl={8}>
<Button
type="primary"
icon={<SearchOutlined />}
loading={inProgress}
onClick={() => {
handleSearchClick();
}}>
统计
</Button>
</Col>
</Row>
<Row>
<Col span={24}>
<Typography.Title level={3}>目的地团信息</Typography.Title>
<Divider orientation="right" plain>
<TableExportBtn label={'目的地团信息'} {...{ columns: destinationGroupCountColumns, dataSource: destinationGroupCount }} />
</Divider>
<Table
sticky
id="destinationGroupCount"
dataSource={destinationGroupCount}
columns={destinationGroupCountColumns}
@ -56,8 +111,17 @@ const DestinationGroupCount = () => {
rowKey={(record) => record.key}
loading={inProgress}
pagination={false}
scroll={{ x: 1000 }}
scroll={{ x: "100%" }}
/>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("destinationGroupCount").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "目的地团信息.xlsx");
}}>
导出excel
</a>
</Divider>
</Col>
</Row>
</Space>

@ -1,97 +1,112 @@
import { useContext, useEffect } from 'react';
import { Row, Col, Space, Table, List, Typography, Divider } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import { NavLink, useParams } from 'react-router-dom';
import React, {useContext, useEffect} from 'react';
import {Row, Col, Typography, Space, DatePicker, Button, Select, Table, List} from 'antd';
import {
SearchOutlined,
} from '@ant-design/icons';
import {stores_Context} from '../config';
import * as config from "../config";
import {observer} from 'mobx-react';
import { NavLink, useParams } from "react-router-dom";
import 'moment/locale/zh-cn';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import moment from "moment";
import zhCNlocale from 'antd/es/date-picker/locale/zh_CN';
import GroupSelect from './../components/search/GroupSelect';
const DestinationGroupList = () => {
const { destinationId } = useParams();
const { customerServicesStore, date_picker_store } = useContext(stores_Context);
const {destinationId} = useParams();
const { customerServicesStore } = useContext(stores_Context);
useEffect(() => {
customerServicesStore.fetchDistGroupInfoByCountry(destinationId);
customerServicesStore.fetchGroupListByDestinationId(destinationId);
}, []);
customerServicesStore.fetchGroupListByDestinationId(destinationId);
}, []);
const destinationGroupList = customerServicesStore.destinationGroupList;
const destinationGroupListColumns = customerServicesStore.destinationGroupListColumns;
const nationality_count_data = customerServicesStore.nationality_count_data;
const { inProgress } = customerServicesStore;
const {startDate, endDate, dateType, inProgress} = customerServicesStore;
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={{ md: 24 }} justify="end">
<Col span={24}>
return (
<>
<Space direction="vertical" style={{width: '100%'}}>
<Row gutter={{md: 24}} justify="end">
<Col span={8}>
<NavLink to={'/destination/group/count'}>返回</NavLink>
</Col>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
countryArea: { key: 'china', label: '国内' },
...customerServicesStore.searchValues,
},
shows: ['departureDateType', 'DepartmentList', 'dates', 'orderStatus'],
fieldProps: {
DepartmentList: { show_all: true },
orderStatus: { show_all: true },
countryArea: { show_all: false },
dates: { hide_vs: true },
departureDateType: { disabledKeys: ['applyDate'] },
},
}}
onSubmit={(_err, obj, form) => {
customerServicesStore.setSearchValues(obj, form);
customerServicesStore.fetchGroupListByDestinationId(destinationId);
customerServicesStore.fetchDistGroupInfoByCountry(destinationId);
customerServicesStore.fetchDestinationGroupCount();
}}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<Typography.Title level={3}>国籍统计</Typography.Title>
<Divider orientation="right" plain>
<TableExportBtn label={'国籍统计'} {...{ columns: nationality_count_data.destinationGroupByCountryListColumns, dataSource: nationality_count_data.destinationGroupByCountryList }} />
</Divider>
<Table
sticky
id="destinationGroupByCountry"
dataSource={nationality_count_data.destinationGroupByCountryList}
columns={nationality_count_data.destinationGroupByCountryListColumns}
size="small"
rowKey={(record) => record.key}
loading={nationality_count_data.loading}
pagination={false}
scroll={{ x: 1000 }}
<Col span={4}>
<GroupSelect value={customerServicesStore.selectedTeam}
onChange={(value) => customerServicesStore.selectTeam(value)}
style={{ width: '95%' }} show_all={true}
/>
</Col>
<Col span={4}>
<Select value={dateType} style={{ width: "95%" }} onChange={(value) => customerServicesStore.selectDateType(value)}>
<Select.Option key="1" value="startDate">
走团日期
</Select.Option>
<Select.Option key="2" value="ConfirmDate">
成团日期
</Select.Option>
</Select>
</Col>
<Col span={4}>
<DatePicker.RangePicker
format={config.DATE_FORMAT} locale={zhCNlocale}
allowClear={false}
value={[startDate, endDate]}
onChange={(dates) => {customerServicesStore.selectDateRange(dates[0], dates[1]);}}
ranges={{
'本周': [moment().startOf('week'), moment().endOf('week')],
'上周': [moment().startOf('week').subtract(7, 'days'), moment().endOf('week').subtract(7, 'days')],
'本月': [moment().startOf('month'), moment().endOf('month')],
'上个月': [moment().subtract(1, 'months').startOf('month'), moment(new Date()).subtract(1, 'months').endOf('month')],
'近30天': [moment().subtract(30, 'days'), moment()],
'近三个月': [moment().subtract(2, 'month').startOf('month'), moment().endOf('month')],
'今年': [moment().startOf('year').subtract(1, 'month'), moment().endOf('year').subtract(1, 'month')],
'去年': [moment().subtract(1, 'year').startOf('year').subtract(1, 'month'), moment().subtract(1, 'year').endOf('year').subtract(1, 'month')],
}}
/>
</Col>
<Col span={4}>
<Button
type="primary"
icon={<SearchOutlined />}
loading={inProgress}
onClick={() => {
customerServicesStore.fetchGroupListByDestinationId(destinationId);
customerServicesStore.fetchDestinationGroupCount();
}}>
统计
</Button>
</Col>
</Row>
<Row>
<Col span={24}>
<Table
sticky
dataSource={destinationGroupList}
columns={destinationGroupListColumns}
size="small"
rowKey={(record) => record.key}
loading={inProgress}
pagination={false}
scroll={{ x: 600 }}
scroll={{ x: "100%" }}
expandable={{
expandedRowRender: (record) => (
<List itemLayout="horizontal">
<List
itemLayout="horizontal"
>
<List.Item>
<List.Item.Meta title="经过城市" description={record.PassCity} />
<List.Item.Meta
title='经过城市'
description={record.PassCity}
/>
</List.Item>
<List.Item>
<List.Item.Meta title="地接社" description={record.VendorList} />
<List.Item.Meta
title='地接社'
description={record.VendorList}
/>
</List.Item>
</List>
),
@ -99,9 +114,9 @@ const DestinationGroupList = () => {
/>
</Col>
</Row>
</Space>
</>
);
</Space>
</>
);
};
export default observer(DestinationGroupList);

@ -1,12 +1,12 @@
import { useContext, useEffect } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Row, Col, Spin, Tabs, Table, Space, Typography, Divider } from 'antd';
import { Row, Col, Spin, Tabs, Table, Space, Typography } from 'antd';
import { RingProgress } from '@ant-design/plots';
import SearchForm from './../components/search/SearchForm';
import { empty } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
import { VSTag, TableExportBtn } from './../components/Data';
import { VSTag } from './../components/Data';
import MixYnQ from './../components/MixYnQ';
import './kpi.css';
@ -19,22 +19,15 @@ const apartOptions = [
{ key: 'ConfirmDays', value: 'ConfirmDays', label: '成团周期' },
{ key: 'ApplyDays', value: 'ApplyDays', label: '预定周期' },
{ key: 'PersonNum', value: 'PersonNum', label: '人等' },
{ key: 'destination', value: 'destination', label: '国内目的地', },
{ key: 'GlobalDestination', value: 'GlobalDestination', label: '海外目的地', },
{ key: 'destinationCountry', value: 'destinationCountry', label: '目的地国籍', },
{ key: 'guestCountry', value: 'guestCountry', label: '客人国籍', },
{ key: 'destination', value: 'destination', label: '国内目的地' },
{ key: 'GlobalDestination', value: 'GlobalDestination', label: '海外目的地' },
{ key: 'destinationCountry', value: 'destinationCountry', label: '目的地国籍' },
{ key: 'guestCountry', value: 'guestCountry', label: '客人国籍' },
];
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
export default observer(() => {
const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { formValues, formValuesToSub } = searchFormStore;
const { curTab, dateStringQ, dateStringY } = DistributionStore;
const pageRefresh = (obj) => {
@ -43,6 +36,12 @@ export default observer(() => {
});
};
useEffect(() => {
if (empty(DistributionStore[curTab].dataSource)) {
pageRefresh();
}
}, [curTab]);
useEffect(() => {
DistributionStore.setFormDates(formValuesToSub);
DistributionStore.resetData();
@ -51,9 +50,6 @@ export default observer(() => {
const onTabsChange = (tab) => {
DistributionStore.setCurTab(tab);
if (empty(DistributionStore[tab].dataSource)) {
pageRefresh();
}
};
const RingProgressConfig = {
height: 60,
@ -77,38 +73,27 @@ export default observer(() => {
innerRadius: 0.90,
};
const columns = [
{ title: '#', dataIndex: 'label' },
{
title: '预定',
dataIndex: 'SumOrder',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>{v}</Text>
<span>
<span>同比: </span> <VSTag diffPercent={r.SumOrderToY} diffData={r.SumOrderDiffY} />
</span>
<span>
<span>环比: </span> <VSTag diffPercent={r.SumOrderToQ} diffData={r.SumOrderDiffQ} />
</span>
</Space>
</>
),
},
{ title: '', dataIndex: 'label' },
{
title: '团数',
dataIndex: 'ConfirmOrder',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Row align={'middle'}>
<Col flex={"100px"}>
<Text strong>{v}</Text>
<span>
</Col>
<Col flex={'auto'}>
<Space direction={'vertical'}>
<span>
<span>同比: </span> <VSTag diffPercent={r.ConfirmOrderToY} diffData={r.ConfirmOrderDiffY} />
</span>
<span>
<span>环比: </span> <VSTag diffPercent={r.ConfirmOrderToQ} diffData={r.ConfirmOrderDiffQ} />
</span>
</Space>
</Col>
</Row>
</>
),
},
@ -117,15 +102,21 @@ export default observer(() => {
dataIndex: 'SumML',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Row align={'middle'}>
<Col flex={"150px"}>
<Text strong>{dataFieldAlias.SumML.formatter(v)}</Text>
<span>
</Col>
<Col flex={'auto'}>
<Space direction={'vertical'}>
<span>
<span>同比: </span> <VSTag diffPercent={r.SumMLToY} diffData={dataFieldAlias.SumML.formatter(r.SumMLDiffY)} />
</span>
<span>
<span>环比: </span> <VSTag diffPercent={r.SumMLToQ} diffData={dataFieldAlias.SumML.formatter(r.SumMLDiffQ)} />
</span>
</Space>
</Col>
</Row>
</>
),
},
@ -141,42 +132,36 @@ export default observer(() => {
},
{
title: () => <><div>去年同期</div><div>{dateStringY}</div></>,
titleX: ['去年同期', dateStringY], //
align: 'center',
children: [
{
title: '团数占比',
titleX: '团数占比',
width: 90,
dataIndex: ['resultToY', 'ConfirmOrderPercent'], // 'ConfirmOrderPercent',
dataIndex: 'ConfirmOrderPercent',
render: (v, r) => r.resultToY.ConfirmOrderPercent ? <RingProgress {...RingProgressConfigY} percent={r.resultToY.ConfirmOrderPercent / 100} /> : '-',
},
{
title: '业绩占比',
titleX: '业绩占比',
width: 90,
dataIndex: ['resultToY', 'SumMLPercent'], // 'SumMLPercent',
dataIndex: 'SumMLPercent',
render: (v, r) => r.resultToY.SumMLPercent ? <RingProgress {...RingProgressConfigY} percent={r.resultToY.SumMLPercent / 100} /> : '-',
},
],
},
{
title: () => <><div>上个时间段</div><div>{dateStringQ}</div></>,
titleX: ['上个时间段', dateStringY], //
align: 'center',
children: [
{
title: '团数占比',
titleX: '团数占比',
width: 90,
dataIndex: ['resultToQ', 'ConfirmOrderPercent'], // 'ConfirmOrderPercent',
dataIndex: 'ConfirmOrderPercent',
render: (v, r) => r.resultToQ.ConfirmOrderPercent ? <RingProgress {...RingProgressConfigQ} percent={r.resultToQ.ConfirmOrderPercent / 100} /> : '-',
},
{
title: '业绩占比',
titleX: '业绩占比',
width: 90,
dataIndex: ['resultToQ', 'SumMLPercent'], // 'SumMLPercent',
dataIndex: 'SumMLPercent',
render: (v, r) => r.resultToQ.SumMLPercent ? <RingProgress {...RingProgressConfigQ} percent={r.resultToQ.SumMLPercent / 100} /> : '-',
},
],
@ -189,7 +174,7 @@ export default observer(() => {
};
return (
<>
<Row gutter={16} className={siderBroken ? "" : "sticky-top"} >
<Row gutter={16} style={{ margin: '-16px -8px' }}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
@ -220,12 +205,8 @@ export default observer(() => {
children: (
<Spin spinning={DistributionStore.pageLoading}>
<MixYnQ {...chartsConfig} dataSource={DistributionStore[curTab].dataSource} />
<Divider orientation="right" plain>
<TableExportBtn label={`统计分布-${ele.label}`} {...{ columns, dataSource: DistributionStore[curTab].dataSource }} />
</Divider>
<Table
id="table_to_xlsx_sale"
components={{ body: { cell: TdCell } }}
dataSource={DistributionStore[curTab].dataSource}
columns={columns}
size="small"

@ -1,6 +1,6 @@
import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { Row, Col, Spin, Space, Radio, Table, Button, } from 'antd';
import { Row, Col, Spin, Space, Radio, Table } from 'antd';
import { CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwoTone } from '@ant-design/icons';
import { stores_Context } from '../config';
import StatisticCard2 from '../components/StatisticCard2';
@ -8,8 +8,6 @@ import Bullet from '../components/BulletWithSort';
import Waterfall from '../components/Waterfall';
import MixTBWithKPI from './../components/MixTBWithKPI';
import Donut from './../components/Donut';
import MapCountry from './../components/MapCountry';
import LineWithKPI from '../components/LineWithKPI';
import DataFieldRadio from '../components/DataFieldRadio';
import { datePartOptions } from './../components/DateGroupRadio/date';
import SearchForm from './../components/search/SearchForm';
@ -18,19 +16,11 @@ import { dataFieldAlias } from './../libs/ht';
import './home.css';
const topSeries = [
{ key: 'dept', value: 'dept', label: '小组', graphVisible: true },
{ key: 'operator', value: 'operator', label: '顾问', graphVisible: true },
{ key: 'destination', value: 'destination', label: '目的地', graphVisible: true },
{ key: 'GuestGroupType', value: 'GuestGroupType', label: '客群类别', graphVisible: false },
{ key: 'country', value: 'country', label: '国籍', graphVisible: true },
{ key: 'webcode', value: 'webcode', label: '站点', graphVisible: false },
{ key: 'bizarea', value: 'bizarea', label: '国境', graphVisible: false },
{ key: 'destinationcountry', value: 'destinationcountry', label: '目的地国籍', graphVisible: true },
];
const allGroupTypes = [
{ key: 'overview', value: 'overview', label: '总额' },
...topSeries,
{ key: 'dept', label: '小组', graphVisible: true },
{ key: 'operator', label: '顾问', graphVisible: true },
{ key: 'country', label: '国籍', graphVisible: true },
{ key: 'GuestGroupType', label: '客群类别', graphVisible: false },
{ key: 'destination', label: '目的地', graphVisible: true },
];
// const iconSets = [CheckCircleTwoTone, <MoneyCollectTwoTone />, <FlagTwoTone />, <ClockCircleTwoTone />, <DashboardTwoTone />,<SmileTwoTone />,];
@ -38,27 +28,17 @@ const iconSets = [CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwo
export default observer(() => {
// const navigate = useNavigate();
const { TradeStore, date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context);
const { searchValues, sideData, summaryData, BuData, topData, timeData, timeLineKey, targetTableProps, timeDiffData, groupKey } = TradeStore;
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { curTab, dateStringQ, dateStringY } = DistributionStore;
const { TradeStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { sideData, summaryData, BuData, topData, timeData, timeLineKey, targetTableProps } = TradeStore;
const { formValues } = searchFormStore;
useEffect(() => {
DistributionStore.setCurTab('destinationCountry');
// if (empty(summaryData.dataSource)) {
// // pageRefresh();
// }
if (empty(summaryData.dataSource)) {
// pageRefresh();
}
return () => {};
}, []);
const getDestinationCountry = (obj) => {
DistributionStore.setCurTab('destinationCountry');
DistributionStore.getApartData({
...(obj || formValuesToSub),
}, false);
};
const [topSeriesSet, setTopSeriesSet] = useState(topSeries);
const [overviewFlag, setOverviewFlag] = useState(true);
const [groupTypeVal, setGroupTypeVal] = useState('overview');
@ -67,11 +47,9 @@ export default observer(() => {
const groupType = _overviewFlag ? 'overview' : 'dept';
queryData.groupType = groupType;
setGroupTypeVal(groupType);
setDiffGroupKey('overview');
TradeStore.resetData();
TradeStore.fetchSummaryData(Object.assign({}, queryData, { groupType }));
TradeStore.fetchTradeDataByDate(queryData);
TradeStore.fetchTradeDataDiffByDate(queryData);
// // TradeStore.fetchTradeDataByBU(queryData);
TradeStore.fetchTradeDataByMonth(queryData);
const topSeriesF = _overviewFlag ? topSeries : topSeries.filter((ele) => ele.key !== 'dept');
@ -154,6 +132,7 @@ export default observer(() => {
xAxis: {
type: 'cat',
},
smooth: true,
point: {
size: 4,
shape: 'cicle',
@ -178,49 +157,35 @@ export default observer(() => {
TradeStore.setTimeLineKey(value);
if (!isEmpty(TradeStore.searchPayloadHome)) {
TradeStore.fetchTradeDataByDate({ groupType: groupTypeVal });
TradeStore.fetchTradeDataDiffByDate({ groupType: diffGroupKey });
}
};
const [diffGroupKey, setDiffGroupKey] = useState(groupKey);
const handleChangeDiffType = ({ target: { value } }) => {
// console.log('diffGroupKey', diffGroupKey, value);
setDiffGroupKey(value);
TradeStore.setGroupKey(value);
if (!isEmpty(TradeStore.searchPayloadHome)) {
TradeStore.fetchTradeDataDiffByDate({ groupType: value });
}
};
const [showDiff, setShowDiff] = useState(false);
return (
<>
<Row gutter={16} className={siderBroken ? "" : "sticky-top"}>
<Row gutter={16} style={{ margin: '-16px -8px', position: 'sticky', top: 0, zIndex: 10 }}>
{/* style={{ margin: '-16px -8px', padding: 0 }} */}
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'years'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: true },
years: { hide_vs: false },
years: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
TradeStore.setSearch(obj, form);
TradeStore.setStateSearch(obj);
pageRefresh(obj);
getDestinationCountry(obj);
}}
/>
</Col>
</Row>
<section>
<Space>
<h2>
年度业绩<span style={{ fontSize: 'small' }}> =传统+商务</span>
</h2>
<h2>年度业绩=传统+商务</h2>
</Space>
<Spin spinning={summaryData.loading}>
<Row gutter={layoutProps.gutter}>
@ -230,7 +195,7 @@ export default observer(() => {
</Col>
))} */}
{summaryData.dataSource.map((item, i) => (
<Col {...layoutProps} key={item.title} lg={{ span: item?.col || layoutProps.lg.span }}>
<Col {...layoutProps} key={item.title}>
<StatisticCard2 {...item} showProgress={item.hasKPI} icon={iconSets[i]} />
</Col>
))}
@ -238,44 +203,22 @@ export default observer(() => {
</Spin>
</section>
<section>
<Row gutter={16}>
<Col flex={'4em'}><h3>{showDiff === false ? '走势' : '对比'}</h3></Col>
<Col ><DataFieldRadio value={timeDataField} onChange={handleChangetimeDataField} /></Col>
<Col ><Radio.Group options={datePartOptions} optionType="button" onChange={handleChangeDateType} value={dateField} /></Col>
<Col >{searchValues.yearDiff && (
<Button type="primary" ghost size={'small'} onClick={() => setShowDiff(!showDiff)}>
{showDiff === false ? '显示对比' : '返回本期走势'}
</Button>
)}</Col>
</Row>
{showDiff === false ? (
<Spin spinning={timeData.loading}>
<MixTBWithKPI dataSource={timeData.dataSource} summaryData={timeData.origin?.summaryRows || []} {...lineConfig} />
</Spin>
) : (
<Spin spinning={timeDiffData.loading}>
<Row gutter={16}>
<Col flex={'6em'}><h3>分类对比</h3></Col>
<Col ><Radio.Group options={allGroupTypes} optionType="button" onChange={handleChangeDiffType} value={diffGroupKey} /></Col>
</Row>
<LineWithKPI
dataSource={timeDiffData.dataSource}
showKPI={false}
{...lineConfig}
{...{ appendPadding: 10, legend: { position: 'right-top', title: { text: '虚线: 对比年份' } }, point: false }}
/>
</Spin>
)}
<Space gutter={16} size={'large'}>
<h3>走势</h3>
<DataFieldRadio value={timeDataField} onChange={handleChangetimeDataField} />
<Radio.Group options={datePartOptions} optionType="button" onChange={handleChangeDateType} value={dateField} />
</Space>
<Spin spinning={timeData.loading}>
{/* <LineWithKPI dataSource={timeData.dataSource} {...lineConfig} /> */}
<MixTBWithKPI dataSource={timeData.dataSource} summaryData={timeData.origin?.summaryRows || []} {...lineConfig} />
</Spin>
</section>
<section>
<h3>市场 (仅传统订单) </h3>
<Spin spinning={sideData.loading}>
<Spin spinning={BuData.loading}>
<Row gutter={layoutProps3.gutter}>
<Col {...layoutProps3}>
<>
<Donut {...{ angleField: 'SumML', colorField: 'groupsLabel' }} title={formValues.DepartmentList?.label} dataSource={sideData.yearData} />
</>
<><Donut {...{angleField: 'SumML', colorField: 'groupsLabel'}} title={formValues.DepartmentList?.label} dataSource={sideData.yearData} /></>
{/* {overviewFlag ? (
<>
<Bullet {...BUConfig} dataSource={BuData?.dataSource || []} />
@ -285,14 +228,12 @@ export default observer(() => {
<><Donut {...{angleField: 'SumML', colorField: 'groupsLabel'}} title={formValues.DepartmentList?.label} dataSource={sideData.yearData} /></>
)} */}
</Col>
{Object.keys(sideData.dataSource)
.sort()
.map((key) => (
<Col {...layoutProps3} key={key}>
<Waterfall key={key} {...WaterfallConfig} title={key} dataSource={sideData.dataSource[key]} line={summaryData.kpi} />
<h3 style={{ textAlign: 'center' }}>{`${key}每月业绩`}</h3>
</Col>
))}
{Object.keys(sideData.dataSource).sort().map((key) => (
<Col {...layoutProps3} key={key}>
<Waterfall key={key} {...WaterfallConfig} title={key} dataSource={sideData.dataSource[key]} line={summaryData.kpi} />
<h3 style={{ textAlign: 'center' }}>{`${key}每月业绩`}</h3>
</Col>
))}
</Row>
</Spin>
</section>
@ -300,19 +241,21 @@ export default observer(() => {
<h3>
英语区目标客户
<Spin spinning={topData?.GuestGroupType?.loading || false}>
<Table {...targetTableProps} pagination={false} rowKey={'groupsLabel'} />
<Table {...targetTableProps} pagination={false} />
</Spin>
</h3>
</section>
<section>
<Row gutter={16}>
<Col flex={'4em'}><h3>TOP</h3></Col>
<Col ><DataFieldRadio value={valueKey} onChange={handleChangeValueKey} /></Col>
</Row>
<Row gutter={layoutProps3.gutter}>
<Space>
<h3>TOP</h3>
<div>
<DataFieldRadio value={valueKey} onChange={handleChangeValueKey} />
</div>
</Space>
<Row gutter={layoutProps.gutter}>
{topSeriesSet.map((item) =>
item.graphVisible ? (
<Col {...layoutProps3} key={item.key}>
<Col {...layoutProps} key={item.key}>
<Spin spinning={topData[item.key]?.loading || false}>
<h3 style={{ textAlign: 'center' }}>{item.label}</h3>
<Bullet {...BulletConfig} dataSource={topData[item.key]?.dataSource || []} itemLength={10} key={item.key} />
@ -320,31 +263,6 @@ export default observer(() => {
</Col>
) : null
)}
<Col key={'mapG'} span={22}>
<hr />
<h3>来源国籍分布</h3>
<Spin spinning={topData?.country?.loading || false}>
<div id="topC" style={{ height: '700px' }}>
<MapCountry sourceField={'groupsLabel'} valueField={BUConfig.measureField} dataSource={topData?.country?.dataSource || []} containerNode='#topC' />
</div>
</Spin>
</Col>
<Col key={'mapG-r'} span={1}></Col>
<Col key={'mapDestinationCountry'} span={22}>
<hr />
<h3>目的地国籍分布</h3>
{/* <Spin spinning={DistributionStore.pageLoading || false}>
<div id="mapDestinationCountry" style={{ height: '700px' }}>
<MapCountry sourceField={'label'} valueField={BUConfig.measureField} dataSource={DistributionStore.destinationCountry.originData || []} containerNode='#mapDestinationCountry' />
</div>
</Spin> */}
<Spin spinning={topData?.destinationcountry?.loading || false}>
<div id="mapDestinationCountry" style={{ height: '700px' }}>
<MapCountry sourceField={'groupsLabel'} valueField={BUConfig.measureField} dataSource={topData?.destinationcountry?.dataSource || []} containerNode='#mapDestinationCountry' />
</div>
</Spin>
</Col>
<Col key={'mapGc-r'} span={1}></Col>
</Row>
</section>
</>

@ -1,147 +0,0 @@
import { useContext } from 'react';
import { Row, Col, Typography, Space, Table, Divider } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import 'moment/locale/zh-cn';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from '../components/Data';
import * as comm from '../utils/commons';
const HostCaseCount = () => {
const { customer_store, date_picker_store } = useContext(stores_Context);
const host_case_data = customer_store.host_case_data;
const columnsList = [
{
title: '团数',
dataIndex: 'TotalGroupNum',
key: 'TotalGroupNum',
sorter: (a, b) => parseInt(a.TotalGroupNum) - parseInt(b.TotalGroupNum),
},
{
title: '人数',
dataIndex: 'TotalPersonNum',
key: 'TotalPersonNum',
sorter: (a, b) => parseInt(a.TotalPersonNum) - parseInt(b.TotalPersonNum),
},
{
title: '计费团天数',
dataIndex: 'TotalDays',
key: 'TotalDays',
sorter: (a, b) => parseInt(a.TotalDays) - parseInt(b.TotalDays),
},
{
title: '交易额',
dataIndex: 'TotalPrice',
key: 'TotalPrice',
sorter: (a, b) => parseInt(a.TotalPrice) - parseInt(b.TotalPrice),
},
];
//
const getFiltersData=(filterData,filterKey)=>{
return comm.uniqWith(filterData.map(rr => ({ text: rr[filterKey], value: rr[filterKey] })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)).sort((a, b) => a.text.localeCompare(b.text));
};
//
const allOPIGroup = getFiltersData(host_case_data.summaryData,"GroupBy");
//
const allOPIConsultant = getFiltersData(host_case_data.counselorData,"GroupBy");
//
const allOPIDetailConsultant = getFiltersData(host_case_data.singleDetailData,"OPI_Name");
const summaryColumnsList = [{
title: '',
dataIndex: 'GroupBy',
key: 'GroupBy',
}, ...columnsList];
const groupColumnsList = [{
title: '组名',
dataIndex: 'GroupBy',
key: 'GroupBy',
filters: allOPIGroup,
onFilter: (value, record) => record.GroupBy === value,
filterSearch: true,
}, ...columnsList];
const counselorColumnsList = [{
title: '顾问名',
dataIndex: 'GroupBy',
key: 'GroupBy',
filters: allOPIConsultant,
onFilter: (value, record) => record.GroupBy === value,
filterSearch: true,
}, ...columnsList];
const singleDetailColumnsList = [{
title: '团名',
dataIndex: 'GroupBy',
key: 'GroupBy',
},
{
title: '顾问名',
dataIndex: 'OPI_Name',
key: 'OPI_Name',
filters: allOPIDetailConsultant,
onFilter: (value, record) => record.OPI_Name === value,
filterSearch: true,
},
...columnsList.slice(1)];
const renderRow=(rowColumns,rowDataSource,title)=>{
return(
<Row>
<Col span={24}>
<Typography.Title level={3}>{title}</Typography.Title>
<Divider orientation="right" plain>
<TableExportBtn label={title} {...{ columns: rowColumns, dataSource: rowDataSource }} />
</Divider>
<Table
sticky
id={`${rowColumns}`}
dataSource={rowDataSource}
columns={rowColumns}
size="small"
rowKey={(record) => record.key}
loading={host_case_data.loading}
pagination={false}
scroll={{ x: 1000 }}
/>
</Col>
</Row>
);
};
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"} >
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...host_case_data.searchValues,
},
shows: ['DepartmentList', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form) => {
customer_store.setSearchValues(obj, form,"host_case_data");
customer_store.getHostCaseData("1");
customer_store.getHostCaseData("2");
customer_store.getHostCaseData("3");
customer_store.getHostCaseData("4");
}}
/>
</Col>
</Row>
{renderRow(summaryColumnsList,host_case_data.summaryData,'东道主项目汇总')}
{renderRow(groupColumnsList,host_case_data.groupData,'东道主项目小组统计')}
{renderRow(counselorColumnsList,host_case_data.counselorData,'东道主项目顾问统计')}
{renderRow(singleDetailColumnsList,host_case_data.singleDetailData,'单团明细')}
</Space>
</>
);
};
export default observer(HostCaseCount);

@ -1,174 +0,0 @@
import React, { useContext } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Row, Col, Table, Space, Typography, Divider } from 'antd';
import SearchForm from './../components/search/SearchForm';
import { VSTag, TableExportBtn } from './../components/Data';
const { Text } = Typography;
export default observer((props) => {
const { date_picker_store: searchFormStore } = useContext(stores_Context);
const { date_picker_store, HotelCruiseStore } = useContext(stores_Context);
const { loading, dataSource, summaryRow, filters } = HotelCruiseStore.hotel;
const { formValues, siderBroken } = searchFormStore;
const tableSorter = (a, b, colName) => a[colName] - b[colName];
const tableExportDataRow = (col1, col2) => [col1, col2].filter((r) => r).join(' VS ');
const tableProps = {
size: 'small',
bordered: true,
pagination: false,
columns: [
{
title: '目的地',
sorter: (a, b) => a.CityName.localeCompare(b.CityName, 'zh-CN'),
children: [{ title: summaryRow.CityName, dataIndex: 'CityName', key: 'CityName' }],
filters: filters,
onFilter: (value, record) => record.CityName === value,
filterSearch: true,
},
{
title: '总间夜',
sorter: (a, b) => tableSorter(a, b, 'TotalNum'),
children: [
{
title: (
<>
<Space direction={'vertical'}>
<Text strong>
{summaryRow.TotalNum}
{summaryRow.TotalNumPercent ? <Text type="secondary"> VS {summaryRow.CPTotalNum}</Text> : null}
</Text>
{summaryRow.TotalNumPercent && <VSTag diffPercent={summaryRow.TotalNumPercent} />}
</Space>
</>
),
titleX: tableExportDataRow(summaryRow.TotalNum, summaryRow.CPTotalNum),
dataIndex: 'TotalNum',
key: 'TotalNum',
dataExport: (v, r) => tableExportDataRow(r.TotalNum, r.CPTotalNum),
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPTotalNum ? <Text type="secondary"> VS {r.CPTotalNum}</Text> : null}
</Text>
{r.CPTotalNum && <VSTag diffPercent={r.TotalNumPercent} />}
</Space>
</>
),
},
],
},
{
title: '主推',
sorter: (a, b) => tableSorter(a, b, 'RecomendNum'),
children: [
{
title: (
<>
<Space direction={'vertical'}>
<Text strong>
{summaryRow.RecomendNum}
{summaryRow.RecomendNumPercent ? <Text type="secondary"> VS {summaryRow.CPRecomendNum}</Text> : null}
</Text>
{summaryRow.RecomendNumPercent && <VSTag diffPercent={summaryRow.RecomendNumPercent} />}
</Space>
</>
),
titleX: tableExportDataRow(summaryRow.RecomendNum, summaryRow.CPRecomendNum),
dataIndex: 'RecomendNum',
key: 'RecomendNum',
dataExport: (v, r) => tableExportDataRow(r.RecomendNum, r.CPRecomendNum),
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPRecomendNum ? <Text type="secondary"> VS {r.CPRecomendNum}</Text> : null}
</Text>
{r.CPRecomendNum && <VSTag diffPercent={r.RecomendNumPercent} />}
</Space>
</>
),
},
],
},
{
title: '使用比例',
children: [
{
title: (
<>
<Space direction={'vertical'}>
<Text strong>
{summaryRow.RecommendRate_100}
{summaryRow.RecommendRateDelta ? <Text type="secondary"> VS {summaryRow.CPRecommendRate_100}</Text> : null}
</Text>
{summaryRow.RecommendRateDelta && <VSTag diffPercent={summaryRow.RecommendRateDelta} />}
</Space>
</>
),
titleX: tableExportDataRow(summaryRow.RecommendRate_100, summaryRow.CPRecommendRate_100),
dataIndex: 'RecommendRate_100',
key: 'RecommendRate_100',
dataExport: (v, r) => tableExportDataRow(r.RecommendRate_100, r.CPRecommendRate_100),
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.RecommendRateDelta !== undefined && <Text type="secondary"> VS {r.CPRecommendRate_100}</Text>}
</Text>
{r.RecommendRateDelta !== undefined && <VSTag diffPercent={r.RecommendRateDelta} />}
</Space>
</>
),
},
],
},
],
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={24} xxl={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...HotelCruiseStore.searchValues,
},
// 'countryArea', 'DateType', 'dates', 'hotelRecommandRate',
shows: ['DepartmentList', 'countryArea', 'orderStatus', 'hotelBookType', 'hotelStar', 'DateType', 'dates'],
sort: { DateType: 101, dates: 102 },
fieldProps: {
DepartmentList: { show_all: true, mode: 'multiple' },
countryArea: { show_all: true },
orderStatus: { show_all: true },
hotelBookType: { show_all: true },
hotelRecommandRate: { show_all: true },
// years: { hide_vs: false },
DateType: { disabledKeys: ['applyDate'] },
},
}}
onSubmit={(_err, obj, form) => {
HotelCruiseStore.setSearchValues(obj, form);
HotelCruiseStore.getHotelData(obj);
}}
/>
</Col>
</Row>
<section>
<Divider orientation="right" >
<TableExportBtn label={'酒店'} {...{ columns: tableProps.columns, dataSource }} />
</Divider>
<Table {...tableProps} bordered {...{ loading, dataSource }} rowKey={(record) => record.CityName} />
</section>
</>
);
});

@ -1,133 +0,0 @@
import { useContext } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Table, Row, Col, Divider, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { fixTo2Decimals } from './../utils/commons';
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals(number / (1000 * scale)) + '';
};
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
export default observer((props) => {
const { date_picker_store: searchFormStore, MeetingDataStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const dataRefresh = async (obj) => {
MeetingDataStore.dataGHOrder({ ...(obj || formValuesToSub) });
MeetingDataStore.dataGHSales({ ...(obj || formValuesToSub) });
MeetingDataStore.dataGHService({ ...(obj || formValuesToSub) });
};
const targetTableProps = {
loading: MeetingDataStore.GHLoading,
// sticky: true,
scroll: { x: 1000, y: 400 },
pagination: false,
components: { body: { cell: TdCell } },
orderColumns: [
{ key: 'label', title: '市场:', dataIndex: 'label', width: 150 },
{
key: 'LineClass_Origin',
titleX: '网站',
title: () => (
<>
网站{' '}
<Tooltip title="网前自然订单">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'LineClass_Origin',
},
{
key: 'external',
titleX: '站外渠道',
title: () => (
<>
站外渠道{' '}
<Tooltip title="Facebook, Pinterest, Youtube, Instagram">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'external',
},
{ key: 'LineClass_PPC', title: 'PPC', dataIndex: 'LineClass_PPC' },
{ key: 'toB', title: 'To B', dataIndex: 'toB' },
{ key: 'isOld1', title: 'C老客户', dataIndex: 'isOld1' },
{ key: 'total', title: '合计', dataIndex: 'total' },
{ key: 'rowYear', title: '截至年订单数', dataIndex: 'rowYear' },
// { key: 'groupsLabel2', title: '', dataIndex: 'groupsLabel2' },
// { key: 'groupsLabel2', title: '', dataIndex: 'groupsLabel2' },
],
salesColumns: [
{ key: 'label', title: '顾问:', dataIndex: 'label', width: 150 },
{ key: 'CJCount', title: '成交个数', dataIndex: 'CJCount' },
{ key: 'YJLY', title: '成交毛利(万)', dataIndex: 'YJLY', render: (text) => numberConvert10K(text) },
{ key: 'CJCount1', title: '年成交个数', dataIndex: ['rowYear', 'CJCount'] },
{ key: 'YJLY1', title: '年成交毛利(万)', dataIndex: ['rowYear', 'YJLY'], render: (text) => numberConvert10K(text) },
{ key: 'YJLY2', title: '年走团毛利(万)', dataIndex: ['rowYear', 'YJLY2'], render: (text) => numberConvert10K(text) },
],
serviceColumns: [
{ key: 'label', title: '客服:', dataIndex: 'label', width: 150 },
{ key: 'GroupCount', title: '走团个数', dataIndex: 'GroupCount' },
{ key: 'GoodCount', title: '好评个数', dataIndex: 'GoodCount' },
{ key: 'GroupCount1', title: '年走团个数', dataIndex: ['rowYear', 'GroupCount'] },
{ key: 'GoodCount2', title: '年好评个数', dataIndex: ['rowYear', 'GoodCount'] },
],
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...MeetingDataStore.searchValues,
// ...searchInitial,
},
shows: ['IncludeTickets', 'dates'], // 'country'
fieldProps: {
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
MeetingDataStore.setSearchValues(form);
dataRefresh(obj);
}}
/>
</Col>
</Row>
<Divider orientation="right" plain>
<span>GH: 市场</span>
<TableExportBtn label={`${formValuesToSub.Date1}-GH: 市场`} {...{ columns: targetTableProps.orderColumns, dataSource: MeetingDataStore.GHTableData }} />
</Divider>
<Table {...targetTableProps} key={'GHTable'} dataSource={MeetingDataStore.GHTableData} columns={targetTableProps.orderColumns} loading={MeetingDataStore.GHTableLoading} />
<Divider orientation="right" plain>
<span>GH: 顾问成交</span>
<TableExportBtn label={`${formValuesToSub.Date1}-GH: 顾问成交`} {...{ columns: targetTableProps.salesColumns, dataSource: MeetingDataStore.GHSalesTableData }} />
</Divider>
<Table {...targetTableProps} key={'GHSales'} dataSource={MeetingDataStore.GHSalesTableData} columns={targetTableProps.salesColumns} loading={MeetingDataStore.GHSalesLoading} />
<Divider orientation="right" plain>
<span>GH: 客服</span>
<TableExportBtn label={`${formValuesToSub.Date1}-GH: 客服`} {...{ columns: targetTableProps.serviceColumns, dataSource: MeetingDataStore.GHServiceTableData }} />
</Divider>
<Table {...targetTableProps} key={'GHService'} dataSource={MeetingDataStore.GHServiceTableData} columns={targetTableProps.serviceColumns} loading={MeetingDataStore.GHServiceLoading} />
</>
);
});

@ -1,134 +0,0 @@
import { useContext } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Table, Row, Col, Divider, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { fixTo2Decimals } from './../utils/commons';
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals(number / (1000 * scale)) + '';
};
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
export default observer((props) => {
const { date_picker_store: searchFormStore, MeetingData2025Store: MeetingDataStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const dataRefresh = async (obj) => {
MeetingDataStore.dataGHOrder({ ...(obj || formValuesToSub) });
MeetingDataStore.dataGHSales({ ...(obj || formValuesToSub) });
MeetingDataStore.dataGHService({ ...(obj || formValuesToSub) });
};
const targetTableProps = {
loading: MeetingDataStore.GHLoading,
// sticky: true,
scroll: { x: 1000, y: 400 },
pagination: false,
components: { body: { cell: TdCell } },
orderColumns: [
{ key: 'label', title: '市场:', dataIndex: 'label', width: 150 },
{
key: 'LineClass_Origin',
titleX: '网站',
title: () => (
<>
网站{' '}
<Tooltip title="网前自然订单">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'LineClass_Origin',
},
{
key: 'external',
titleX: '站外渠道',
title: () => (
<>
站外渠道{' '}
<Tooltip title="Facebook, Pinterest, Youtube, Instagram">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'external',
},
{ key: 'LineClass_PPC', title: 'PPC', dataIndex: 'LineClass_PPC' },
{ key: 'toB', title: 'To B', dataIndex: 'toB' },
{ key: 'isOld1', title: 'C老客户', dataIndex: 'isOld1' },
{ key: 'total', title: '合计', dataIndex: 'total' },
{ key: 'rowYear', title: '截至年订单数', dataIndex: 'rowYear' },
// { key: 'groupsLabel2', title: '', dataIndex: 'groupsLabel2' },
// { key: 'groupsLabel2', title: '', dataIndex: 'groupsLabel2' },
],
salesColumns: [
{ key: 'label', title: '顾问:', dataIndex: 'label', width: 150 },
{ key: 'CJCount', title: '成交个数', dataIndex: 'CJCount' },
{ key: 'YJLY', title: '成交毛利(万)', dataIndex: 'YJLY', render: (text) => numberConvert10K(text) },
{ key: 'CJCount1', title: '年成交个数', dataIndex: ['rowYear', 'CJCount'] },
{ key: 'YJLY1', title: '年成交毛利(万)', dataIndex: ['rowYear', 'YJLY'], render: (text) => numberConvert10K(text) },
{ key: 'YJLY2', title: '年走团毛利(万)', dataIndex: ['rowYear', 'YJLY2'], render: (text) => numberConvert10K(text) },
],
serviceColumns: [
{ key: 'label', title: '客服:', dataIndex: 'label', width: 150 },
{ key: 'GroupCount', title: '走团个数', dataIndex: 'GroupCount' },
// { key: 'GoodCount', title: '', dataIndex: 'GoodCount' },
{ key: 'GroupCount1', title: '年走团个数', dataIndex: ['rowYear', 'GroupCount'] },
// { key: 'GoodCount2', title: '', dataIndex: ['rowYear', 'GoodCount'] },
],
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...MeetingDataStore.searchValues,
// ...searchInitial,
},
shows: ['IncludeTickets', 'dates'], // 'country'
fieldProps: {
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
MeetingDataStore.setSearchValues(form);
dataRefresh(obj);
}}
/>
</Col>
</Row>
<h2>GH例会数据 2025</h2>
<Divider orientation="left" plain>
<span>GH: 市场</span>
<TableExportBtn label={`${formValuesToSub.Date1}-GH: 市场`} {...{ columns: targetTableProps.orderColumns, dataSource: MeetingDataStore.GHTableData }} style={{ marginLeft: 'auto' }} />
</Divider>
<Table {...targetTableProps} key={'GHTable'} dataSource={MeetingDataStore.GHTableData} columns={targetTableProps.orderColumns} loading={MeetingDataStore.GHTableLoading} />
<Divider orientation="left" plain>
<span>GH: 顾问成交</span>
<TableExportBtn label={`${formValuesToSub.Date1}-GH: 顾问成交`} {...{ columns: targetTableProps.salesColumns, dataSource: MeetingDataStore.GHSalesTableData }} />
</Divider>
<Table {...targetTableProps} key={'GHSales'} dataSource={MeetingDataStore.GHSalesTableData} columns={targetTableProps.salesColumns} loading={MeetingDataStore.GHSalesLoading} />
<Divider orientation="left" plain>
<span>GH: 客服</span>
<TableExportBtn label={`${formValuesToSub.Date1}-GH: 客服`} {...{ columns: targetTableProps.serviceColumns, dataSource: MeetingDataStore.GHServiceTableData }} />
</Divider>
<Table {...targetTableProps} key={'GHService'} dataSource={MeetingDataStore.GHServiceTableData} columns={targetTableProps.serviceColumns} loading={MeetingDataStore.GHServiceLoading} />
</>
);
});

@ -1,17 +1,18 @@
import React, { Component } from "react";
import { Row, Col, Tabs, Table, Divider, Spin } from "antd";
import { ContainerOutlined, BlockOutlined, SmileOutlined, TagsOutlined, GlobalOutlined, FullscreenOutlined, DingtalkOutlined, CarryOutOutlined, CoffeeOutlined, ClockCircleOutlined, HeartOutlined, IdcardOutlined, ContactsOutlined } from "@ant-design/icons";
import { Row, Col, Button, Tabs, Table, Divider, Select, Radio } from "antd";
import { ContainerOutlined, CarryOutOutlined, BlockOutlined, SmileOutlined, TagsOutlined, GlobalOutlined, SearchOutlined, FullscreenOutlined, DingtalkOutlined } from "@ant-design/icons";
import { stores_Context } from "../config";
import { Line, Pie } from "@ant-design/charts";
import SiteSelect from "../components/search/SiteSelect";
import GroupSelect from "../components/search/GroupSelect";
import DataTypeSelect from "../components/search/DataTypeSelect";
import { observer } from "mobx-react";
import DatePickerCharts from "../components/search/DatePickerCharts";
import * as config from "../config";
import { NavLink } from "react-router-dom";
import * as comm from "../utils/commons";
import { utils, writeFileXLSX } from "xlsx";
import DateGroupRadio from '../components/DateGroupRadio';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
class Orders extends Component {
static contextType = stores_Context;
@ -28,92 +29,81 @@ class Orders extends Component {
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
//
result.columns = [
{
title: '#',
fixed: 'left',
children: [
{
title: (
<span>
<div>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
</div>
<div>
{date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~{date_picker_store.end_date_cp.format(config.DATE_FORMAT)}
</div>
</span>
),
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) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
},
],
},
{
title: '数量',
children: [
{
title: comm.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: comm.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: comm.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: comm.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: comm.show_vs_tag(ordercountTotal1.YJLY_vs, ordercountTotal1.YJLY_diff, ordercountTotal1.YJLY, ordercountTotal2.YJLY),
titleX: [ordercountTotal1.YJLY, ordercountTotal2.YJLY].join(' vs '),
dataIndex: 'YJLY',
},
],
},
{
title: "",
children: [
{
title: (
<span>
<div>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
</div>
<div>
{date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~{date_picker_store.end_date_cp.format(config.DATE_FORMAT)}
</div>
</span>
),
dataIndex: "OrderType",
render: (text, record) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${record.OrderType}`}>{text}</NavLink>,
},
],
},
{
title: "数量",
children: [
{
title: comm.show_vs_tag(ordercountTotal1.OrderCount_vs, ordercountTotal1.OrderCount_diff, ordercountTotal1.OrderCount, ordercountTotal2.OrderCount),
dataIndex: "OrderCount",
},
],
},
{
title: "成交数",
children: [
{
title: comm.show_vs_tag(ordercountTotal1.CJCount_vs, ordercountTotal1.CJCount_diff, ordercountTotal1.CJCount, ordercountTotal2.CJCount),
dataIndex: "CJCount",
},
],
},
{
title: "成交人数",
children: [
{
title: comm.show_vs_tag(ordercountTotal1.CJPersonNum_vs, ordercountTotal1.CJPersonNum_diff, ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum),
dataIndex: "CJPersonNum",
},
],
},
{
title: "成交率",
children: [
{
title: comm.show_vs_tag(ordercountTotal1.CJrate_vs, ordercountTotal1.CJrate_diff, ordercountTotal1.CJrate, ordercountTotal2.CJrate),
dataIndex: "CJrate",
},
],
},
{
title: "成交毛利(预计)",
children: [
{
title: comm.show_vs_tag(ordercountTotal1.YJLY_vs, ordercountTotal1.YJLY_diff, ordercountTotal1.YJLY, ordercountTotal2.YJLY),
dataIndex: "YJLY",
},
],
},
{
title: '单个订单价值',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.Ordervalue_vs, ordercountTotal1.Ordervalue_diff, ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue),
titleX: [ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue].join(' vs '),
dataIndex: 'Ordervalue',
},
],
},
];
{
title: "单个订单价值",
children: [
{
title: comm.show_vs_tag(ordercountTotal1.Ordervalue_vs, ordercountTotal1.Ordervalue_diff, ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue),
dataIndex: "Ordervalue",
},
],
},
];
// 1.OrderType 2.OrderType
let has_same_type = false; // 12
for (const item of data.ordercount1) {
@ -127,17 +117,11 @@ class Orders extends Component {
OrderType: item.OrderType,
OrderTypeSN: item.OrderTypeSN,
OrderCount: comm.show_vs_tag(item.OrderCount_vs, item.OrderCount_diff, item.OrderCount, item2.OrderCount),
OrderCount_X: ([item.OrderCount, item2.OrderCount].join(' vs ')),
CJCount: comm.show_vs_tag(item.CJCount_vs, item.CJCount_diff, item.CJCount, item2.CJCount),
CJCount_X: ([item.CJCount, item2.CJCount].join(' vs ')),
CJPersonNum: comm.show_vs_tag(item.CJPersonNum_vs, item.CJPersonNum_diff, item.CJPersonNum, item2.CJPersonNum),
CJPersonNum_X: ([item.CJPersonNum, item2.CJPersonNum].join(' vs ')),
CJrate: comm.show_vs_tag(item.CJrate_vs, item.CJrate_diff, item.CJrate, item2.CJrate),
CJrate_X: ([item.CJrate, item2.CJrate].join(' vs ')),
YJLY: comm.show_vs_tag(item.YJLY_vs, item.YJLY_diff, item.YJLY, item2.YJLY),
YJLY_X: ([item.YJLY, item2.YJLY].join(' vs ')),
Ordervalue: comm.show_vs_tag(item.Ordervalue_vs, item.Ordervalue_diff, item.Ordervalue, item2.Ordervalue),
Ordervalue_X: ([item.Ordervalue, item2.Ordervalue].join(' vs ')),
});
}
}
@ -148,17 +132,11 @@ class Orders extends Component {
OrderType: item.OrderType,
OrderTypeSN: item.OrderTypeSN,
OrderCount: comm.show_vs_tag(comm.formatPercent(item.OrderCount), item.OrderCount, item.OrderCount, 0),
OrderCount_X: ([item.OrderCount, 0].join(' vs ')),
CJCount: comm.show_vs_tag(comm.formatPercent(item.CJCount), item.CJCount, item.CJCount, 0),
CJCount_X: ([item.CJCount, 0].join(' vs ')),
CJPersonNum: comm.show_vs_tag(comm.formatPercent(item.CJPersonNum), item.CJPersonNum, item.CJPersonNum, 0),
CJPersonNum_X: ([item.CJPersonNum, 0].join(' vs ')),
CJrate: comm.show_vs_tag(item.CJrate, item.CJrate, item.CJrate, 0),
CJrate_X: ([item.CJrate, 0].join(' vs ')),
YJLY: comm.show_vs_tag(comm.formatPercent(item.YJLY), item.YJLY, item.YJLY, 0),
YJLY_X: ([item.YJLY, 0].join(' vs ')),
Ordervalue: comm.show_vs_tag(comm.formatPercent(item.Ordervalue), item.Ordervalue, item.Ordervalue, 0),
Ordervalue_X: ([item.Ordervalue, 0].join(' vs ')),
});
}
}
@ -176,25 +154,18 @@ class Orders extends Component {
OrderType: item2.OrderType,
OrderTypeSN: item2.OrderTypeSN,
OrderCount: comm.show_vs_tag(comm.formatPercent(-item2.OrderCount), -item2.OrderCount, 0, item2.OrderCount),
OrderCount_X: ([ 0, item2.OrderCount].join(' vs ')),
CJCount: comm.show_vs_tag(comm.formatPercent(-item2.CJCount), -item2.CJCount, 0, item2.CJCount),
CJCount_X: ([ 0, item2.CJCount].join(' vs ')),
CJPersonNum: comm.show_vs_tag(comm.formatPercent(-item2.CJPersonNum), -item2.CJPersonNum, 0, item2.CJPersonNum),
CJPersonNum_X: ([0, item2.CJPersonNum].join(' vs ')),
CJrate: comm.show_vs_tag(-item2.CJrate, -item2.CJrate, 0, item2.CJrate),
CJrate_X: ([ 0, item2.CJrate].join(' vs ')),
YJLY: comm.show_vs_tag(comm.formatPercent(-item2.YJLY), -item2.YJLY, 0, item2.YJLY),
YJLY_X: ([0, item2.YJLY].join(' vs ')),
Ordervalue: comm.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',
title: "",
children: [
{
title: (
@ -204,10 +175,8 @@ class Orders extends Component {
</div>
</span>
),
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) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
dataIndex: "OrderType",
render: (text, record) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${record.OrderType}`}>{text}</NavLink>,
},
],
},
@ -250,7 +219,7 @@ class Orders extends Component {
}
render() {
const { orders_store, date_picker_store } = this.context;
const { orders_store } = this.context;
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);
@ -318,7 +287,7 @@ class Orders extends Component {
return ret;
},
},
// smooth: true,
smooth: true,
};
const pie_config = {
appendPadding: 10,
@ -341,44 +310,55 @@ class Orders extends Component {
],
};
const tableProps = {
dataSource: table_data.dataSource,
columns: table_data.columns,
size: 'small',
pagination: false,
scroll: { x: (100*(table_data.columns.length)) },
loading: orders_store.loading,
};
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"} >
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...orders_store.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
years: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
orders_store.setSearchValues(obj, form);
orders_store.getOrderCount();
orders_store.onChange_Tabs(orders_store.active_tab_key);
}}
/>
</Col>
</Row>
<Row gutter={[16, { sm: 16, lg: 32 }]}>
<Col span={24} style={{ textAlign: 'right' }}>
return (
<div>
<Row gutter={{ sm: 16, lg: 32 }}>
<Col md={24} lg={12} xxl={14}></Col>
<Col md={24} lg={12} xxl={10}>
<Row>
<Col md={24} lg={8} xxl={8}>
<GroupSelect store={orders_store} />
</Col>
<Col md={24} lg={8} xxl={8}>
<SiteSelect store={orders_store} show_all={true} />
</Col>
<Col md={24} lg={8} xxl={8}>
<Select style={{ width: "100%" }} placeholder="是否含门票" value={orders_store.include_tickets} onChange={orders_store.handleChange_include_tickets}>
<Select.Option key="1" value="1">
含门票
</Select.Option>
<Select.Option key="0" value="0">
不含门票
</Select.Option>
</Select>
</Col>
</Row>
<Row>
<Col md={24} lg={8} xxl={8}>
<DataTypeSelect store={orders_store} />
</Col>
<Col md={24} lg={12} xxl={12}>
<DatePickerCharts />
</Col>
<Col md={24} lg={4} xxl={4} className='align_right'>
<Button
type="primary"
icon={<SearchOutlined />}
loading={orders_store.loading}
onClick={() => {
orders_store.getOrderCount();
orders_store.onChange_Tabs(orders_store.active_tab_key);
}}>
统计
</Button>
</Col>
</Row>
</Col>
</Row>
<Row gutter={[16, { sm: 16, lg: 32 }]} >
<Col span={24} style={{textAlign: 'right'}}>
<DateGroupRadio
visible={data_source.length !== 0}
visible={data_source.length!==0}
dataRaw={orders_store.orderCountDataRaw}
onChange={orders_store.onChangeDateGroup}
value={orders_store.lineChartXGroup}
@ -386,126 +366,177 @@ class Orders extends Component {
fieldMapper={orders_store.orderCountDataFieldMapper}
/>
</Col>
<Col span={24}>
<Spin spinning={orders_store.loading}>
<Line {...config} />
</Spin>
</Col>
<Col span={24}>
<Line {...config} />
</Col>
<Col span={24}>
<Tabs
activeKey={orders_store.active_tab_key}
onChange={(active_key) => orders_store.onChange_Tabs(active_key)}
items={[
{
key: 'Form',
label: (
<span>
<ContainerOutlined />
来源类型
</span>
),
},
{
key: 'Product',
label: (
<span>
<CarryOutOutlined />
产品类型
</span>),
},
{
key: 'Country',
label: (
<span>
<SmileOutlined />
国籍
</span>
),
},
{
key: 'line',
label: (
<span>
<TagsOutlined />
线路
</span>
),
},
{
key: 'city',
label: (
<span>
<GlobalOutlined />
目的地
</span>
),
},
{
key: 'LineClass',
label: (
<span>
<BlockOutlined />
页面类型
</span>
),
},
{
key: 'GuestGroupType',
label: (
<span>
<FullscreenOutlined />
客群类别
</span>
),
},
{
key: 'TravelMotivation',
label: (
<span>
<DingtalkOutlined />
出行动机
</span>
),
},
{
key: 'ToB',
label: (
<span>
<ContactsOutlined />
客运类别
</span>
),
},
{key: 'FoodRequirement',label: (<span><CoffeeOutlined />饮食要求</span>),},
{key: 'hobbies',label: (<span><HeartOutlined/>兴趣爱好</span>),},
{key: 'ages',label: (<span><IdcardOutlined/>年龄段</span>),},
].map((ele) => {
return {
...ele,
children: (
<>
<Table sticky id={`table_to_xlsx_${ele.key}`} {...tableProps} />
<Divider orientation="right" plain>
<TableExportBtn label={ele.key} {...{ columns: tableProps.columns, dataSource: tableProps.dataSource }} />
</Divider>
</>
),
};
})}
/>
<Row>
<Col sm={24} lg={12}>
<Pie {...pie_config} data={pie_data} />
</Col>
<Col sm={24} lg={12}>
<Pie {...pie_config} data={pie_data2} />
</Col>
</Row>
</Col>
</Row>
</div>
);
<Col span={24}>
<Tabs activeKey={orders_store.active_tab_key} onChange={active_key => orders_store.onChange_Tabs(active_key)}>
<Tabs.TabPane
tab={
<span>
<ContainerOutlined />
来源类型
</span>
}
key="Form">
<Table id="table_to_xlsx_form" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_form").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "来源类型.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<CarryOutOutlined />
产品类型
</span>
}
key="Product">
<Table id="table_to_xlsx_product" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_product").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "产品类型.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<SmileOutlined />
国籍
</span>
}
key="Country">
<Table id="table_to_xlsx_country" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_country").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "国籍.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<TagsOutlined />
线路
</span>
}
key="line">
<Table id="table_to_xlsx_line" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_line").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "线路.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<GlobalOutlined />
目的地
</span>
}
key="city">
<Table id="table_to_xlsx_city" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_city").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "目的地.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<BlockOutlined />
页面类型
</span>
}
key="LineClass">
<Table id="table_to_xlsx_LineClass" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_LineClass").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "页面类型.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<FullscreenOutlined />
客群类别
</span>
}
key="GuestGroupType">
<Table id="table_to_xlsx_GuestGroupType" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_GuestGroupType").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "客群类别.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<DingtalkOutlined />
出行动机
</span>
}
key="TravelMotivation">
<Table id="table_to_xlsx_TravelMotivation" dataSource={table_data.dataSource} columns={table_data.columns} size="small" pagination={false} scroll={{ x: "100%" }} />
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_TravelMotivation").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "出行动机.xlsx");
}}>
导出excel
</a>
</Divider>
</Tabs.TabPane>
</Tabs>
<Row>
<Col sm={24} lg={12}>
<Pie {...pie_config} data={pie_data} />
</Col>
<Col sm={24} lg={12}>
<Pie {...pie_config} data={pie_data2} />
</Col>
</Row>
</Col>
</Row>
</div>
);
}
}

@ -1,16 +1,18 @@
import React, { useContext, useEffect, useState } from "react";
import { Row, Col, Tabs, Table, Divider } from "antd";
import { ContainerOutlined } from "@ant-design/icons";
import React, { useContext, useEffect } from "react";
import { Row, Col, Button, Tabs, Table, Select, Divider } from "antd";
import { ContainerOutlined, SearchOutlined } from "@ant-design/icons";
import { stores_Context } from "../config";
import { Line } from "@ant-design/charts";
import { observer } from "mobx-react";
import DatePickerCharts from "../components/search/DatePickerCharts";
import SiteSelect from "../components/search/SiteSelect";
import GroupSelect from "../components/search/GroupSelect";
import DataTypeSelect from "../components/search/DataTypeSelect";
import { NavLink, useParams } from "react-router-dom";
import * as comm from "../utils/commons";
import * as config from "../config";
import { utils, writeFileXLSX } from "xlsx";
import DateGroupRadio from '../components/DateGroupRadio';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
const Orders_sub = () => {
const { ordertype, ordertype_sub, ordertype_title } = useParams();
@ -149,8 +151,8 @@ const Orders_sub = () => {
},
{
title: "出发日期",
dataIndex: "CGI_ArriveDate",
key: "CGI_ArriveDate",
dataIndex: "COLI_OrderStartDate",
key: "COLI_OrderStartDate",
},
{
title: "客人需求",
@ -171,163 +173,178 @@ const Orders_sub = () => {
return result;
};
//
const addLineBreaksAtCommas = (text) => {
if (!text) return '';
return text.replace(/&amp;/g, "&").replace(//g, '\n').replace(/,/g, ',\n').replace(//g, '\n').replace(/;/g, ';\n');
};
const table_data = format_data_detail(orders_store.orderCountData_Form_sub.ordercount1);
const table_data2 = format_data_detail(orders_store.orderCountData_Form_sub.ordercount2);
const table_data_p = format_data(orders_store.orderCountData_Form_sub.ordercount1);
const table_data2_p = format_data(orders_store.orderCountData_Form_sub.ordercount2);
return (
<div>
<Row gutter={{ sm: 16, lg: 32 }}>
<Col md={24} lg={12} xxl={14}>
<NavLink to={`/orders`}>返回</NavLink>
</Col>
<Col md={24} lg={12} xxl={10}>
<Row>
<Col md={24} lg={8} xxl={8}>
<GroupSelect store={orders_store} />
</Col>
<Col md={24} lg={8} xxl={8}>
<SiteSelect store={orders_store} show_all={true} />
</Col>
<Col md={24} lg={8} xxl={8}>
<Select style={{ width: "100%" }} placeholder="是否含门票" value={orders_store.include_tickets} onChange={orders_store.handleChange_include_tickets}>
<Select.Option key="1" value="1">
含门票
</Select.Option>
<Select.Option key="0" value="0">
不含门票
</Select.Option>
</Select>
</Col>
</Row>
<Row>
<Col md={24} lg={8} xxl={8}>
<DataTypeSelect store={orders_store} />
</Col>
<Col md={24} lg={12} xxl={12}>
<DatePickerCharts />
</Col>
<Col md={24} lg={4} xxl={4} className="align_right">
<Button
type="primary"
icon={<SearchOutlined />}
loading={orders_store.loading}
onClick={() => {
orders_store.getOrderCount_type(ordertype, ordertype_sub);
orders_store.getOrderCountByType_sub(ordertype, ordertype_sub, orders_store.active_tab_key_sub);
}}>
统计
</Button>
</Col>
</Row>
</Col>
</Row>
const tab_items = [
{
key: 'detail', label: <span><ContainerOutlined />订单内容</span>, title: '订单内容',
children: (<Row>
<Col span={24}>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
<Table
id="table_to_xlsx_form"
dataSource={table_data.dataSource}
columns={table_data.columns}
size="small"
pagination={false}
rowKey={record => record.key}
expandable={{
expandedRowRender: record => (
<pre style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', fontSize: '16px' }}>
<Divider orientation="left" plain>
客户需求
</Divider>
{record.COLI_CustomerRequest}
<Divider orientation="left" plain>
订单内容
</Divider>
{addLineBreaksAtCommas(record.COLI_OrderDetailText)}
</pre>
),
}}
/>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_form").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "订单列表.xlsx");
}}>
导出excel
</a>
</Divider>
</Col>
<Col span={24}>
{date_picker_store.start_date_cp ? date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "~" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) : ""}
<Table
dataSource={table_data2.dataSource}
columns={table_data2.columns}
size="small"
rowKey={record => record.key}
expandable={{
expandedRowRender: record => <pre style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', fontSize: '16px' }}>{record.COLI_OrderDetailText}</pre>,
}}
/>
</Col>
</Row>),
},
{ key: 'page', label: <span><ContainerOutlined />访问路径</span>, title: '访问路径',children: (<Row>
<Col span={24}>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
<Table dataSource={table_data_p.dataSource} rowKey={record => record.key} columns={table_data_p.columns} size="small" />
</Col>
<Col span={24}>
{date_picker_store.start_date_cp ? date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "~" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) : ""}
<Table dataSource={table_data2_p.dataSource} rowKey={record => record.key} columns={table_data2_p.columns} size="small" />
</Col>
</Row>)},
{ key: 'page_cxstate1', label: <span><ContainerOutlined />访问路径成行</span>, title: '访问路径(成行)',children: (<Row>
<Col span={24}>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
<Table dataSource={table_data_p.dataSource} rowKey={record => record.key} columns={table_data_p.columns} size="small" />
</Col>
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={24} style={{textAlign: 'right'}}>
<DateGroupRadio
visible={data_source.length!==0}
dataRaw={orders_store.orderCountDataRaw_type}
onChange={orders_store.onChangeDateGroupSub}
value={orders_store.orderCount_type_dateRadio.lineChartXGroup}
dataMapper={orders_store.orderCount_type_dateRadio.orderCountDataMapper}
fieldMapper={orders_store.orderCount_type_dateRadio.orderCountDataFieldMapper}
/>
</Col>
<Col className="gutter-row" span={24}>
<Line {...line} />
</Col>
<Col span={24}>
{date_picker_store.start_date_cp ? date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "~" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) : ""}
<Table dataSource={table_data2_p.dataSource} rowKey={record => record.key} columns={table_data2_p.columns} size="small" />
</Col>
</Row>)},
];
<Col className="gutter-row" span={24}>
<Tabs activeKey={orders_store.active_tab_key_sub} onChange={active_key => orders_store.onChange_Tabs_sub(ordertype, ordertype_sub, active_key)}>
<Tabs.TabPane
tab={
<span>
<ContainerOutlined />
订单内容
</span>
}
key="detail">
<Row>
<Col span={24}>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
<Table
id="table_to_xlsx_form"
dataSource={table_data.dataSource}
columns={table_data.columns}
size="small"
pagination={false}
rowKey={record => record.key}
expandable={{
expandedRowRender: record => (
<pre>
<Divider orientation="left" plain>
客户需求
</Divider>
{record.COLI_CustomerRequest}
<Divider orientation="left" plain>
订单内容
</Divider>
{record.COLI_OrderDetailText}
</pre>
),
}}
/>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById("table_to_xlsx_form").getElementsByTagName("table")[0]);
writeFileXLSX(wb, "订单列表.xlsx");
}}>
导出excel
</a>
</Divider>
</Col>
const [propsForExport, setPropsForExport] = useState(tab_items[0]);
const tabItemsMapped = tab_items.reduce((r, v) => ({...r, [v.key]: v}), {});
const onTabsChange = (active_key) => {
setPropsForExport(tabItemsMapped[active_key]);
orders_store.onChange_Tabs_sub(ordertype, ordertype_sub, active_key);
};
return (
<div>
<Row gutter={{ sm: 16, lg: 32 }} className={date_picker_store.siderBroken ? "" : "sticky-top"}>
<Col md={24} lg={12} xxl={14}>
<NavLink to={`/orders`}>返回</NavLink>
</Col>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
// dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
orders_store.setSearchValues(obj, form);
orders_store.getOrderCount_type(ordertype, ordertype_sub);
orders_store.getOrderCountByType_sub(ordertype, ordertype_sub, orders_store.active_tab_key_sub);
}}
/>
</Col>
</Row>
<Col span={24}>
{date_picker_store.start_date_cp ? date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "~" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) : ""}
<Table
dataSource={table_data2.dataSource}
columns={table_data2.columns}
size="small"
rowKey={record => record.key}
expandable={{
expandedRowRender: record => <pre>{record.COLI_OrderDetailText}</pre>,
}}
/>
</Col>
</Row>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<ContainerOutlined />
访问路径
</span>
}
key="page">
<Row>
<Col span={24}>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
<Table dataSource={table_data_p.dataSource} rowKey={record => record.key} columns={table_data_p.columns} size="small" />
</Col>
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={24} style={{ textAlign: 'right' }}>
<DateGroupRadio
visible={data_source.length !== 0}
dataRaw={orders_store.orderCountDataRaw_type}
onChange={orders_store.onChangeDateGroupSub}
value={orders_store.orderCount_type_dateRadio.lineChartXGroup}
dataMapper={orders_store.orderCount_type_dateRadio.orderCountDataMapper}
fieldMapper={orders_store.orderCount_type_dateRadio.orderCountDataFieldMapper}
/>
</Col>
<Col className="gutter-row" span={24}>
<Line {...line} />
</Col>
<Col span={24}>
{date_picker_store.start_date_cp ? date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "~" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) : ""}
<Table dataSource={table_data2_p.dataSource} rowKey={record => record.key} columns={table_data2_p.columns} size="small" />
</Col>
</Row>
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<ContainerOutlined />
访问路径成行
</span>
}
key="page_cxstate1">
<Row>
<Col span={24}>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
<Table dataSource={table_data_p.dataSource} rowKey={record => record.key} columns={table_data_p.columns} size="small" />
</Col>
<Col className="gutter-row" span={24}>
<Tabs
activeKey={orders_store.active_tab_key_sub}
onChange={onTabsChange}
tabBarExtraContent={{
right: (
<TableExportBtn
label={propsForExport.title}
{...(orders_store.active_tab_key_sub === 'detail'
? { columns: table_data.columns, dataSource: table_data.dataSource }
: { columns: table_data_p.columns, dataSource: table_data_p.dataSource })}
/>
),
}}
items={tab_items}
/>
</Col>
</Row>
</div>
);
<Col span={24}>
{date_picker_store.start_date_cp ? date_picker_store.start_date_cp.format(config.DATE_FORMAT) + "~" + date_picker_store.end_date_cp.format(config.DATE_FORMAT) : ""}
<Table dataSource={table_data2_p.dataSource} rowKey={record => record.key} columns={table_data2_p.columns} size="small" />
</Col>
</Row>
</Tabs.TabPane>
</Tabs>
</Col>
</Row>
</div>
);
};
export default observer(Orders_sub);

@ -1,87 +1,36 @@
import { useContext } from 'react';
import { Button, Result, message, Typography, Image } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import { Outlet, useLocation } from 'react-router-dom';
import authExample from './../auth-apply.png';
import React, {useContext, useEffect} from 'react';
import {Row, Col, Button, Tabs, Spin, Result, Space} from 'antd';
import {
ContainerOutlined,
SearchOutlined,
} from '@ant-design/icons';
import {stores_Context} from '../config';
import {Line} from "@ant-design/charts";
import {observer} from 'mobx-react';
import DatePickerCharts from '../components/search/DatePickerCharts';
import {NavLink, useParams,Outlet, useOutlet, useLocation, useNavigate} from "react-router-dom";
import * as comm from "../utils/commons";
import * as config from "../config";
const { Text } = Typography;
const ProtectedRoute = ({auth}) => {
const {auth_store} = useContext(stores_Context);
const ProtectedRoute = ({ auth }) => {
const { auth_store } = useContext(stores_Context);
if (auth_store.has_permission(auth)) {
return <Outlet/>;
}
if (auth_store.has_permission(auth)) {
return <Outlet />;
}
return (
<div>
<Result
status="403"
title="403 权限不足"
subTitle={"试着联系一下技术,所需权限: "+auth.toString()}
extra=''
/>
</div>
);
const authApplyLink =
// eslint-disable-next-line max-len
'dingtalk://dingtalkclient/action/openapp?app_id=-4&container_type=work_platform&corpid=ding48bce8fd3957c96b&ddtab=true&redirect_type=jump&redirect_url=https%3A%2F%2Faflow.dingtalk.com%2Fdingtalk%2Fmobile%2Fhomepage.htm%3Fback_control%3Dfalse%26backcontrol%3Dfalse%26corpid%3Dding48bce8fd3957c96b%26dd_progress%3Dfalse%26dd_share%3Dfalse%26ddtab%3Dtrue%26showmenu%3Dfalse%23%2Fcustom%3Fpcredirect%3Dself%26processCode%3DPROC-C0C2E970-1E4E-44CF-A389-C7D3F10A7885';
const copyToClipboard = (text) => {
navigator.clipboard.writeText(text).then(() => {
message.success('已复制到剪贴板');
});
};
const { pathname } = useLocation();
const applyInfo = `授权账户: ${auth_store.user.name}(${auth_store.user.userid})\n申请权限: ${auth[auth.length - 1].toString()}\n请求页面: ${pathname}`;
return (
<div>
{/* '试着联系一下技术,所需权限: ' + auth.toString() */}
<Result
status={auth_store.user.name === 'loading' ? '500' : '403'}
title={auth_store.user.name === 'loading' ? '无服务' : '403 权限不足'}
// title="403 "
subTitle={
auth_store.user.name !== 'loading' ? (
<>
<div style={{ width: 300, textAlign: 'left', margin: 'auto auto' }}>
<div>
<Text type={'danger'} strong>
申请步骤:
</Text>
</div>
<ol style={{ padding: '0 1rem' }}>
<li>
复制以下信息
{/* <Button type="link" onClick={() => copyToClipboard(applyInfo)}>
复制
</Button> */}
</li>
{/* <li> */}
<pre className={'p-s1'} style={{ border: '1px solid rgba(0,0,0,.15)', borderRadius: 4 }}>
{applyInfo}
</pre>
{/* </li> */}
<li>
<Button type="link" href={authApplyLink}>
点击打开 &raquo;OA审批 &raquo;
</Button>
<ul>
<li>输入申请信息(姓名等)</li>
<li>
选择开通权限: <Text type={'warning'}>HT系统分析</Text>
</li>
<li>
填入上述复制的信息到 <Text type={'warning'}>权限内容</Text>
</li>
</ul>
</li>
</ol>
<div>
<Text type={'secondary'} strong>
示例:
</Text>
</div>
<Image alt="example" src={authExample} preview={false} />
</div>
</>
) : null
}
/>
</div>
);
};
export default observer(ProtectedRoute);

@ -1,13 +1,17 @@
import React, { useContext } from 'react';
import { Row, Col, Tabs, Table, Divider, Spin } from 'antd';
import { ContainerOutlined, UserSwitchOutlined } from '@ant-design/icons';
import React, { useContext, useEffect } from 'react';
import { Row, Col, Button, Tabs, Table, Divider, Radio, Select } from 'antd';
import { ContainerOutlined, SearchOutlined, UserSwitchOutlined } from '@ant-design/icons';
import { stores_Context } from '../config';
import { Column, Pie } from '@ant-design/charts';
import { Column, Pie, Treemap } from '@ant-design/charts';
import { observer } from 'mobx-react';
import DatePickerCharts from '../components/search/DatePickerCharts';
import DataTypeSelect from '../components/search/DataTypeSelect';
import { NavLink, useParams } from 'react-router-dom';
import * as comm from '../utils/commons';
import * as config from '../config';
import SiteSelect from '../components/search/SiteSelect';
import GroupSelect from '../components/search/GroupSelect';
import { utils, writeFileXLSX } from 'xlsx';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
const Sale = () => {
const { sale_store, date_picker_store } = useContext(stores_Context);
@ -48,10 +52,6 @@ const Sale = () => {
...config_data,
...{
//seriesField: "OPI_Name",//
columnWidthRatio: 0.28,
// dodgePadding: 1,
// minColumnWidth: 5,
// maxColumnWidth: 15,
label: {
position: 'top',
},
@ -204,35 +204,51 @@ const Sale = () => {
),
},
];
const tableColumns = type_data.columns.map((ele, i) =>
i === 0 ? { ...ele, fixed: 'left', children: (ele?.children || []).map((ele_child, i_child) => (i_child === 0 ? { ...ele_child, fixed: 'left' } : { ...ele_child })) } : { ...ele }
);
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"} >
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...sale_store.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
years: { hide_vs: true },
operator: { mode: 'multiple'},
},
}}
onSubmit={(_err, obj, form, str) => {
sale_store.setSearchValues(obj, form);
sale_store.get_department_order_ml_by_type(date_picker_store);
}}
/>
</Col>
</Row>
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
<Col md={24} lg={12} xxl={14}></Col>
<Col md={24} lg={12} xxl={10}>
<Row>
<Col md={24} lg={8} xxl={8}>
<GroupSelect store={sale_store} />
</Col>
<Col md={24} lg={8} xxl={8}>
<SiteSelect store={sale_store} show_all={true} />
</Col>
<Col md={24} lg={8} xxl={8}>
<Select style={{ width: '100%' }} placeholder="是否含门票" value={sale_store.include_tickets} onChange={sale_store.handleChange_include_tickets}>
<Select.Option key="1" value="1">
含门票
</Select.Option>
<Select.Option key="0" value="0">
不含门票
</Select.Option>
</Select>
</Col>
</Row>
<Row>
<Col md={24} lg={8} xxl={8}>
<DataTypeSelect store={sale_store} />
</Col>
<Col md={24} lg={12} xxl={12}>
<DatePickerCharts />
</Col>
<Col md={24} lg={4} xxl={4} className="align_right">
<Button
type="primary"
icon={<SearchOutlined />}
loading={sale_store.loading}
onClick={() => {
// sale_store.get_department_order_ml(date_picker_store);
sale_store.get_department_order_ml_by_type(date_picker_store);
}}
>
统计
</Button>
</Col>
</Row>
{/* <Row>
<Col md={24} lg={12} xxl={12}>
@ -268,11 +284,12 @@ const Sale = () => {
</Select>
</Col>
</Row> */}
</Col>
</Row>
<Row>
<Col className="gutter-row" md={24}>
<Spin spinning={sale_store.loading_table} >
<Column {...column_config} /></Spin>
<Column {...column_config} />
</Col>
<Col className="gutter-row" md={24}>
@ -286,18 +303,25 @@ const Sale = () => {
></Tabs>
<Row>
<Col span={24}>
<Table sticky
<Table
id="table_to_xlsx_sale"
dataSource={type_data.dataSource}
columns={tableColumns}
columns={type_data.columns}
size="small"
rowKey={(record) => record.key}
loading={sale_store.loading_table}
pagination={false}
scroll={{ x: (100*(tableColumns.length)) }}
scroll={{ x: '100%' }}
/>
<Divider orientation="right" plain>
<TableExportBtn label={'sale'} {...{ columns: tableColumns, dataSource: type_data.dataSource }} />
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx_sale').getElementsByTagName('table')[0]);
writeFileXLSX(wb, 'sale.xlsx');
}}
>
导出excel
</a>
</Divider>
</Col>
<Col sm={24} lg={12}>

@ -1,38 +1,31 @@
import React, { useContext, useState } from 'react';
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd';
import React, { useContext } from 'react';
import { Row, Col, Table, Select, Space, Typography, Progress, Spin } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import * as comm from '../utils/commons';
import SearchForm from './../components/search/SearchForm';
import { dataFieldAlias, overviewGroup } from '../libs/ht';
import { dataFieldAlias } from '../libs/ht';
import Donut from './../components/Donut';
import LineWithKPI from '../components/LineWithKPI';
import { TableExportBtn } from './../components/Data';
const { Text } = Typography;
const overviewGroupKeys = overviewGroup.map(item => item.key);
const Sale_KPI = () => {
const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context);
const { formValues, siderBroken } = searchFormStore;
const { groupType, loading, operator, tableDataSource: dataSource } = sale_store.salesTrade;
const { formValues } = searchFormStore;
const { groupType, loading, operator, } = sale_store.salesTrade;
const yearData = sale_store.salesTrade[groupType].reduce((r, ele) => r.concat(Object.values(ele.mData)), []);
const dataSource = [].concat(sale_store.salesTrade[groupType], operator);
const yearData = Object.values(sale_store.salesTrade[groupType]?.[0]?.mData || {});
const operatorObjects = operator.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel }));
const pageRefresh = async (queryData) => {
const overviewFlag = queryData.DepartmentList.toLowerCase() === 'all'
|| overviewGroupKeys.includes(queryData.DepartmentList.toLowerCase()); // queryData.DepartmentList.toLowerCase().includes(',');
const overviewFlag = queryData.DepartmentList.toLowerCase() === 'all' || queryData.DepartmentList.toLowerCase().includes(',');
const _groupType = overviewFlag ? 'overview' : 'dept';
sale_store.setGroupType(_groupType);
await sale_store.fetchOperatorTradeData(_groupType, { ...queryData, groupDateType: 'year' });
await sale_store.fetchOperatorTradeData('operator', { ...queryData, groupDateType: 'year' });
sale_store.fetchOperatorTradeData(_groupType, { ...queryData, groupDateType: 'year' });
sale_store.fetchOperatorTradeData('operator', { ...queryData, groupDateType: 'year' });
sale_store.setPickSales([]);
sale_store.setTableDataSource(false);
setIfmerge(false);
};
const [ifmerge, setIfmerge] = useState(false);
const monthCol = new Array(12).fill(1).map((_, index) => {
return {
title: `${index + 1}`,
@ -75,13 +68,11 @@ const Sale_KPI = () => {
dataIndex: 'groupsLabel',
editable: false,
width: '7.5em',
fixed: 'left',
},
{
title: '年度',
dataIndex: 'yearValue',
width: '10em',
fixed: 'left',
render: (_, row) => (
<Space direction={'vertical'}>
<div>
@ -109,72 +100,24 @@ const Sale_KPI = () => {
},
...monthCol,
];
const columnsForExport = [
{
title: `#`,
dataIndex: 'groupsLabel',
editable: false,
width: '7.5em',
fixed: 'left',
},
{
title: '--',
dataIndex: 'rowLabel',
width: '10em',
fixed: 'left',
},
{
title: '年度',
dataIndex: 'yearML',
width: '10em',
fixed: 'left',
},
...new Array(12).fill(1).map((_, index) => {
return {
title: `${index + 1}`,
dataIndex: ['mData', `month_${String(index + 1).padStart(2, '0')}`], // , 'SumML'
valueType: 'digit',
width: '7.5em',
};
}),
];
const dataForExport = dataSource.reduce((r, ele) => {
const targetRow = {
groupsLabel: ele.groupsLabel,
rowLabel: '目标',
yearML: ele.yData.MLKPIvalue,
mData: Object.values(ele.mData).reduce((rt, et) => ({ ...rt, [`month_${et.dateVal}`]: et.MLKPIvalue }), {}),
};
const valRow = { groupsLabel: ele.groupsLabel, rowLabel: '完成', yearML: ele.yData.SumML, mData: Object.values(ele.mData).reduce((rt, et) => ({ ...rt, [`month_${et.dateVal}`]: et.SumML }), {}) };
const processRow = {
groupsLabel: ele.groupsLabel,
rowLabel: '进度(%)',
yearML: ele.yData.MLKPIrates,
mData: Object.values(ele.mData).reduce((rt, et) => ({ ...rt, [`month_${et.dateVal}`]: et.MLKPIrates }), {}),
};
r.push(targetRow, valRow, processRow);
return r;
}, []);
const lineConfig = { appendPadding: 10, xField: 'groupDateVal', yField: 'SumML', seriesField: 'groupsLabel', isGroup: true, smooth: false, meta: comm.cloneDeep(dataFieldAlias), };
const lineConfig = { appendPadding: 10, xField: 'groupDateVal', yField: 'SumML', seriesField: 'groupsLabel', isGroup: true, smooth: true, meta: comm.cloneDeep(dataFieldAlias), };
return (
<div>
<Row gutter={16} className={siderBroken ? "" : "sticky-top"}>
<Row gutter={16} style={{ margin: '-16px -8px' }}>
<Col md={24} lg={24} xxl={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...sale_store.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'years'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
DepartmentList: { show_all: true },
WebCode: { show_all: true },
years: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
sale_store.setSearchValues(obj, form);
pageRefresh(obj);
}}
/>
@ -183,65 +126,47 @@ const Sale_KPI = () => {
<Spin spinning={loading}>
<h2 style={{ marginTop: '.5em' }}>年度业绩组成和走势</h2>
<Row>
<Col className="gutter-row" md={8} sm={24} xs={24}>
<Col className="gutter-row" md={8}>
<Donut
{...{ angleField: 'SumML', colorField: 'groupsLabel', label1: { style: { color: '#000000' }, type: 'spider', content: '{name}\n{percentage}' }, legend: false, label2: false }}
title={formValues.DepartmentList?.label}
dataSource={operator.map((row) => ({ ...row, SumML: row.yearML }))}
/>
</Col>
<Col className="gutter-row" md={16} sm={24} xs={24}>
<LineWithKPI dataSource={yearData} showKPI={true} {...lineConfig} {...{ legend: false }} />
<Col className="gutter-row" md={16}>
<LineWithKPI dataSource={yearData} {...lineConfig} {...{legend: false}} />
</Col>
<Col className="gutter-row" md={24}>
<Row gutter={16}>
<Col flex={'12em'}><h2>顾问业绩走势</h2></Col>
<Col flex={'auto'}>
<Select
labelInValue
mode={'multiple'}
style={{ width: '100%' }}
placeholder={'选择顾问'}
onChange={sale_store.setPickSales}
value={sale_store.salesTrade.pickSales}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{operatorObjects.map((ele) => (
<Select.Option key={ele.key} value={ele.value}>
{ele.label}
</Select.Option>
))}
</Select>
</Col>
</Row>
<Space gutter={16} size={'large'}>
<h2>顾问业绩走势</h2>
<Select
labelInValue
mode={'multiple'}
style={{ width: '400px' }}
placeholder={'选择顾问'}
onChange={sale_store.setPickSales}
value={sale_store.salesTrade.pickSales}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{operatorObjects.map((ele) => (
<Select.Option key={ele.key} value={ele.value}>
{ele.label}
</Select.Option>
))}
</Select>
</Space>
</Col>
<Col className="gutter-row" span={24}>
<LineWithKPI dataSource={sale_store.salesTrade.pickSalesData} showKPI={true} {...lineConfig} />
<Col className="gutter-row" md={24}>
<LineWithKPI dataSource={sale_store.salesTrade.pickSalesData} {...lineConfig} />
</Col>
</Row>
<Row>
<Col className="gutter-row" md={24}>
<Divider orientation="right">
{dataSource.length > 0 && (
<Switch
unCheckedChildren="各账户"
checkedChildren="合并"
key={'openOrMerge'}
checked={ifmerge}
onChange={(e) => {
sale_store.setTableDataSource(e);
setIfmerge(e);
}}
/>
)}
<TableExportBtn label={'sales kpi'} {...{ columns: columnsForExport, dataSource: dataForExport }} />
</Divider>
<Table
sticky={{ offsetHeader: 64 }}
sticky
key={`salesTradeTable`}
loading={loading}
columns={columns}

@ -1,10 +1,8 @@
import { useContext, useEffect, useState } from 'react';
import { useContext } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Spin, Table, Row, Col, Tabs, Switch } from 'antd';
import { Spin, Table, Row, Col, Tabs } from 'antd';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { empty } from '../utils/commons';
import './kpi.css';
const apartOptions = [
@ -12,39 +10,33 @@ const apartOptions = [
{ key: 'outbound', value: 'outbound', label: '出境' },
{ key: 'domestic', value: 'domestic', label: '国内' },
];
const apartOptionsMapped = apartOptions.reduce((r, v) => ({...r, [v.value]: v}), {});
export default observer((props) => {
const { financial_store: financialStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { formValues, formValuesToSub } = searchFormStore;
const { servicePersonNum } = financialStore;
const { curTab } = servicePersonNum;
const [ifNull, setIfNull] = useState(false);
useEffect(() => {
// DistributionStore.setFormDates(formValuesToSub);
financialStore.resetPersonNumData();
return () => {};
}, [formValuesToSub]);
const pageRefresh = (queryData = formValuesToSub) => {
// console.log(queryData, 'qqqq');
financialStore.getPersonNum(queryData);
financialStore.setPersonNumTableDataSource(false);
setIfNull(false);
};
const columns = [
{ title: apartOptionsMapped[curTab].label, dataIndex: 'groupsLabel', children: [{ title: `${formValuesToSub.Date1}~${formValuesToSub.Date2.substring(0, 10)}`, dataIndex: 'groupsLabel' }] },
{ title: '名称', dataIndex: 'groupsLabel' },
{
title: '人次数',
children: [{ title: '组织', dataIndex: 'orgz' }, ...(['inbound', 'domestic'].includes(curTab) ? [{ title: '接待', dataIndex: 'hosts' }] : [])],
children: [
{ title: '外联', dataIndex: 'orgz' },
{ title: '接待', dataIndex: 'hosts' },
],
},
{
title: '人天',
children: [{ title: '组织', dataIndex: 'orgzPDays' }, ...(['inbound', 'domestic'].includes(curTab) ? [{ title: '接待', dataIndex: 'hostsPDays' }] : [])],
children: [
{ title: '外联', dataIndex: 'orgzPDays' },
{ title: '接待', dataIndex: 'hostsPDays' },
],
},
];
@ -58,7 +50,7 @@ export default observer((props) => {
...formValues,
DateType: { key: 'startDate', value: 'startDate', label: '走团日期' },
},
shows: ['dates'],
shows: ['DateType', 'dates'],
fieldProps: {
dates: { hide_vs: true },
},
@ -75,37 +67,18 @@ export default observer((props) => {
activeKey={curTab}
onChange={(v) => {
financialStore.setCurTab(v);
if (empty(servicePersonNum[v].dataSource)) {
pageRefresh();
}
}}
tabBarExtraContent={{
right: (
<>
<Switch
unCheckedChildren="原数据"
checkedChildren="去除空"
key={'ifNull'}
checked={ifNull}
onChange={(e) => {
financialStore.setPersonNumTableDataSource(e);
setIfNull(e);
}}
/>
<TableExportBtn label={'服务人数_'+apartOptionsMapped[curTab].label} {...{ columns, dataSource: servicePersonNum[curTab].dataSource }} />
</>
),
pageRefresh();
}}
type="card"
items={apartOptions.map((ele) => {
return {
...ele,
children: (
<Spin spinning={servicePersonNum.loading}>
<Spin spinning={servicePersonNum[curTab].loading}>
<Table
id="table_to_xlsx_sale"
dataSource={servicePersonNum[curTab].dataSource}
rowKey="groupsKey"
rowKey='groupsKey'
columns={columns}
size="small"
loading={servicePersonNum[curTab].loading}

@ -1,19 +0,0 @@
import { observer } from 'mobx-react';
import moment from 'moment';
import { APP_VERSION } from '../config';
import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined } from '@ant-design/icons';
export default observer((props) => {
const compileTime = moment(Number(process.env.REACT_APP_BUILD_TIME)).format('YYYY-MM-DD ddd HH:mm:ss');
return (
<>
<div>
<SketchOutlined /> <AntCloudOutlined /> <SlackOutlined /> <RedditOutlined /> <GithubOutlined />
<div>欢迎! </div>
<div>当前版本: v<span>{APP_VERSION}</span></div>
<div>编译时间: <span>{compileTime}</span></div>
<SketchOutlined /> <AntCloudOutlined /> <SlackOutlined /> <RedditOutlined /> <GithubOutlined />
</div>
</>
);
});

@ -1,334 +0,0 @@
import React, { useContext, useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { Link } from 'react-router-dom';
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../../config';
import moment from 'moment';
import { Row, Col, Table, Select, Spin, Tag } from 'antd';
import SearchForm from '../../components/search/SearchForm';
import MixFieldsDetail from '../../components/MixFieldsDetail';
import { fixTo2Decimals, isEmpty, pick } from '../../utils/commons';
import Column from '../../components/Column';
import { groupsMappedByKey } from '../../libs/ht';
const COLOR_SETS = [
"#FFFFFF",
// "#5B8FF9",
// "#FF6B3B",
"#9FB40F",
"#76523B",
"#DAD5B5",
"#E19348",
"#F383A2",
];
const transparentHex = '1A';
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals((number/(1000*scale)));
};
export default observer((props) => {
const { SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const _formValuesToSub = pick(formValuesToSub, ['DepartmentList', 'WebCode']);
const { searchValues, resetData, results } = SalesCRMDataStore;
const operatorObjects = results.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel }));
// console.log(operatorObjects);
const pageRefresh = async (obj) => {
resetData('results');
const deptList = obj.DepartmentList.split(',');
const includeCH = ['1', '2', '7'].some(ele => deptList.includes(ele));
const includeAH = ['28'].every(ele => deptList.includes(ele));
const includeGH = ['33'].every(ele => deptList.includes(ele));
const otherDept = deptList.filter(ele => !['1', '2', '7', '28', '33'].includes(ele));
const separateParam = [];
if (includeCH) {
const inCH = deptList.filter(k => ['1', '2', '7'].includes(k)).join(',');
separateParam.push({ DepartmentList: inCH, retLabel: 'CH'});
}
if (includeAH) {
separateParam.push({ DepartmentList: '28', retLabel: 'AH'});
}
if (includeGH) {
separateParam.push({ DepartmentList: '33', retLabel: 'GH'});
}
if (!isEmpty(otherDept) && (!isEmpty(includeAH) || !isEmpty(includeCH) || !isEmpty(includeGH))) {
separateParam.push({ DepartmentList: otherDept.join(','), retLabel: otherDept.map(k => groupsMappedByKey[k].label).join(', ') }); // ''
}
if (!includeAH && !includeCH && !includeGH) {
separateParam.push({ DepartmentList: obj.DepartmentList });
}
// console.log('separateParam', separateParam, otherDept);
// console.log('formValuesToSub --- pageRefresh', formValuesToSub.DepartmentList);
// return;
for await (const subParam of separateParam) {
// console.log(subParam);
await SalesCRMDataStore.get90n180Data({
...(obj || _formValuesToSub),
...subParam,
groupType: 'overview',
// groupType: 'dept', // todo:
groupDateType: '',
});
await SalesCRMDataStore.get90n180Data({
...(obj || _formValuesToSub),
...subParam,
groupType: 'operator',
groupDateType: '',
});
}
};
const getFullYearDiagramData = async (obj) => {
// console.log('invoke --- getFullYearDiagramData');
// console.log('formValuesToSub --- getFullYearDiagramData', formValuesToSub.DepartmentList);
await SalesCRMDataStore.getResultData({
..._formValuesToSub,
Date1: moment(obj.date).startOf('year').format(DATE_FORMAT),
Date2: moment(obj.date).endOf('year').format(SMALL_DATETIME_FORMAT),
groupType: 'overview',
// groupType: 'operator',
groupDateType: 'month',
...(obj),
});
};
const getDiagramData = async (obj) => {
// console.log('invoke --- getDiagramData');
// console.log(_formValuesToSub, SalesCRMDataStore.searchValuesToSub);
await SalesCRMDataStore.getResultData({
..._formValuesToSub,
...SalesCRMDataStore.searchValuesToSub,
// Date1: moment().startOf('year').format(DATE_FORMAT),
// Date2: moment().endOf('year').format(SMALL_DATETIME_FORMAT),
// groupType: 'overview',
groupType: 'operator',
groupDateType: '',
...(obj),
});
};
const retPropsMinRatesSet = { 'CH': 12, 'AH': 8, 'default': 10 }; //
/**
* 业绩数据列
* ! 成行率: CH个人成行率<12%, AH<8%
*/
const dataFields = (suffix, colRootIndex) => [
{
key: 'ConfirmRates' + suffix,
title: '成行率',
dataIndex: [suffix, 'ConfirmRates'],
width: '5em',
// CH<12%, AH<8%
render: (val, r) => ({
props: { style: { color: val < (retPropsMinRatesSet?.[r?.retProps || 'default'] || retPropsMinRatesSet.default) ? 'red' : 'green', backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: val ? `${val}%` : '',
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmRates - b[suffix].ConfirmRates),
},
{
key: 'SumML' + suffix,
title: '业绩/万',
dataIndex: [suffix, 'SumML'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: numberConvert10K(_),
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumML - b[suffix].SumML),
},
{
key: 'ConfirmOrder' + suffix,
title: '团数',
dataIndex: [suffix, 'ConfirmOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmOrder - b[suffix].ConfirmOrder),
},
{
key: 'SumOrder' + suffix,
title: '订单数',
dataIndex: [suffix, 'SumOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumOrder - b[suffix].SumOrder),
},
{
key: 'ResumeOrder' + suffix,
title: '老客户团数',
dataIndex: [suffix, 'ResumeOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeOrder - b[suffix].ResumeOrder),
},
{
key: 'ResumeRates' + suffix,
title: '老客户成行率',
dataIndex: [suffix, 'ResumeRates'],
width: '5em',
render: (val, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: val ? `${val}%` : ''
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeRates - b[suffix].ResumeRates),
},
];
const dashboardTableProps = {
pagination: false,
size: 'small',
showSorterTooltip: false,
columns: [
{
title: '',
dataIndex: 'groupsLabel',
key: 'name',
width: '5em',
filterSearch: true,
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
onFilter: (value, record) => record.groupsKey === value || record.groupType === 'overview',
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/sales-crm/risk/sales/${record.groupsKey}`}>{text}</Link>),
},
{
title: () => (<>前90 -30<br/>{searchValues.date90.Date1} {searchValues.date90.Date2}</>),
key: 'date',
children: dataFields('result90', 0),
},
{
title: () => (<>前180 -50<br/>{searchValues.date180.Date1} {searchValues.date180.Date2}</>),
key: 'department',
children: dataFields('result180', 1),
},
],
rowClassName: (record, rowIndex) => {
return record.groupType === 'overview' ? 'ant-tag-blue' : '';
},
};
const columnConfig = {
xField: 'groupsLabel',
yField: 'SumML',
label: { position: 'top' },
};
const [clickColumn, setClickColumn] = useState({});
const [clickColumnTitle, setClickColumnTitle] = useState('');
const onChartItemClick = (colData) => {
// console.log('onChartItemClick', colData);
if (colData.groupType === 'operator') {
// test: 0
return false; // ,
}
setClickColumn(colData);
setClickColumnTitle(moment(colData.groupDateVal).format('YYYY-MM'));
};
const chartsConfig = {
colFields: ['ConfirmOrder', 'SumOrder'],
lineFields: ['SumML', 'ConfirmRates'],
seriesField: null,
xField: 'groupDateVal',
itemClick: onChartItemClick,
};
useEffect(() => {
if (isEmpty(clickColumnTitle)) {
return () => {};
}
getDiagramData({
Date1: moment(clickColumn.groupDateVal).startOf('month').format(DATE_FORMAT),
Date2: moment(clickColumn.groupDateVal).endOf('month').format(SMALL_DATETIME_FORMAT),
groupType: 'operator', // test: overview
groupDateType: '',
});
return () => {};
}, [clickColumnTitle]);
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={24} xxl={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...SalesCRMDataStore.searchValues,
},
shows: ['DepartmentList', 'WebCode', 'date'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
SalesCRMDataStore.setSearchValues(obj, form);
pageRefresh(obj);
getFullYearDiagramData({ groupType: 'overview', ...obj });
}}
/>
</Col>
</Row>
<section>
<Table {...dashboardTableProps} bordered dataSource={[...results.dataSource, ...results.details]} loading={results.loading} sticky />
</section>
<section>
<Row gutter={16}>
<Col flex={'12em'}><h3>每月数据</h3></Col>
<Col flex={'auto'}>
<Select
labelInValue
// mode={'multiple'}
style={{ width: '100%' }}
placeholder={'选择顾问'}
// onChange={sale_store.setPickSales}
// onSelect={}
onChange={(labelInValue) => labelInValue ? getFullYearDiagramData({ groupType: 'operator', opisn: labelInValue.value, }) : false}
onClear={() => getFullYearDiagramData({ groupType: 'overview', opisn: -1, })}
// value={sale_store.salesTrade.pickSales}
// maxTagCount={1}
// maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} ...`}
allowClear={true}
options={operatorObjects}
/>
{/* {operatorObjects.map((ele) => (
<Select.Option key={ele.key} value={ele.value}>
{ele.label}
</Select.Option>
))}
</Select> */}
</Col>
</Row>
<Spin spinning={results.loading}>
{/* 小组每月; x轴: 日期; y轴: [订单数, ...] */}
<MixFieldsDetail {...chartsConfig} dataSource={results.byDate} />
</Spin>
<h3>
点击上方图表的柱状图, 查看当月 <Tag color='orange'>业绩</Tag>数据: <Tag color='orange'>{clickColumnTitle}</Tag>
</h3>
{/* 显示小组的详情: 所有顾问? */}
<Spin spinning={results.loading}>
<Column {...columnConfig} dataSource={results.byOperator} />
</Spin>
{/* <Table columns={[{ title: '', dataIndex: 'date', key: 'date', width: '5em' }, ...dataFields]} bordered size='small' /> */}
</section>
<section>
{/* 月份×小组的详情; x轴: 顾问; y轴: [订单数, ...] */}
{/* <MixFieldsDetail {...chartsConfig} xField={'label'} dataSource={[]} /> */}
</section>
</>
);
});

@ -1,259 +0,0 @@
import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import { observer } from 'mobx-react';
import { stores_Context } from '../../config';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Row, Col, Table, Tooltip } from 'antd';
import SearchForm from '../../components/search/SearchForm';
import { pick } from '../../utils/commons';
const COLOR_SETS = [
"#FFFFFF",
// "#5B8FF9",
// "#FF6B3B",
"#9FB40F",
"#76523B",
"#DAD5B5",
"#E19348",
"#F383A2",
];
const transparentHex = '1A';
export default observer((props) => {
const { SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
// const { formValues, siderBroken } = searchFormStore;
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const _formValuesToSub = pick(formValuesToSub, ['DepartmentList', 'WebCode']);
const { resetData, process } = SalesCRMDataStore;
const operatorObjects = process.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel }));
const pageRefresh = async (obj) => {
resetData('process');
Promise.allSettled([
SalesCRMDataStore.getProcessData({
...(obj || _formValuesToSub),
// ...subParam,
// groupType: 'overview',
groupType: 'dept',
groupDateType: '',
}),
SalesCRMDataStore.getProcessData({
...(obj || _formValuesToSub),
// ...subParam,
groupType: 'operator',
groupDateType: '',
}),
]);
};
const tableSorter = (a, b, colName) => (a.groupType !== 'operator' ? -1 : b.groupType !== 'operator' ? 0 : a[colName] - b[colName]);
const percentageRender = val => val ? `${val}%` : '';
const activityTableProps = {
rowKey: 'groupsKey',
pagination: { pageSize: 10, showSizeChanger: false },
size: 'small',
rowClassName: (record, rowIndex) => {
return record.groupType === 'operator' ? '': 'ant-tag-blue';
},
showSorterTooltip: false,
columns: [
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem',
filterSearch: true,
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/sales-crm/risk/sales/${record.groupsKey}`}>{text}</Link>),
}, // :
{
title: '顾问动作',
key: 'date',
children: [
{ title: () => (
<>
首次响应率24H{' '}
<Tooltip title="达到24H内回复的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender,
sorter: (a, b) => tableSorter(a, b, 'firstTouch24'),
},
{ title: () => (
<>
48H内报价率{' '}
<Tooltip title="达到48H内报价的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender ,
sorter: (a, b) => tableSorter(a, b, 'firstQuote48'), },
{ title: () => (
<>
一次报价率{' '}
<Tooltip title="首次报价的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'quote1', key: 'quote1',render: percentageRender ,
sorter: (a, b) => tableSorter(a, b, 'quote1'), },
{ title: () => (
<>
二次报价率{' '}
<Tooltip title="二次报价的订单数/一次报价后回复数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'quote2', key: 'quote2',render: percentageRender ,
sorter: (a, b) => tableSorter(a, b, 'quote2'), },
{ title: () => (
<>
&gt;50条会话{' '}
<Tooltip title=">50条会话的订单数/总订单">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender ,
sorter: (a, b) => tableSorter(a, b, 'turnsGT50'), },
{ title: () => (
<>
违规数{' '}
<Tooltip title="未遵循24H回复的订单数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'violations', key: 'violations',
sorter: (a, b) => tableSorter(a, b, 'violations'), },
],
},
{
title: '客人回复',
key: 'department',
children: [
{
title: () => (
<>
首次回复率24H{' '}
<Tooltip title="达到24H内回复的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'firstReply24', key: 'firstReply24',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
children: percentageRender(_),
}),
sorter: (a, b) => tableSorter(a, b, 'firstReply24'), },
{
title: () => (
<>
48H内报价回复率{' '}
<Tooltip title="48H内报价后的回复数/报价的订单数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'replyQuote48', key: 'replyQuote48',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
children: percentageRender(_),
}),
sorter: (a, b) => tableSorter(a, b, 'replyQuote48'), },
{
title: () => (
<>
一次报价回复率{' '}
<Tooltip title="一次报价后回复率/订单总数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'replyQuote1', key: 'replyQuote1',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
children: percentageRender(_),
}),
sorter: (a, b) => tableSorter(a, b, 'replyQuote1'), },
{
title: () => (
<>
二次报价回复率{' '}
<Tooltip title="二次报价后回复数/一次报价后回复数">
<InfoCircleOutlined />
</Tooltip>
</>
), dataIndex: 'replyQuote2', key: 'replyQuote2',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
children: percentageRender(_),
}),
sorter: (a, b) => tableSorter(a, b, 'replyQuote2'), },
],
},
],
};
const riskTableProps = {
rowKey: 'groupsKey',
pagination: { pageSize: 10, showSizeChanger: false },
size: 'small',
rowClassName: (record, rowIndex) => {
return record.groupType === 'operator' ? '': 'ant-tag-blue';
},
showSorterTooltip: false,
columns: [
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem',
filterSearch: true,
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/sales-crm/risk/sales/${record.groupsKey}`}>{text}</Link>),
}, // :
{ title: '>24H回复', dataIndex: 'lostTouch24', key: 'lostTouch24',
sorter: (a, b) => tableSorter(a, b, 'lostTouch24'),
},
{ title: '首次报价周期>48h', dataIndex: 'lostQuote48', key: 'lostQuote48',
sorter: (a, b) => tableSorter(a, b, 'lostQuote48'),
},
{ title: '报价次数<1次', dataIndex: 'lostQuote1', key: 'lostQuote1',
sorter: (a, b) => tableSorter(a, b, 'lostQuote1'),
},
{ title: '报价次数<2次', dataIndex: 'lostQuote2', key: 'lostQuote2',
sorter: (a, b) => tableSorter(a, b, 'lostQuote2'),
},
],
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={24} xxl={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...SalesCRMDataStore.searchValues,
},
shows: ['DepartmentList', 'WebCode', 'DateType', 'dates', 'IncludeTickets', 'country'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
dates: { hide_vs: true },
country: { show_all: false, mode: 'multiple', col: 5 },
},
}}
onSubmit={(_err, obj, form, str) => {
SalesCRMDataStore.setSearchValues(obj, form);
pageRefresh(obj);
}}
/>
</Col>
</Row>
<section>
<Table {...activityTableProps} bordered dataSource={[...process.dataSource, ...process.details]} loading={process.loading} sticky />
</section>
<section>
<h3>未成行订单 数量</h3>
<Table {...riskTableProps} bordered dataSource={[...process.dataSource, ...process.details]} loading={process.loading} sticky />
</section>
</>
);
});

@ -1,397 +0,0 @@
import React, { useContext, useEffect } from 'react';
import { NavLink, useParams } from 'react-router-dom';
import { observer } from 'mobx-react';
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../../config';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Row, Col, Table, Divider, Button, Popover, Tooltip } from 'antd';
import { fixTo2Decimals } from '../../utils/commons';
import MixFieldsDetail from '../../components/MixFieldsDetail';
const COLOR_SETS = [
'#FFFFFF',
// "#5B8FF9",
// "#FF6B3B",
'#9FB40F',
'#76523B',
'#DAD5B5',
'#E19348',
'#F383A2',
];
const transparentHex = '1A';
const percentageRender = (val) => (val ? `${val}%` : '');
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals((number/(1000*scale)));
};
const retPropsMinRatesSet = { 'CH': 12, 'AH': 8, 'default': 10 }; //
export default observer((props) => {
const { opisn, opi_name } = useParams();
const { sale_store, SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { formValues, siderBroken } = searchFormStore;
const { searchValues, searchValuesToSub, resetData, results, process, risk } = SalesCRMDataStore;
useEffect(() => {
Promise.allSettled([
//
SalesCRMDataStore.get90n180Data({ opisn, groupType: 'operator', groupDateType: '' }),
// :
SalesCRMDataStore.getResultData({
opisn,
Date1: searchValues.date.clone().startOf('year').format(DATE_FORMAT),
Date2: searchValues.date.clone().endOf('year').format(SMALL_DATETIME_FORMAT),
groupType: 'overview',
// groupType: 'operator',
groupDateType: 'month',
}),
//
SalesCRMDataStore.getProcessData({ opisn, groupType: 'operator', groupDateType: '' }),
//
SalesCRMDataStore.getRiskDetailData({ opisn }),
]);
}, [opisn]);
const dataFields = (suffix, colRootIndex) => [
{
key: 'ConfirmRates' + suffix,
title: '成行率',
dataIndex: [suffix, 'ConfirmRates'],
width: '5em',
// CH<12%, AH<8%
render: (val, r) => ({
props: { style: { color: val < (retPropsMinRatesSet?.[r?.retProps || 'default'] || retPropsMinRatesSet.default) ? 'red' : 'green', backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: val ? `${val}%` : '',
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmRates - b[suffix].ConfirmRates),
},
{
key: 'SumML' + suffix,
title: '业绩/万',
dataIndex: [suffix, 'SumML'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: numberConvert10K(_),
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumML - b[suffix].SumML),
},
{
key: 'ConfirmOrder' + suffix,
title: '团数',
dataIndex: [suffix, 'ConfirmOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmOrder - b[suffix].ConfirmOrder),
},
{
key: 'SumOrder' + suffix,
title: '订单数',
dataIndex: [suffix, 'SumOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumOrder - b[suffix].SumOrder),
},
{
key: 'ResumeOrder' + suffix,
title: '老客户团数',
dataIndex: [suffix, 'ResumeOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeOrder - b[suffix].ResumeOrder),
},
{
key: 'ResumeRates' + suffix,
title: '老客户成行率',
dataIndex: [suffix, 'ResumeRates'],
width: '5em',
render: (val, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: val ? `${val}%` : ''
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeRates - b[suffix].ResumeRates),
},
];
const dashboardTableProps = {
rowKey: 'groupsKey',
pagination: false,
size: 'small',
showSorterTooltip: false,
columns: [
{
title: '',
dataIndex: 'groupsLabel',
key: 'name',
width: '5em',
},
{
title: () => (<>前90 -30<br/>{searchValues.date90.Date1} {searchValues.date90.Date2}</>),
key: 'date',
children: dataFields('result90', 0),
},
{
title: () => (<>前180 -50<br/>{searchValues.date180.Date1} {searchValues.date180.Date2}</>),
key: 'department',
children: dataFields('result180', 1),
},
],
rowClassName: (record, rowIndex) => {
return record.groupType === 'overview' ? 'ant-tag-blue' : '';
},
};
const activityTableProps = {
rowKey: 'groupsKey',
pagination: false,
size: 'small',
rowClassName: (record, rowIndex) => {
return record.groupType === 'operator' ? '' : 'ant-tag-blue';
},
showSorterTooltip: false,
columns: [
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem' }, // :
{
title: '顾问动作',
key: 'date',
children: [
{
title: () => (
<>
首次响应率24H{' '}
<Tooltip title="达到24H内回复的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'firstTouch24',
key: 'firstTouch24',
render: percentageRender,
},
{
title: () => (
<>
48H内报价率{' '}
<Tooltip title="达到48H内报价的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'firstQuote48',
key: 'firstQuote48',
render: percentageRender,
},
{
title: () => (
<>
一次报价率{' '}
<Tooltip title="首次报价的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'quote1',
key: 'quote1',
render: percentageRender,
},
{
title: () => (
<>
二次报价率{' '}
<Tooltip title="二次报价的订单数/一次报价后回复数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'quote2',
key: 'quote2',
render: percentageRender,
},
{
title: () => (
<>
&gt;50条会话{' '}
<Tooltip title=">50条会话的订单数/总订单">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'turnsGT50',
key: 'turnsGT50',
render: percentageRender,
},
{
title: () => (
<>
违规数{' '}
<Tooltip title="未遵循24H回复的订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'violations',
key: 'violations',
},
],
},
{
title: '客人回复',
key: 'department',
children: [
{
title: () => (
<>
首次回复率24H{' '}
<Tooltip title="达到24H内回复的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'firstReply24',
key: 'firstReply24',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
{
title: () => (
<>
48H内报价回复率{' '}
<Tooltip title="48H内报价后的回复数/报价的订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'replyQuote48',
key: 'replyQuote48',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
{
title: () => (
<>
一次报价回复率{' '}
<Tooltip title="一次报价后回复率/订单总数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'replyQuote1',
key: 'replyQuote1',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
{
title: () => (
<>
二次报价回复率{' '}
<Tooltip title="二次报价后回复数/一次报价后回复数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'replyQuote2',
key: 'replyQuote2',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
],
},
],
};
const riskTableProps = {
loading: risk.loading,
sticky: true,
// scroll: { x: 1000, y: 400 },
pagination: false,
rowKey: (row) => row.coli_id,
columns: [
{ title: '客人姓名', dataIndex: 'guest_name', key: 'guest_name', width: '6rem' },
{ title: '团号', dataIndex: 'coli_id', key: 'coli_id', width: '6rem' },
{
title: '表单内容',
dataIndex: 'coli_contents',
key: 'coli_contents',
width: '6rem',
render: (text, record) => (
<Popover
title={`${record.coli_id} ${record.guest_name}`}
content={<pre dangerouslySetInnerHTML={{ __html: text }} style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word' }} />}
trigger={['click']}
placement="right"
overlayStyle={{ width: '500px', maxHeight: '500px' }}
autoAdjustOverflow={false}
>
<Button type="link" size="small">
表单内容
</Button>
</Popover>
),
},
{ title: '顾问', dataIndex: 'opi_name', key: 'opi_name', width: '6rem' },
{ title: '预定时间', dataIndex: 'coli_applydate', key: 'coli_applydate', width: '6rem' },
],
};
const chartsConfig = {
colFields: ['ConfirmOrder', 'SumOrder'],
lineFields: ['SumML', 'ConfirmRates'],
seriesField: null,
xField: 'groupDateVal',
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={12} xxl={14}>
<NavLink to={-1}>返回</NavLink>
</Col>
</Row>
<section>
<h2>结果指标 @ {searchValues.date.format(DATE_FORMAT)}</h2>
<Table {...dashboardTableProps} bordered dataSource={results[`operator_${opisn}`]} loading={results.loading} sticky />
</section>
<section>
<h3>全年每月业绩 @ {searchValues.date.format('YYYY')}</h3>
<MixFieldsDetail {...chartsConfig} dataSource={results[`operator_byDate_${opisn}`] || []} />
</section>
<Divider />
<section>
<h2>过程指标 @ {searchValuesToSub.Date1} {searchValuesToSub.Date2}</h2>
<Table {...activityTableProps} bordered dataSource={process[`operator_${opisn}`]} loading={process.loading} sticky />
</section>
<section>
<h2>违规明细</h2>
<h3>&gt;24H回复</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostTouch24 || []} />
</section>
<section>
<h3>首次报价周期&gt;48h</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote48 || []} />
</section>
<section>
<h3>报价次数&lt;1</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote1 || []} />
</section>
<section>
<h3>报价次数&lt;2</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote2 || []} />
</section>
</>
);
});
Loading…
Cancel
Save