Merge branch 'feature/2.0' of github.com:hainatravel/dashboard into feature/2.0

feature/2.0-sales-trade
YCC 2 years ago
commit fac532d83a

@ -10,9 +10,9 @@ import {
DollarOutlined, DollarOutlined,
AreaChartOutlined, AreaChartOutlined,
WechatOutlined, WechatOutlined,
UserOutlined, FlagOutlined, PieChartOutlined UserOutlined, FlagOutlined, PieChartOutlined, BarChartOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Layout, Menu, Image, Spin } from 'antd'; import { Layout, Menu, Image, Badge } from 'antd';
import { BrowserRouter, Route, Routes, NavLink } from 'react-router-dom'; import { BrowserRouter, Route, Routes, NavLink } from 'react-router-dom';
import Home from './views/Home'; import Home from './views/Home';
import Dashboard from './views/Dashboard'; import Dashboard from './views/Dashboard';
@ -37,6 +37,7 @@ import { observer } from 'mobx-react';
import ExchangeRate from './charts/ExchangeRate'; import ExchangeRate from './charts/ExchangeRate';
import KPI from './views/KPI'; import KPI from './views/KPI';
import Distribution from './views/Distribution'; import Distribution from './views/Distribution';
import Detail from './views/Detail';
const App = () => { const App = () => {
const { Content, Footer, Sider } = Layout; const { Content, Footer, Sider } = Layout;
@ -123,6 +124,15 @@ const App = () => {
}, },
{ key: 'kpi', label: <NavLink to="/kpi">目标</NavLink>, icon: <FlagOutlined /> }, { key: 'kpi', label: <NavLink to="/kpi">目标</NavLink>, icon: <FlagOutlined /> },
{ key: 'distribution', label: <NavLink to="/distribution">统计分布</NavLink>, icon: <PieChartOutlined /> }, { key: 'distribution', label: <NavLink to="/distribution">统计分布</NavLink>, icon: <PieChartOutlined /> },
{
key: 'detail',
label: (
<NavLink to="/detail">
<Badge.Ribbon text="Beta">统计分析</Badge.Ribbon>
</NavLink>
),
icon: <BarChartOutlined />,
},
]; ];
return ( return (
@ -165,6 +175,7 @@ const App = () => {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/kpi" element={<KPI />} /> <Route path="/kpi" element={<KPI />} />
<Route path="/distribution" element={<Distribution />} /> <Route path="/distribution" element={<Distribution />} />
<Route path="/detail" element={<Detail />} />
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'marketing']} />}> <Route element={<ProtectedRoute auth={['admin', 'director_bu', 'marketing']} />}>
<Route path="/orders" element={<Orders />} /> <Route path="/orders" element={<Orders />} />
<Route path="/orders_sub/:ordertype/:ordertype_sub/:ordertype_title" element={<Orders_sub />} /> <Route path="/orders_sub/:ordertype/:ordertype_sub/:ordertype_title" element={<Orders_sub />} />

@ -0,0 +1,53 @@
import { observer } from 'mobx-react';
import { Line } from '@ant-design/plots';
import { merge, isEmpty } 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 { config, dataSource, ...extProps } = props;
const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v;
const _data = dataSource.reduce((r, v) => {
r.push(v);
if ( ! isEmpty(v[kpiKey])) { // 线 : #F4664A
r.push({...v, [config.yField]: v[kpiKey], [config.seriesField]: dataFieldAlias[kpiKey].label});
}
return r;
}, []);
const mergeLineConfig = merge({
color: ['#598cf3', '#F4664A', '#FAAD14'],
lineStyle: (data) => {
console.log(data);
if (data[config.seriesField].includes('目标')) {
return {
lineDash: [4, 4],
opacity: 0.5,
};
}
return {
opacity: 1,
};
},
}, config);
return <Line {...mergeLineConfig} data={_data} />;
});

@ -0,0 +1,42 @@
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { sortBy, merge } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
import { Mix, Scatter } from '@ant-design/plots';
export default observer((props) => {
const { dataSource, ...extProps } = props;
const config = merge(
{
appendPadding: 10,
// xField: 'Revenue (Millions)',
// yField: 'Rating',
shape: 'circle',
// colorField: 'Genre',
size: 4,
yAxis: {
nice: true,
line: {
style: {
stroke: '#aaa',
},
},
},
xAxis: {
min: -100,
grid: {
line: {
style: {
stroke: '#eee',
},
},
},
line: {
style: {
stroke: '#aaa',
},
},
},
}, extProps);
return <Scatter {...config} data={dataSource} />;
});

@ -5,7 +5,7 @@ import { merge } from '../utils/commons';
export default observer((props) => { export default observer((props) => {
const { dataSource, line, title, ...extProps } = props; const { dataSource, line, title, ...extProps } = props;
const yMax = (Math.max(line?.value || 0, ...dataSource.map((ele) => ele[extProps.yField])))*1; const yMax = (Math.max(line?.value || 0, (dataSource.reduce((r, ele) => r+ele[extProps.yField], 0))))*1;
const annotationsLine = line const annotationsLine = line
? [ ? [
{ {

@ -6,6 +6,7 @@ import SearchForm from './../search/SearchForm';
import { bu, KPIObjects, KPISubjects } from './../../libs/ht'; import { bu, KPIObjects, KPISubjects } from './../../libs/ht';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt, merge } from './../../utils/commons'; import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt, merge } from './../../utils/commons';
import ProfitTable from './SubjectTable/Profit'; import ProfitTable from './SubjectTable/Profit';
import { toJS } from 'mobx';
const Todo = (props) => { const Todo = (props) => {
return <h2>TODO</h2>; return <h2>TODO</h2>;
@ -28,7 +29,7 @@ export default observer((props) => {
const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context); const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { sort, initialValue, hides, shows, fieldProps: _fieldProps } = { const { sort, initialValue, hides, shows, fieldProps: _fieldProps } = {
sort: '', sort: '',
initialValue: searchFormStore.formValues, initialValue: '', // searchFormStore.formValues,
fieldProps: {}, fieldProps: {},
hides: [], hides: [],
shows: ['DateType', 'years'], shows: ['DateType', 'years'],
@ -43,15 +44,15 @@ export default observer((props) => {
<Col className="gutter-row mb-n1 p-none" span={24}> <Col className="gutter-row mb-n1 p-none" span={24}>
<SearchForm <SearchForm
defaultValue={{ defaultValue={{
initialValue, sort, initialValue,
shows, shows,
fieldProps, fieldProps,
}} }}
confirmText="查询" confirmText="查询"
onSubmit={(_err, obj, form, str) => { onSubmit={(_err, obj, form, str) => {
console.log('invoke kpi setting search'); // console.log('invoke kpi setting search');
if (typeof onSearchSubmit === 'function') { if (typeof onSearchSubmit === 'function') {
onSearchSubmit(obj); onSearchSubmit(obj, form);
} }
}} }}
/> />
@ -63,6 +64,7 @@ export default observer((props) => {
tabPosition={'left'} tabPosition={'left'}
onChange={(sub) => { onChange={(sub) => {
KPIStore.setSettingSubject(sub); KPIStore.setSettingSubject(sub);
// onSearchSubmit(searchFormStore.formValuesToSub);
}} }}
items={KPISubjects.map((ele, i) => { items={KPISubjects.map((ele, i) => {
const SubjectTableComponent = subjectComponents[ele.key]; const SubjectTableComponent = subjectComponents[ele.key];

@ -1,23 +1,22 @@
import { useContext } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
// import { stores_Context } from '../config';
import { Table } from 'antd';
import KPISettings from './KPISettings'; import KPISettings from './KPISettings';
import { bu, KPISubjects } from '../../libs/ht'; import { bu, KPISubjects } from '../../libs/ht';
const sort = { DateType: 10, years: 11 };
const yearInitial = {};
const searchFormItemSet = { const searchFormItemSet = {
'bu': { shows: ['DateType', 'years', 'HTBusinessUnits'] }, 'bu': { shows: ['DateType', 'years', 'HTBusinessUnits'], sort },
'dept': { shows: ['DateType', 'years', 'DepartmentList'], fieldProps: { DepartmentList: { allowClear: true } } }, 'dept': { shows: ['DateType', 'years', 'DepartmentList'], sort, fieldProps: { DepartmentList: { allowClear: true,isLeaf: true, show_all: false } }, },
'operator': { shows: ['DateType', 'years', 'DepartmentList'] }, // , 'operator' 'operator': { shows: ['DateType', 'years', 'DepartmentList', 'operator'], fieldProps: { DepartmentList: { allowClear: true, isLeaf: true }, operator: { param: { is_assign: 1 } } }, sort },
'destination': { shows: ['DateType', 'years', 'destination'] }, 'destination': { shows: ['DateType', 'years', 'destination'], sort },
'country': { shows: ['DateType', 'years', 'country'] }, 'country': { shows: ['DateType', 'years', 'country'], sort },
}; };
export default observer((props) => { export default observer((props) => {
const searchProps = searchFormItemSet?.[props.curObject] || {}; const searchProps = searchFormItemSet?.[props.curObject] || {};
return ( return (
<> <>
<KPISettings {...{ searchProps, objects: bu, KPISubjects }} {...props} /> <KPISettings {...{ searchProps, KPISubjects }} {...props} />
</> </>
); );
}); });

@ -20,12 +20,12 @@ export default observer((props) => {
const { curObject } = props; const { curObject } = props;
const [dataSource, setDataSource] = useState([]); const [dataSource, setDataSource] = useState([]);
useEffect(() => { useEffect(() => {
onSearchSubmit({ // onSearchSubmit({
object: curObject, // object: curObject,
date_type: 'applyDate', // date_type: 'applyDate',
start_date: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'), // start_date: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'),
end_date: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'), // end_date: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'),
}); // });
return () => {}; return () => {};
}, []); }, []);
@ -129,6 +129,7 @@ export default observer((props) => {
value: mVal, value: mVal,
kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined, kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined,
key: undefined, key: undefined,
group_date_type: 'month',
}; };
}); });
return r.concat(allMonth); return r.concat(allMonth);
@ -143,6 +144,7 @@ export default observer((props) => {
start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'), start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'),
end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'), end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'),
kpi_id: kpiYear?.kpi_id || undefined, kpi_id: kpiYear?.kpi_id || undefined,
group_date_type: 'year',
}))(dataSource?.[0] || {}); }))(dataSource?.[0] || {});
tableData.unshift(yearRow); tableData.unshift(yearRow);
console.log('sub', tableData, delKpiIds); console.log('sub', tableData, delKpiIds);

@ -24,11 +24,11 @@ export default observer((props) => {
const [editableRowsKeys, setEditableRowKeys] = useState([]); const [editableRowsKeys, setEditableRowKeys] = useState([]);
useEffect(() => { useEffect(() => {
setDataSource(KPIStore.pageData); setDataSource(KPIStore.pageData);
setEditableRowKeys([]);
setEditOpen(false); setEditOpen(false);
return () => {}; return () => {};
}, [KPIStore.pageData]); }, [KPIStore.pageData]);
const PercentInput = useMemo( const PercentInput = useMemo(
() => () =>
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
@ -84,7 +84,7 @@ export default observer((props) => {
title: '年度目标', title: '年度目标',
dataIndex: 'yearValue', dataIndex: 'yearValue',
valueType: 'digit', valueType: 'digit',
fieldProps: { style: { width: '100%' }, step: 10000*100 }, fieldProps: { style: { width: '100%' }, step: 10000 * 100 },
formItemProps: { formItemProps: {
style: { width: '100%' }, style: { width: '100%' },
}, },
@ -138,22 +138,23 @@ export default observer((props) => {
value: mVal, value: mVal,
kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined, kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined,
key: undefined, key: undefined,
group_date_type: 'month',
}; };
}); });
return r.concat(allMonth); const yearRow = (({ object, object_name, object_id, subject, date_type, yearValue, kpiYear }) => ({
object,
object_name,
object_id,
subject,
date_type,
value: yearValue,
start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'),
end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'),
kpi_id: kpiYear?.kpi_id || undefined,
group_date_type: 'year',
}))(curObj);
return r.concat(allMonth, yearRow);
}, []); }, []);
const yearRow = (({ object, object_name, object_id, subject, date_type, yearValue, kpiYear }) => ({
object,
object_name,
object_id,
subject,
date_type,
value: yearValue,
start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'),
end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'),
kpi_id: kpiYear?.kpi_id || undefined,
}))(dataSource?.[0] || {});
tableData.unshift(yearRow);
console.log('sub', tableData, 'del:', delKpiIds); console.log('sub', tableData, 'del:', delKpiIds);
// return false; // debug: // return false; // debug:
KPIStore.onSubmit(tableData, { delQueue: delKpiIds }).then((res) => { KPIStore.onSubmit(tableData, { delQueue: delKpiIds }).then((res) => {
@ -180,28 +181,37 @@ export default observer((props) => {
date_type: searchFormStore.formValuesToSub.DateType, date_type: searchFormStore.formValuesToSub.DateType,
kpiDataMapped: {}, kpiDataMapped: {},
key: Date.now().toString(32), key: Date.now().toString(32),
group_date_type: 'month',
}), }),
{} {}
); // v.formItemProps.initialValue ); // v.formItemProps.initialValue
const makeInitialTable = (e) => { const makeInitialTable = (e) => {
setEditOpen(e); setEditOpen(e);
// todo: , // test: ,
const _initialRow = Object.assign({}, initialRow, initialPercentKey);
const _objects = isEmpty(objects) ? curObjectItem?.data || [] : objects;
// console.log('ooo', objects, isEmpty(objects), curObjectItem?.data || []);
const _initialTable = _objects.map((obj) => ({
...cloneDeep(_initialRow),
object_name: obj.label,
object_id: obj.value,
key: Date.now().toString(32) + obj.value,
}));
// console.log(_initialRow, 'iiiii');
const mergePageData = Object.values(
Object.assign(
{},
_initialTable.reduce((r, v) => ({ ...r, [v.object_name]: v }), {}),
dataSource.reduce((r, v) => ({ ...r, [v.object_name]: v }), {})
)
);
if (e && isEmpty(dataSource)) { if (e && isEmpty(dataSource)) {
const _initialRow = Object.assign({}, initialRow, initialPercentKey);
const _objects = isEmpty(objects) ? (curObjectItem?.data || []) : objects;
console.log('ooo', objects, isEmpty(objects), curObjectItem?.data || []);
const _initialTable = _objects.map((obj) => ({
...cloneDeep(_initialRow),
object_name: obj.label,
object_id: obj.value,
key: Date.now().toString(32) + obj.value,
}));
console.log(_initialRow, 'iiiii');
setDataSource(_initialTable); setDataSource(_initialTable);
setEditableRowKeys(_initialTable.map((ele) => ele.key)); setEditableRowKeys(_initialTable.map((ele) => ele.key));
return false; return false;
} }
setEditableRowKeys(e ? dataSource.map((ele) => ele.key) : []); setDataSource(mergePageData);
setEditableRowKeys(e ? mergePageData.map((ele) => ele.key) : []);
}; };
const [delKpiIds, setDelKpiIds] = useState([]); const [delKpiIds, setDelKpiIds] = useState([]);
return ( return (
@ -226,13 +236,11 @@ export default observer((props) => {
return [defaultDoms.delete]; return [defaultDoms.delete];
}, },
onDelete: (_key, _row) => { onDelete: (_key, _row) => {
// console.log('del', _key, _row);
const rowKpiIds = (_row?.kpiData || []).map((ele) => ele.kpi_id); const rowKpiIds = (_row?.kpiData || []).map((ele) => ele.kpi_id);
rowKpiIds.push(_row?.kpiYear?.kpi_id); rowKpiIds.push(_row?.kpiYear?.kpi_id);
setDelKpiIds(rowKpiIds); setDelKpiIds(rowKpiIds);
}, },
onValuesChange: (record, recordList) => { onValuesChange: (record, recordList) => {
// console.log('on edit, onValuesChange',record, recordList);
onTableChange(recordList); onTableChange(recordList);
}, },
onChange: (editableKeys, editableRows) => { onChange: (editableKeys, editableRows) => {
@ -242,9 +250,14 @@ export default observer((props) => {
/> />
</Col> </Col>
<Col className="gutter-row mb-n1 p-none mt-1 align_center" span={24}> <Col className="gutter-row mb-n1 p-none mt-1 align_center" span={24}>
<Button className="mt-1 mb-1 align_center" disabled={!editOpen} type="primary" key="save" onClick={onTableSubmit}> <Space size={'large'}>
保存数据 <Button className="mt-1 mb-1 align_center" disabled={!editOpen} type="primary" key="save" onClick={onTableSubmit}>
</Button> 保存数据
</Button>
{!editOpen && <Button className="mt-1 mb-1 align_center" disabled={false} type={'ghost'} key="initTable" onClick={() => {makeInitialTable(true);}}>
编辑设置
</Button>}
</Space>
</Col> </Col>
</Row> </Row>
</> </>

@ -2,8 +2,8 @@ import { useContext } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
// import { stores_Context } from '../config'; // import { stores_Context } from '../config';
import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio, Tabs } from 'antd'; import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio, Tabs } from 'antd';
import SearchForm from './../search/SearchForm'; import SearchForm from '../../search/SearchForm';
import { bu, KPIObjects } from './../../libs/ht'; import { bu, KPIObjects } from '../../../libs/ht';
export default observer((props) => { export default observer((props) => {
// const { } = useContext(stores_Context); // const { } = useContext(stores_Context);

@ -20,7 +20,7 @@ const Business_unit = (props) => {
} }
store?.bu_handleChange(value); store?.bu_handleChange(value);
}} }}
labelInValue={true} labelInValue={false}
{...extProps} {...extProps}
> >
{props.show_all ? <Select.Option key="-1" value="ALL">ALL 事业部</Select.Option> : ''} {props.show_all ? <Select.Option key="-1" value="ALL">ALL 事业部</Select.Option> : ''}

@ -21,7 +21,7 @@ const Business_unit = (props) => {
} }
// store?.bu_handleChange(value); // store?.bu_handleChange(value);
}} }}
labelInValue={true} labelInValue={false}
{...extProps} {...extProps}
> >
{_show_all ? <Select.Option key="-1" value="ALL">ALL 事业部</Select.Option> : ''} {_show_all ? <Select.Option key="-1" value="ALL">ALL 事业部</Select.Option> : ''}

@ -23,15 +23,16 @@ class DataTypeSelect extends Component {
}; };
render() { render() {
const store = this.props.store; const { store, ...extProps } = this.props;
return ( return (
<Select labelInValue={true} <Select labelInValue={false}
// value={store.date_type} // value={store.date_type}
value={this.props?.value || store?.date_type} value={this.props?.value || store?.date_type}
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder="选择日期类型" placeholder="选择日期类型"
// onChange={(value) => store.onChange_datetype(value)} // onChange={(value) => store.onChange_datetype(value)}
onChange={this.handleChange} onChange={this.handleChange}
{...extProps}
> >
{dateTypes.map((ele) => ( {dateTypes.map((ele) => (
<Select.Option key={ele.key} value={ele.key}> <Select.Option key={ele.key} value={ele.key}>

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Select } from 'antd'; import { Select } from 'antd';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { groups } from '../../libs/ht'; import { groups, leafGroup } from '../../libs/ht';
class GroupSelect extends Component { class GroupSelect extends Component {
constructor(props) { constructor(props) {
@ -9,9 +9,10 @@ class GroupSelect extends Component {
} }
render() { render() {
const { store, mode, value, onChange, show_all, ...extProps } = this.props; const { store, mode, value, onChange, show_all, isLeaf, ...extProps } = this.props;
const _mode = mode || store?.group_select_mode || null; const _mode = mode || store?.group_select_mode || null;
const _show_all = ['tags', 'multiple'].includes(_mode) ? false : show_all; const _show_all = ['tags', 'multiple'].includes(_mode) ? false : show_all;
const options = isLeaf===true ? leafGroup : groups;
return ( return (
<div> <div>
<Select <Select
@ -25,7 +26,7 @@ class GroupSelect extends Component {
} }
store?.group_handleChange(value); store?.group_handleChange(value);
}} }}
labelInValue={true} labelInValue={false}
maxTagCount={1} maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`} maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={_mode != null} allowClear={_mode != null}
@ -38,7 +39,7 @@ class GroupSelect extends Component {
) : ( ) : (
'' ''
)} )}
{groups.map((ele) => ( {options.map((ele) => (
<Select.Option key={ele.key} value={ele.key}> <Select.Option key={ele.key} value={ele.key}>
{ele.label} {ele.label}
</Select.Option> </Select.Option>

@ -4,7 +4,7 @@ import querystring from 'querystring';
// import * as oMapper from 'object-mapper'; // import * as oMapper from 'object-mapper';
import { fetchJSON } from './../../utils/request'; import { fetchJSON } from './../../utils/request';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { objectMapper } from './../../utils/commons'; import { merge, objectMapper } from './../../utils/commons';
const { Option } = Select; const { Option } = Select;
@ -24,10 +24,7 @@ function curl(opts, callback) {
const _p = [{ 'key': '0', 'label': '空' }]; const _p = [{ 'key': '0', 'label': '空' }];
return callback(_p); return callback(_p);
} }
const param = { const param = merge({ code: 'utf-8', q: opts.value }, opts.param);
code: 'utf-8',
q: opts.value,
};
// const str = new URLSearchParams({ // const str = new URLSearchParams({
// code: 'utf-8', // code: 'utf-8',
// q: opts.value, // q: opts.value,
@ -66,9 +63,9 @@ class SearchInput extends React.Component {
componentDidMount() { componentDidMount() {
if (this.props.autoGet === true) { if (this.props.autoGet === true) {
const { map, resultkey } = this.props; const { map, resultkey, param } = this.props;
const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {}); const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {});
curl({ value: '', url: this.props.url || '', map: mapKey, resultkey }, (data) => 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) : '')) this.setState({ data, autoData: data }, () => (typeof this.props.onSearchAfter === 'function' ? this.props.onSearchAfter(data, this.state.value) : ''))
); );
} }
@ -90,10 +87,10 @@ class SearchInput extends React.Component {
this.setState({ data: f || [] }); this.setState({ data: f || [] });
return false; return false;
} }
const { map, resultkey } = this.props; const { map, resultkey, param } = this.props;
const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {}); const mapKey = Object.keys(map).reduce((r, v) => ({ ...r, [v]: { key: map[v] } }), {});
if (value) { if (value) {
curl({ value, url: this.props.url || '', map: mapKey, resultkey }, (data) => 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) : '')) this.setState({ data }, () => (typeof this.props.onSearchAfter === 'function' ? this.props.onSearchAfter(data, this.state.value) : ''))
); );
} else { } else {
@ -102,6 +99,7 @@ class SearchInput extends React.Component {
}; };
handleChange = (value, option) => { handleChange = (value, option) => {
console.log('invoke denpendencies change', value);
this.setState({ value }, () => this.props.onChange(value, option)); this.setState({ value }, () => this.props.onChange(value, option));
}; };

@ -45,7 +45,6 @@ export default observer((props) => {
const { onSubmit, confirmText } = props; const { onSubmit, confirmText } = props;
const formValuesMapper = (values) => { const formValuesMapper = (values) => {
console.log('Received values of form, origin form value: ', values);
const destinationObject = { const destinationObject = {
'DateType': { 'DateType': {
key: 'DateType', key: 'DateType',
@ -55,28 +54,28 @@ export default observer((props) => {
'HTBusinessUnits': { 'HTBusinessUnits': {
key: 'HTBusinessUnits', key: 'HTBusinessUnits',
transform: (value) => { transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '-1'; return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '';
}, },
default: '', default: '',
}, },
'businessUnits': { 'businessUnits': {
key: 'businessUnits', key: 'businessUnits',
transform: (value) => { transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '-1'; return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '';
}, },
default: '', default: '',
}, },
'DepartmentList': { 'DepartmentList': {
key: 'DepartmentList', key: 'DepartmentList',
transform: (value) => { transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : 'ALL'; return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : '';
}, },
default: '', default: '',
}, },
'WebCode': { 'WebCode': {
key: 'WebCode', key: 'WebCode',
transform: (value) => { transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : 'ALL'; return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : '';
}, },
default: '', default: '',
}, },
@ -154,12 +153,13 @@ export default observer((props) => {
} }
// omit empty // omit empty
Object.keys(dest).forEach((key) => (dest[key] == null || dest[key] === '' || dest[key].length === 0) && delete dest[key]); Object.keys(dest).forEach((key) => (dest[key] == null || dest[key] === '' || dest[key].length === 0) && delete dest[key]);
console.log('form value send to onSubmit:', dest);
return dest; return dest;
}; };
const onFinish = (values) => { const onFinish = (values) => {
console.log('Received values of form, origin form value: ', values);
const dest = formValuesMapper(values); const dest = formValuesMapper(values);
console.log('form value send to onSubmit:', dest);
const str = new URLSearchParams(dest).toString(); const str = new URLSearchParams(dest).toString();
searchFormStore.setFormValues(values); searchFormStore.setFormValues(values);
searchFormStore.setFormValuesToSub(dest); searchFormStore.setFormValuesToSub(dest);
@ -175,8 +175,11 @@ export default observer((props) => {
}; };
const onValuesChange = (...args) => { const onValuesChange = (...args) => {
const [changedValues, allValues] = args; const [changedValues, allValues] = args;
console.log('form onValuesChange', args); // console.log('form onValuesChange', Object.keys(changedValues), args);
const dest = formValuesMapper(allValues);
searchFormStore.setFormValues(allValues); searchFormStore.setFormValues(allValues);
searchFormStore.setFormValuesToSub(dest);
}; };
return ( return (
@ -229,28 +232,28 @@ function getFields(props) {
'HTBusinessUnits', 'HTBusinessUnits',
99, 99,
<Form.Item name={`HTBusinessUnits`} initialValue={at(props, 'initialValue.HTBusinessUnits')[0] || undefined}> <Form.Item name={`HTBusinessUnits`} initialValue={at(props, 'initialValue.HTBusinessUnits')[0] || undefined}>
<BusinessUnitSelect {...fieldProps.HTBusinessUnits} /> <BusinessUnitSelect {...fieldProps.HTBusinessUnits} labelInValue={true} />
</Form.Item> </Form.Item>
), ),
item( item(
'businessUnits', 'businessUnits',
99, 99,
<Form.Item name={`businessUnits`} initialValue={at(props, 'initialValue.businessUnits')[0] || undefined}> <Form.Item name={`businessUnits`} initialValue={at(props, 'initialValue.businessUnits')[0] || undefined}>
<BusinessSelect {...fieldProps.businessUnits} /> <BusinessSelect {...fieldProps.businessUnits} labelInValue={true} />
</Form.Item> </Form.Item>
), ),
item( item(
'DepartmentList', 'DepartmentList',
99, 99,
<Form.Item name={`DepartmentList`} initialValue={at(props, 'initialValue.DepartmentList')[0] || (fieldProps?.DepartmentList?.show_all ? { key: 'ALL', label: '所有小组' } : undefined)}> <Form.Item name={`DepartmentList`} initialValue={at(props, 'initialValue.DepartmentList')[0] || (fieldProps?.DepartmentList?.show_all ? { key: 'ALL', label: '所有小组' } : undefined)}>
<GroupSelect {...fieldProps.DepartmentList} /> <GroupSelect {...fieldProps.DepartmentList} labelInValue={true} />
</Form.Item> </Form.Item>
), ),
item( item(
'WebCode', 'WebCode',
99, 99,
<Form.Item name={`WebCode`} initialValue={at(props, 'initialValue.WebCode')[0] || (fieldProps?.WebCode?.show_all ? { key: 'ALL', label: '所有来源' } : undefined)}> <Form.Item name={`WebCode`} initialValue={at(props, 'initialValue.WebCode')[0] || (fieldProps?.WebCode?.show_all ? { key: 'ALL', label: '所有来源' } : undefined)}>
<SiteSelect {...fieldProps.WebCode} /> <SiteSelect {...fieldProps.WebCode} labelInValue={true} />
</Form.Item> </Form.Item>
), ),
item( item(
@ -274,18 +277,10 @@ function getFields(props) {
'DateType', 'DateType',
99, 99,
<Form.Item name={`DateType`} initialValue={at(props, 'initialValue.DateType')[0] || { key: 'applyDate', label: '提交日期' }}> <Form.Item name={`DateType`} initialValue={at(props, 'initialValue.DateType')[0] || { key: 'applyDate', label: '提交日期' }}>
<DateTypeSelect /> <DateTypeSelect labelInValue={true} />
</Form.Item>, </Form.Item>,
2 2
), ),
item(
'dates',
99,
<Form.Item>
<DatePickerCharts isform={true} {...fieldProps.dates} form={form} />
</Form.Item>,
midCol
),
item( item(
'years', 'years',
99, 99,
@ -303,11 +298,26 @@ function getFields(props) {
</Form.Item>, </Form.Item>,
2 2
), ),
item(
'dates',
99,
<Form.Item>
<DatePickerCharts isform={true} {...fieldProps.dates} form={form} />
</Form.Item>,
midCol
),
item( item(
'operator', 'operator',
99, 99,
<Form.Item name={'operator'}> <Form.Item name={'operator'} dependencies={['DepartmentList']} >
<SearchInput autoGet url="/service-Analyse2/GetOperatorInfo" map={{ 'op_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索顾问: 中/英名字" /> <SearchInput
autoGet
url="/service-Analyse2/GetOperatorInfo"
map={{ 'op_id': 'key', 'cn_name': 'label' }}
resultkey={'result'}
placeholder="输入搜索顾问: 中/英名字"
param={{ dept_id: (form.getFieldValue('DepartmentList')?.value || '').replace('ALL', ''), ...(fieldProps?.operator?.param || {}) }}
/>
</Form.Item> </Form.Item>
), ),
item( item(

@ -26,7 +26,7 @@ class SiteSelect extends Component {
} }
store?.handleChange_webcode(value); store?.handleChange_webcode(value);
}} }}
labelInValue={true} labelInValue={false}
maxTagCount={1} maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`} maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={_mode != null} allowClear={_mode != null}

@ -55,7 +55,7 @@ export const groups = [
{ value: '31', key: '31', label: '花梨鹰', code: '', children: [] }, { value: '31', key: '31', label: '花梨鹰', code: '', children: [] },
]; ];
export const groupsMappedByCode = groups.reduce((a, c) => ({ ...a, [String(c.code || c.key)]: c }), {}); export const groupsMappedByCode = groups.reduce((a, c) => ({ ...a, [String(c.code || c.key)]: c }), {});
export const leafGroup = groups.slice(3);
/** /**
* 来源 * 来源
*/ */
@ -107,7 +107,7 @@ export const dataFieldAlias = dataFieldOptions.reduce(
(a, c) => ({ (a, c) => ({
...a, ...a,
[c.value]: { ...c, alias: c.label, formatter: (v) => c.formatter(v) }, [c.value]: { ...c, alias: c.label, formatter: (v) => c.formatter(v) },
[c.nestkey.v]: { ...c, value: c.nestkey.v, alias: `${c.label}目标`, formatter: (v) => c.formatter(v) }, [c.nestkey.v]: { ...c, value: c.nestkey.v, alias: `${c.label}目标`, label: `${c.label}目标`, formatter: (v) => c.formatter(v) },
}), }),
{} {}
); );
@ -118,7 +118,7 @@ export const dataFieldAlias = dataFieldOptions.reduce(
export const KPIObjects = [ export const KPIObjects = [
{ key: 'overview', value: 'overview', label: '海纳' }, { key: 'overview', value: 'overview', label: '海纳' },
{ key: 'bu', value: 'bu', label: '事业部', data: bu }, { key: 'bu', value: 'bu', label: '事业部', data: bu },
{ key: 'dept', value: 'dept', label: '小组', data: groups }, { key: 'dept', value: 'dept', label: '小组', data: leafGroup },
{ key: 'du', value: 'du', label: '销售小组', data: deptUnits }, { key: 'du', value: 'du', label: '销售小组', data: deptUnits },
{ key: 'operator', value: 'operator', label: '顾问' }, { key: 'operator', value: 'operator', label: '顾问' },
{ key: 'destination', value: 'destination', label: '目的地' }, { key: 'destination', value: 'destination', label: '目的地' },

@ -1,15 +1,13 @@
{ {
"get|/service-web/baseinfo/operator/test": { "get|/service-Analyse2/GetDestinationInfo/test": {
"errcode": 0, "errcode": 0,
"errmsg": "", "errmsg": "",
"data": null, "data": null,
"loading": null, "loading": null,
"result|10": [ "result|10": [
{ {
"mobile": "13@integer(99999999,999999999)", "id": "@integer(10,99)",
"op_id": "@integer(10,99)",
"cn_name": "@cname", "cn_name": "@cname",
"email": "@email",
"en_name": "@first", "en_name": "@first",
"code": "@word(2,3)", "code": "@word(2,3)",
"key": "@increment" "key": "@increment"

@ -10,23 +10,6 @@ class DatePickerStore {
makeAutoObservable(this); makeAutoObservable(this);
} }
formValues = {
'DepartmentList': { 'key': 'ALL', 'label': '所有小组' },
'WebCode': { 'key': 'ALL', 'label': '所有来源' },
'IncludeTickets': { 'key': '1', 'label': '含门票' },
'DateType': { 'key': 'applyDate', 'label': '提交日期' },
'year': moment(),
};
formValuesToSub = {
DepartmentList: 'ALL',
WebCode: 'ALL',
IncludeTickets: '1',
DateType: 'applyDate',
Date1: moment().startOf('year').format('YYYY-MM-DD'),
Date2: moment().endOf('year').format('YYYY-MM-DD 23:59'),
};
start_date = moment().startOf('week').subtract(7, 'days'); start_date = moment().startOf('week').subtract(7, 'days');
end_date = moment().endOf('week').subtract(7, 'days'); end_date = moment().endOf('week').subtract(7, 'days');
start_date_cp = false; start_date_cp = false;
@ -57,6 +40,26 @@ class DatePickerStore {
return [moment(this.start_date).subtract(1, 'year'), moment(this.end_date).subtract(1, 'year')]; return [moment(this.start_date).subtract(1, 'year'), moment(this.end_date).subtract(1, 'year')];
} }
formValues = {
'DepartmentList': { 'key': 'ALL', 'label': '所有小组' },
'WebCode': { 'key': 'ALL', 'label': '所有来源' },
'IncludeTickets': { 'key': '1', 'label': '含门票' },
'DateType': { 'key': 'applyDate', 'label': '提交日期' },
'years': this.start_date,
// 'months': [moment(), moment()],
'dates': [this.start_date, this.end_date],
};
formValuesToSub = {
DepartmentList: 'ALL',
WebCode: 'ALL',
IncludeTickets: '1',
DateType: 'applyDate',
Date1: this.start_date.format('YYYY-MM-DD'),
Date2: this.end_date.format('YYYY-MM-DD 23:59:59'),
};
setFormValues(data){ setFormValues(data){
this.formValues = data; this.formValues = data;
} }

@ -19,6 +19,14 @@ const modelMapper = {
en_name: { key: 'label_alias' }, en_name: { key: 'label_alias' },
}, },
}, },
'destination': {
url: '/service-Analyse2/GetDestinationInfo/test',
mapper: {
id: [{ key: 'key' }, { key: 'value' }],
cn_name: { key: 'label' },
en_name: { key: 'label_alias' },
},
},
'vendor': { 'vendor': {
url: '/service-web/QueryData/GetVEIName', url: '/service-web/QueryData/GetVEIName',
mapper: { mapper: {
@ -48,7 +56,7 @@ class DictData {
runInAction(() => { runInAction(() => {
this[mkey].loading = false; this[mkey].loading = false;
this[mkey].dataSource = objectMapper(json.result, modelMapper[mkey].mapper); this[mkey].dataSource = objectMapper(json.result, modelMapper[mkey].mapper);
console.log({ loading: false, ...json }, model, 'DictData', toJS(this[mkey])); // console.log({ loading: false, ...json }, model, 'DictData', toJS(this[mkey]));
}); });
} }
return this[mkey]; return this[mkey];

@ -1,6 +1,6 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx'; import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request'; import * as req from '../utils/request';
import { isEmpty, sortBy } from '../utils/commons'; import { isEmpty, pick, sortBy } from '../utils/commons';
const modelMapper = { const modelMapper = {
'tourDays': { url: '/service-Analyse2/GetTradeApartByTourDays' }, 'tourDays': { url: '/service-Analyse2/GetTradeApartByTourDays' },
@ -17,7 +17,10 @@ class Distribution {
makeAutoObservable(this); makeAutoObservable(this);
} }
async getData(param){ /**
* 各个类型的分布
*/
getApartData = async (param) => {
const mkey = this.curTab; const mkey = this.curTab;
this[mkey] = { loading: true, dataSource: [] }; this[mkey] = { loading: true, dataSource: [] };
const json = await req.fetchJSON(modelMapper[mkey].url, param); const json = await req.fetchJSON(modelMapper[mkey].url, param);
@ -30,10 +33,29 @@ class Distribution {
}); });
} }
return this[mkey]; return this[mkey];
};
} /**
* 明细
*/
getDetailData = async (param) => {
this.detailData.loading = true;
const json = await req.fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
if (json.errcode === 0) {
runInAction(() => {
this.detailData.loading = false;
this.detailData.dataSource = json.result;
const daysData = json.result.filter(ele => ele.confirmDays).map(row => pick(row, ['o_id', 'tourdays', 'applyDays', 'personNum', 'country', 'startDate']));
this.scatterDays = daysData;
});
}
return this.detailData;
};
resetData = () => {
// this.detailData = { loading: false, dataSource: [] };
// this.scatterDays = [];
resetData() {
this.tourDays = { loading: false, dataSource: [] }; this.tourDays = { loading: false, dataSource: [] };
this.PML = { loading: false, dataSource: [] }; this.PML = { loading: false, dataSource: [] };
this.ConfirmDays = { loading: false, dataSource: [] }; this.ConfirmDays = { loading: false, dataSource: [] };
@ -41,7 +63,7 @@ class Distribution {
this.PersonNum = { loading: false, dataSource: [] }; this.PersonNum = { loading: false, dataSource: [] };
this.destination = { loading: false, dataSource: [] }; this.destination = { loading: false, dataSource: [] };
this.GlobalDestination = { loading: false, dataSource: [] }; this.GlobalDestination = { loading: false, dataSource: [] };
} };
curTab = 'tourDays'; curTab = 'tourDays';
setCurTab(v) { setCurTab(v) {
@ -50,6 +72,9 @@ class Distribution {
pageLoading = false; pageLoading = false;
detailData = { loading: false, dataSource: [], };
scatterDays = [];
tourDays = { loading: false, dataSource: [] }; tourDays = { loading: false, dataSource: [] };
PML = { loading: false, dataSource: [] }; PML = { loading: false, dataSource: [] };
ConfirmDays = { loading: false, dataSource: [] }; ConfirmDays = { loading: false, dataSource: [] };

@ -44,7 +44,7 @@ class KPI {
this.listLoading = false; this.listLoading = false;
this.originData = json.result; this.originData = json.result;
const yearData = parseKPI(json.result, ['subject', 'object_id']); const yearData = parseKPI(json.result, ['subject', 'object_id']);
console.log(111, yearData, yearData[this.settingYear]); // console.log(111, yearData, yearData[this.settingYear]);
this.pageData = yearData?.[this.settingYear]?.[this.settingSubject] || []; this.pageData = yearData?.[this.settingYear]?.[this.settingSubject] || [];
}); });
} }

@ -15,7 +15,7 @@ class Trade {
fetchSummaryData(queryData) { fetchSummaryData(queryData) {
this.summaryData.loading = true; this.summaryData.loading = true;
queryData.groupType = 'overview'; queryData.groupType = 'overview';
// queryData.groupDateType = 'year'; queryData.groupDateType = 'year';
this.fetchTradeData(queryData).then((json) => { this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) { if (json.errcode === 0) {
runInAction(() => { runInAction(() => {

@ -289,7 +289,7 @@ export const sortBy = (key) => {
export function merge(...objects) { export function merge(...objects) {
const isDeep = objects.some(obj => obj !== null && typeof obj === 'object'); const isDeep = objects.some(obj => obj !== null && typeof obj === 'object');
const result = objects[0] ?? {}; const result = objects[0] || (isDeep ? {} : objects[0]);
for (let i = 1; i < objects.length; i++) { for (let i = 1; i < objects.length; i++) {
const obj = objects[i]; const obj = objects[i];
@ -301,7 +301,7 @@ export function merge(...objects) {
if (isDeep) { if (isDeep) {
if (Array.isArray(val)) { if (Array.isArray(val)) {
result[key] = [...result[key] || [], ...val]; result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val);
} else if (typeof val === 'object') { } else if (typeof val === 'object') {
result[key] = merge(result[key], val); result[key] = merge(result[key], val);
} else { } else {

@ -0,0 +1,90 @@
import { useContext, useEffect, useMemo } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Row, Col, Spin, Space, Radio, Tabs, Table } from 'antd';
import { empty } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
import Scatter from './../components/Scatter';
import SearchForm from './../components/search/SearchForm';
import { Histogram } from '@ant-design/plots';
export default observer((props) => {
const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context);
const { formValues, formValuesToSub } = searchFormStore;
const { curTab, scatterDays, detailData } = DistributionStore;
const detailRefresh = (obj) => {
DistributionStore.getDetailData({
...(obj || formValuesToSub),
});
};
useEffect(() => {
if (empty(detailData.dataSource)) {
detailRefresh();
}
}, []);
const ScatterConfig = {
xField: 'startDate',
yField: 'tourdays',
colorField: 'country',
size: 4,
// xAxis: {
// min: 0,
// max: 30,
// },
yAxis: {
min: 0,
max: 10,
},
// quadrant: {
// xBaseline: 15,
// yBaseline: 5,
// },
tooltip: false,
legend: {
position: 'right-top',
},
};
const HistogramConfig = {
binField: 'personNum',
binWidth: 1,
};
return (
<>
<Row gutter={16} style={{ margin: '-16px -8px' }}>
{/* style={{ margin: '-16px -8px', padding: 0 }} */}
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates', 'country'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: true },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
detailRefresh(obj);
}}
/>
</Col>
</Row>
<section>
<Spin spinning={detailData.loading}>
<Scatter {...ScatterConfig} dataSource={scatterDays} />
</Spin>
</section>
{/* <section>
<Spin spinning={detailData.loading}>
<Histogram {...HistogramConfig} data={scatterDays} />
</Spin>
</section> */}
</>
);
});

@ -1,31 +1,31 @@
import { useContext, useEffect, useMemo } from 'react'; import { useContext, useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { stores_Context } from '../config'; import { stores_Context } from '../config';
import { Row, Col, Spin, Space, Radio, Tabs, Table } from 'antd'; import { Row, Col, Spin, Tabs, Table } from 'antd';
import { RingProgress } from '@ant-design/plots'; import { RingProgress } from '@ant-design/plots';
import SearchForm from './../components/search/SearchForm'; import SearchForm from './../components/search/SearchForm';
import "./kpi.css";
import { empty } from '../utils/commons'; import { empty } from '../utils/commons';
import { dataFieldAlias } from '../libs/ht';
import './kpi.css';
const apartOptions = [ const apartOptions = [
{ key: 'tourDays', value: 'tourDays', label: '团天数', }, { key: 'tourDays', value: 'tourDays', label: '团天数' },
{ key: 'PML', value: 'PML', label: '单团毛利', }, { key: 'PML', value: 'PML', label: '单团毛利' },
{ key: 'ConfirmDays', value: 'ConfirmDays', label: '成团周期', }, { key: 'ConfirmDays', value: 'ConfirmDays', label: '成团周期' },
{ key: 'ApplyDays', value: 'ApplyDays', label: '预定周期', }, { key: 'ApplyDays', value: 'ApplyDays', label: '预定周期' },
{ key: 'PersonNum', value: 'PersonNum', label: '人等', }, { key: 'PersonNum', value: 'PersonNum', label: '人等' },
{ key: 'destination', value: 'destination', label: '国内目的地', }, { key: 'destination', value: 'destination', label: '国内目的地' },
{ key: 'GlobalDestination', value: 'GlobalDestination', label: '海外目的地', }, { key: 'GlobalDestination', value: 'GlobalDestination', label: '海外目的地' },
]; ];
export default observer((props) => { export default observer(() => {
const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context); const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context);
const { formValues, formValuesToSub } = searchFormStore; const { formValues, formValuesToSub } = searchFormStore;
const { curTab } = DistributionStore; const { curTab } = DistributionStore;
const pageRefresh = (obj) => { const pageRefresh = (obj) => {
DistributionStore.getData({ DistributionStore.getApartData({
DateType: 'applyDate',
Date1: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'),
Date2: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'),
...(obj || formValuesToSub), ...(obj || formValuesToSub),
}); });
}; };
@ -41,7 +41,6 @@ export default observer((props) => {
return () => {}; return () => {};
}, [formValuesToSub]); }, [formValuesToSub]);
const onTabsChange = (tab) => { const onTabsChange = (tab) => {
DistributionStore.setCurTab(tab); DistributionStore.setCurTab(tab);
}; };
@ -49,69 +48,62 @@ export default observer((props) => {
height: 60, height: 60,
width: 60, width: 60,
autoFit: false, autoFit: false,
// percent: Number(_)/100,
color: ['#5B8FF9', '#E8EDF3'], color: ['#5B8FF9', '#E8EDF3'],
}; };
const columns = [ const columns = [
{ { title: '', dataIndex: 'label' },
title: '', { title: '团数', dataIndex: 'ConfirmOrder' },
dataIndex: 'label', { title: '业绩', dataIndex: 'SumML', render: (v) => dataFieldAlias.SumML.formatter(v) },
}, { title: '团数占比', dataIndex: 'ConfirmOrderPercent', render: (v) => <RingProgress {...RingProgressConfig} percent={v / 100} /> },
{ title: '团数', dataIndex: 'ConfirmOrder'}, { title: '业绩占比', dataIndex: 'SumMLPercent', render: (v) => <RingProgress {...RingProgressConfig} percent={v / 100} /> },
{ title: '业绩', dataIndex: 'SumML', render1: (v) => `1`},
{ title: '团数占比', dataIndex: 'ConfirmOrderPercent', render: (v) => <RingProgress {...RingProgressConfig} percent={v/100} /> },
{ title: '业绩占比', dataIndex: 'SumMLPercent', render: (v) => <RingProgress {...RingProgressConfig} percent={v/100} />},
]; ];
return ( return (
<> <>
<Row gutter={16} style={{ margin: '-16px -8px' }}> <Row gutter={16} style={{ margin: '-16px -8px' }}>
{/* style={{ margin: '-16px -8px', padding: 0 }} */}
<Col className="gutter-row" span={24}> <Col className="gutter-row" span={24}>
<SearchForm <SearchForm
defaultValue={{ defaultValue={{
initialValue: { initialValue: {
...formValues, ...formValues,
}, },
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'years'], shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'],
fieldProps: { fieldProps: {
DepartmentList: { show_all: true }, DepartmentList: { show_all: true },
WebCode: { show_all: true }, WebCode: { show_all: true },
years: { hide_vs: true }, dates: { hide_vs: true },
}, },
}} }}
onSubmit={(_err, obj, form, str) => { onSubmit={(_err, obj) => {
pageRefresh(obj); pageRefresh(obj);
}} }}
/> />
</Col> </Col>
</Row> </Row>
<section>
<Tabs
onChange={onTabsChange}
type="card"
items={apartOptions.map((ele, i) => {
// const ObjectItemPanel = objectComponents[ele.key];
return {
...ele,
children: (
<Spin spinning={DistributionStore.pageLoading}>
{/* <ObjectItemPanel title={ele.label} {...{ curObject, onSearchSubmit, objects: retObjects }} /> */}
<Table <section>
id="table_to_xlsx_sale" <Tabs
dataSource={DistributionStore[curTab].dataSource} onChange={onTabsChange}
columns={columns} type="card"
size="small" items={apartOptions.map((ele) => {
rowKey={(record) => record.label} return {
loading={DistributionStore[curTab].loading} ...ele,
pagination={false} children: (
scroll={{ x: '100%' }} <Spin spinning={DistributionStore.pageLoading}>
/> <Table
</Spin> id="table_to_xlsx_sale"
), dataSource={DistributionStore[curTab].dataSource}
}; columns={columns}
})} size="small"
/> rowKey={(record) => record.label}
loading={DistributionStore[curTab].loading}
pagination={false}
scroll={{ x: '100%' }}
/>
</Spin>
),
};
})}
/>
</section> </section>
</> </>
); );

@ -1,12 +1,13 @@
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Row, Col, Spin, Space, Radio } from 'antd'; import { Row, Col, Spin, Space, Radio } from 'antd';
import { CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwoTone, } from '@ant-design/icons'; import { CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwoTone } from '@ant-design/icons';
import { stores_Context } from '../config'; import { stores_Context } from '../config';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import StatisticCard from '../components/StatisticCard'; import StatisticCard from '../components/StatisticCard';
import Bullet from '../components/BulletWithSort'; import Bullet from '../components/BulletWithSort';
import Waterfall from '../components/Waterfall'; import Waterfall from '../components/Waterfall';
import LineWithKPI from '../components/LineWithKPI';
import DataFieldRadio from '../components/DataFieldRadio'; import DataFieldRadio from '../components/DataFieldRadio';
import { datePartOptions } from './../components/DateGroupRadio/date'; import { datePartOptions } from './../components/DateGroupRadio/date';
import SearchForm from './../components/search/SearchForm'; import SearchForm from './../components/search/SearchForm';
@ -30,7 +31,7 @@ export default observer(() => {
// const navigate = useNavigate(); // const navigate = useNavigate();
const { TradeStore, date_picker_store: searchFormStore } = useContext(stores_Context); const { TradeStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { sideData, summaryData, BuData, topData, timeData, timeLineKey } = TradeStore; const { sideData, summaryData, BuData, topData, timeData, timeLineKey } = TradeStore;
const { formValues, } = searchFormStore; const { formValues } = searchFormStore;
useEffect(() => { useEffect(() => {
if (empty(summaryData.dataSource)) { if (empty(summaryData.dataSource)) {
@ -92,7 +93,7 @@ export default observer(() => {
}, },
}, },
label: { label: {
formatter: (v) => summaryData.kpi.value === 0 ? (dataFieldAlias.SumML?.formatter(v.SumML) || v.SumML) : ((v.SumML / summaryData.kpi.value) * 100).toFixed(2) + '%', formatter: (v) => (summaryData.kpi.value === 0 ? dataFieldAlias.SumML?.formatter(v.SumML) || v.SumML : ((v.SumML / summaryData.kpi.value) * 100).toFixed(2) + '%'),
}, },
}; };
@ -108,7 +109,8 @@ export default observer(() => {
autoHide: true, autoHide: true,
autoRotate: false, autoRotate: false,
}, },
} },
legend: false,
}; };
const lineConfigSet = { const lineConfigSet = {
@ -123,10 +125,11 @@ export default observer(() => {
smooth: true, smooth: true,
point: { point: {
size: 4, size: 4,
shape: "cicle", shape: 'cicle',
}, },
legend: false, legend: false,
meta: { ...cloneDeep(dataFieldAlias) meta: {
...cloneDeep(dataFieldAlias),
// [extProps.yField]: { // [extProps.yField]: {
// alias: dataFieldAlias[extProps.yField]?.alias || extProps.yField, // alias: dataFieldAlias[extProps.yField]?.alias || extProps.yField,
// formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v, // formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v,
@ -139,7 +142,7 @@ export default observer(() => {
const handleChangetimeDataField = (key) => { const handleChangetimeDataField = (key) => {
setTimeDataField(key); setTimeDataField(key);
setLineConfig({ setLineConfig({
...lineConfig, ...cloneDeep(lineConfig),
yField: key, yField: key,
tooltip: { tooltip: {
customItems: (originalItems) => { customItems: (originalItems) => {
@ -151,7 +154,7 @@ export default observer(() => {
}); });
}; };
const [dateField, setDateField] = useState(timeLineKey); const [dateField, setDateField] = useState(timeLineKey);
const handleChangeDateType = ({target: {value}}) => { const handleChangeDateType = ({ target: { value } }) => {
setDateField(value); setDateField(value);
TradeStore.setTimeLineKey(value); TradeStore.setTimeLineKey(value);
if (!isEmpty(TradeStore.searchPayloadHome)) { if (!isEmpty(TradeStore.searchPayloadHome)) {
@ -203,7 +206,8 @@ export default observer(() => {
<Radio.Group options={datePartOptions} optionType="button" onChange={handleChangeDateType} value={dateField} /> <Radio.Group options={datePartOptions} optionType="button" onChange={handleChangeDateType} value={dateField} />
</Space> </Space>
<Spin spinning={timeData.loading}> <Spin spinning={timeData.loading}>
<Line {...lineConfig} data={timeData.dataSource} /> {/* <Line {...lineConfig} data={timeData.dataSource} /> */}
<LineWithKPI dataSource={timeData.dataSource} config={lineConfig} />
</Spin> </Spin>
</section> </section>
<section> <section>
@ -212,7 +216,7 @@ export default observer(() => {
<Row gutter={layoutProps3.gutter}> <Row gutter={layoutProps3.gutter}>
<Col {...layoutProps3}> <Col {...layoutProps3}>
<Bullet {...BUConfig} dataSource={BuData?.dataSource || []} /> <Bullet {...BUConfig} dataSource={BuData?.dataSource || []} />
<h3 style={{ textAlign: 'center' }}>{`各事业部总业绩`}</h3> <h3 style={{ textAlign: 'center' }}>{`各事业部总业绩`}</h3>
</Col> </Col>
{Object.keys(sideData.dataSource).map((key) => ( {Object.keys(sideData.dataSource).map((key) => (
<Col {...layoutProps3} key={key}> <Col {...layoutProps3} key={key}>

@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from 'react';
import { stores_Context } from './../config'; import { stores_Context } from './../config';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Row, Col, Tabs, Spin } from 'antd'; import { Row, Col, Tabs, Spin } from 'antd';
import { flush, objectMapper } from './../utils/commons'; import { flush, isEmpty, objectMapper } from './../utils/commons';
import { KPIObjects } from './../libs/ht'; import { KPIObjects } from './../libs/ht';
import ObjectPanel from '../components/kpi/ObjectPanel'; import ObjectPanel from '../components/kpi/ObjectPanel';
import OverviewPanel from './../components/kpi/OverviewPanel'; import OverviewPanel from './../components/kpi/OverviewPanel';
@ -23,7 +23,7 @@ const objectFilterKey = {
// 'du': 'du', // 'du': 'du',
// 'operator': 'operator', // 'operator': 'operator',
// 'destination': 'destination', // 'destination': 'destination',
// 'country': 'country', 'country': 'country',
}; };
export default observer((props) => { export default observer((props) => {
const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context); const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
@ -42,13 +42,13 @@ export default observer((props) => {
}, [curObject]); }, [curObject]);
const [retObjects, setRetObjects] = useState([]); const [retObjects, setRetObjects] = useState([]);
const onSearchSubmit = (obj, formVal={}) => { const onSearchSubmit = async (obj, formVal={}) => {
const getkpiParam = objectMapper(obj, { const getkpiParam = objectMapper(obj, {
DateType: { key: 'date_type' }, DateType: { key: 'date_type' },
Date1: { key: 'start_date' }, Date1: { key: 'start_date' },
Date2: { key: 'end_date' }, Date2: { key: 'end_date' },
HTBusinessUnits: { key: 'object_id' }, HTBusinessUnits: { key: 'object_id' },
DepartmentList: { key: 'object_id' }, DepartmentList: { key: curObject === 'dept' ? 'object_id' : 'dept_id' },
businessUnits: { key: 'object_id' }, businessUnits: { key: 'object_id' },
WebCode: { key: 'object_id' }, WebCode: { key: 'object_id' },
operator: { key: 'object_id' }, operator: { key: 'object_id' },
@ -56,7 +56,24 @@ export default observer((props) => {
}); });
Object.assign(getkpiParam, { object: curObject }); Object.assign(getkpiParam, { object: curObject });
KPIStore.setSettingYear(formVal?.year?.year() || KPIStore.settingYear); KPIStore.setSettingYear(formVal?.year?.year() || KPIStore.settingYear);
console.log('invoke on search', obj, formVal, getkpiParam); // console.log('invoke on search', obj, formVal, getkpiParam);
if (curObject === 'operator') {
const searchOperator = await DictDataStore.fetchDictData('operator', {
is_assign: 1,
dept_id: (obj?.DepartmentList || '').replace('ALL', ''),
q: searchFormStore.formValues?.operator?.label || '',
});
if ((!isEmpty(getkpiParam.dept_id) && getkpiParam.dept_id !== 'ALL')
|| !isEmpty(searchFormStore.formValues?.operator)) {
// && isEmpty(getkpiParam.object_id)
setRetObjects(searchOperator.dataSource);
getkpiParam.object_id = searchOperator.dataSource.map((ele) => ele.key).join(',');
delete getkpiParam.dept_id;
}
}
getKPIList(getkpiParam);
};
const getKPIList = (getkpiParam) => {
KPIStore.getList(getkpiParam).then((data) => { KPIStore.getList(getkpiParam).then((data) => {
// setDataSource(data); // setDataSource(data);
if (objectFilterKey?.[curObject]) { if (objectFilterKey?.[curObject]) {

Loading…
Cancel
Save