diff --git a/src/App.jsx b/src/App.jsx
index 1ce1708..718dd76 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -10,9 +10,9 @@ import {
DollarOutlined,
AreaChartOutlined,
WechatOutlined,
- UserOutlined, FlagOutlined, PieChartOutlined
+ UserOutlined, FlagOutlined, PieChartOutlined, BarChartOutlined
} 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 Home from './views/Home';
import Dashboard from './views/Dashboard';
@@ -37,6 +37,7 @@ 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';
const App = () => {
const { Content, Footer, Sider } = Layout;
@@ -123,6 +124,15 @@ const App = () => {
},
{ key: 'kpi', label: 目标, icon: },
{ key: 'distribution', label: 统计分布, icon: },
+ {
+ key: 'detail',
+ label: (
+
+ 统计分析
+
+ ),
+ icon: ,
+ },
];
return (
@@ -165,6 +175,7 @@ const App = () => {
} />
} />
} />
+ } />
}>
} />
} />
diff --git a/src/components/LineWithKPI.jsx b/src/components/LineWithKPI.jsx
new file mode 100644
index 0000000..35bb1dd
--- /dev/null
+++ b/src/components/LineWithKPI.jsx
@@ -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 ;
+});
diff --git a/src/components/Scatter.jsx b/src/components/Scatter.jsx
new file mode 100644
index 0000000..3ef84a2
--- /dev/null
+++ b/src/components/Scatter.jsx
@@ -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 ;
+});
diff --git a/src/components/Waterfall.jsx b/src/components/Waterfall.jsx
index 2ec6bd6..87bf2ec 100644
--- a/src/components/Waterfall.jsx
+++ b/src/components/Waterfall.jsx
@@ -5,7 +5,7 @@ import { merge } from '../utils/commons';
export default observer((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
? [
{
diff --git a/src/components/kpi/KPISettings.jsx b/src/components/kpi/KPISettings.jsx
index ecec5d4..2ae7b3e 100644
--- a/src/components/kpi/KPISettings.jsx
+++ b/src/components/kpi/KPISettings.jsx
@@ -6,6 +6,7 @@ import SearchForm from './../search/SearchForm';
import { bu, KPIObjects, KPISubjects } from './../../libs/ht';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt, merge } from './../../utils/commons';
import ProfitTable from './SubjectTable/Profit';
+import { toJS } from 'mobx';
const Todo = (props) => {
return
TODO
;
@@ -28,7 +29,7 @@ export default observer((props) => {
const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { sort, initialValue, hides, shows, fieldProps: _fieldProps } = {
sort: '',
- initialValue: searchFormStore.formValues,
+ initialValue: '', // searchFormStore.formValues,
fieldProps: {},
hides: [],
shows: ['DateType', 'years'],
@@ -43,15 +44,15 @@ export default observer((props) => {
{
- console.log('invoke kpi setting search');
+ // console.log('invoke kpi setting search');
if (typeof onSearchSubmit === 'function') {
- onSearchSubmit(obj);
+ onSearchSubmit(obj, form);
}
}}
/>
@@ -63,6 +64,7 @@ export default observer((props) => {
tabPosition={'left'}
onChange={(sub) => {
KPIStore.setSettingSubject(sub);
+ // onSearchSubmit(searchFormStore.formValuesToSub);
}}
items={KPISubjects.map((ele, i) => {
const SubjectTableComponent = subjectComponents[ele.key];
diff --git a/src/components/kpi/ObjectPanel.jsx b/src/components/kpi/ObjectPanel.jsx
index a78c231..2caa53a 100644
--- a/src/components/kpi/ObjectPanel.jsx
+++ b/src/components/kpi/ObjectPanel.jsx
@@ -1,23 +1,22 @@
-import { useContext } from 'react';
import { observer } from 'mobx-react';
-// import { stores_Context } from '../config';
-import { Table } from 'antd';
import KPISettings from './KPISettings';
import { bu, KPISubjects } from '../../libs/ht';
+const sort = { DateType: 10, years: 11 };
+const yearInitial = {};
const searchFormItemSet = {
- 'bu': { shows: ['DateType', 'years', 'HTBusinessUnits'] },
- 'dept': { shows: ['DateType', 'years', 'DepartmentList'], fieldProps: { DepartmentList: { allowClear: true } } },
- 'operator': { shows: ['DateType', 'years', 'DepartmentList'] }, // , 'operator'
- 'destination': { shows: ['DateType', 'years', 'destination'] },
- 'country': { shows: ['DateType', 'years', 'country'] },
+ 'bu': { shows: ['DateType', 'years', 'HTBusinessUnits'], sort },
+ 'dept': { shows: ['DateType', 'years', 'DepartmentList'], sort, fieldProps: { DepartmentList: { allowClear: true,isLeaf: true, show_all: false } }, },
+ 'operator': { shows: ['DateType', 'years', 'DepartmentList', 'operator'], fieldProps: { DepartmentList: { allowClear: true, isLeaf: true }, operator: { param: { is_assign: 1 } } }, sort },
+ 'destination': { shows: ['DateType', 'years', 'destination'], sort },
+ 'country': { shows: ['DateType', 'years', 'country'], sort },
};
export default observer((props) => {
const searchProps = searchFormItemSet?.[props.curObject] || {};
return (
<>
-
+
>
);
});
diff --git a/src/components/kpi/OverviewPanel.jsx b/src/components/kpi/OverviewPanel.jsx
index 49f8760..e0cac54 100644
--- a/src/components/kpi/OverviewPanel.jsx
+++ b/src/components/kpi/OverviewPanel.jsx
@@ -20,12 +20,12 @@ export default observer((props) => {
const { curObject } = props;
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
- onSearchSubmit({
- object: curObject,
- date_type: 'applyDate',
- start_date: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'),
- end_date: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'),
- });
+ // onSearchSubmit({
+ // object: curObject,
+ // date_type: 'applyDate',
+ // start_date: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'),
+ // end_date: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'),
+ // });
return () => {};
}, []);
@@ -129,6 +129,7 @@ export default observer((props) => {
value: mVal,
kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined,
key: undefined,
+ group_date_type: 'month',
};
});
return r.concat(allMonth);
@@ -143,6 +144,7 @@ export default observer((props) => {
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',
}))(dataSource?.[0] || {});
tableData.unshift(yearRow);
console.log('sub', tableData, delKpiIds);
diff --git a/src/components/kpi/SubjectTable/Profit.jsx b/src/components/kpi/SubjectTable/Profit.jsx
index 777adb4..be76b4a 100644
--- a/src/components/kpi/SubjectTable/Profit.jsx
+++ b/src/components/kpi/SubjectTable/Profit.jsx
@@ -24,11 +24,11 @@ export default observer((props) => {
const [editableRowsKeys, setEditableRowKeys] = useState([]);
useEffect(() => {
setDataSource(KPIStore.pageData);
+ setEditableRowKeys([]);
setEditOpen(false);
return () => {};
}, [KPIStore.pageData]);
-
const PercentInput = useMemo(
() =>
// eslint-disable-next-line react/display-name
@@ -84,7 +84,7 @@ export default observer((props) => {
title: '年度目标',
dataIndex: 'yearValue',
valueType: 'digit',
- fieldProps: { style: { width: '100%' }, step: 10000*100 },
+ fieldProps: { style: { width: '100%' }, step: 10000 * 100 },
formItemProps: {
style: { width: '100%' },
},
@@ -138,22 +138,23 @@ export default observer((props) => {
value: mVal,
kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || 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);
// return false; // debug:
KPIStore.onSubmit(tableData, { delQueue: delKpiIds }).then((res) => {
@@ -180,28 +181,37 @@ export default observer((props) => {
date_type: searchFormStore.formValuesToSub.DateType,
kpiDataMapped: {},
key: Date.now().toString(32),
+ group_date_type: 'month',
}),
{}
); // v.formItemProps.initialValue
const makeInitialTable = (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)) {
- 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);
setEditableRowKeys(_initialTable.map((ele) => ele.key));
return false;
}
- setEditableRowKeys(e ? dataSource.map((ele) => ele.key) : []);
+ setDataSource(mergePageData);
+ setEditableRowKeys(e ? mergePageData.map((ele) => ele.key) : []);
};
const [delKpiIds, setDelKpiIds] = useState([]);
return (
@@ -226,13 +236,11 @@ export default observer((props) => {
return [defaultDoms.delete];
},
onDelete: (_key, _row) => {
- // console.log('del', _key, _row);
const rowKpiIds = (_row?.kpiData || []).map((ele) => ele.kpi_id);
rowKpiIds.push(_row?.kpiYear?.kpi_id);
setDelKpiIds(rowKpiIds);
},
onValuesChange: (record, recordList) => {
- // console.log('on edit, onValuesChange',record, recordList);
onTableChange(recordList);
},
onChange: (editableKeys, editableRows) => {
@@ -242,9 +250,14 @@ export default observer((props) => {
/>
-
+
+
+ {!editOpen && }
+
>
diff --git a/src/components/kpi/SumProfitPanel.jsx b/src/components/kpi/SubjectTable/SumProfitPanel.jsx
similarity index 91%
rename from src/components/kpi/SumProfitPanel.jsx
rename to src/components/kpi/SubjectTable/SumProfitPanel.jsx
index 0818a00..1b5666d 100644
--- a/src/components/kpi/SumProfitPanel.jsx
+++ b/src/components/kpi/SubjectTable/SumProfitPanel.jsx
@@ -2,8 +2,8 @@ import { useContext } from 'react';
import { observer } from 'mobx-react';
// import { stores_Context } from '../config';
import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio, Tabs } from 'antd';
-import SearchForm from './../search/SearchForm';
-import { bu, KPIObjects } from './../../libs/ht';
+import SearchForm from '../../search/SearchForm';
+import { bu, KPIObjects } from '../../../libs/ht';
export default observer((props) => {
// const { } = useContext(stores_Context);
diff --git a/src/components/search/BusinessSelect.jsx b/src/components/search/BusinessSelect.jsx
index cb94770..c84eb5e 100644
--- a/src/components/search/BusinessSelect.jsx
+++ b/src/components/search/BusinessSelect.jsx
@@ -20,7 +20,7 @@ const Business_unit = (props) => {
}
store?.bu_handleChange(value);
}}
- labelInValue={true}
+ labelInValue={false}
{...extProps}
>
{props.show_all ? ALL 事业部 : ''}
diff --git a/src/components/search/BusinessUnitSelect.jsx b/src/components/search/BusinessUnitSelect.jsx
index 1b0738c..2c24c78 100644
--- a/src/components/search/BusinessUnitSelect.jsx
+++ b/src/components/search/BusinessUnitSelect.jsx
@@ -21,7 +21,7 @@ const Business_unit = (props) => {
}
// store?.bu_handleChange(value);
}}
- labelInValue={true}
+ labelInValue={false}
{...extProps}
>
{_show_all ? ALL 事业部 : ''}
diff --git a/src/components/search/DataTypeSelect.jsx b/src/components/search/DataTypeSelect.jsx
index 731c81b..9d199ba 100644
--- a/src/components/search/DataTypeSelect.jsx
+++ b/src/components/search/DataTypeSelect.jsx
@@ -23,15 +23,16 @@ class DataTypeSelect extends Component {
};
render() {
- const store = this.props.store;
+ const { store, ...extProps } = this.props;
return (
-