diff --git a/src/App.css b/src/App.css index 678262a..e9e982f 100644 --- a/src/App.css +++ b/src/App.css @@ -7,10 +7,24 @@ .align_left{ text-align: left; } - +.align_center{ + text-align: center; +} +.m-1{ + margin: 1em; +} +.mt-1{ + margin-top: 1em; +} .mb-1{ margin-bottom: 1em; } +.ml-1{ + margin-left: 1em; +} +.mr-1{ + margin-right: 1em; +} .mb-n1{ margin-bottom: -1em; } diff --git a/src/components/kpi/BUPanel.jsx b/src/components/kpi/BUPanel.jsx deleted file mode 100644 index 0ebdd84..0000000 --- a/src/components/kpi/BUPanel.jsx +++ /dev/null @@ -1,18 +0,0 @@ -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'; - -export default observer((props) => { - // const { } = useContext(stores_Context); - const searchProps = { - // shows: ['DateType', 'years', 'HTBusinessUnits'], - }; - return ( - <> - - - ); -}); diff --git a/src/components/kpi/KPISettings.jsx b/src/components/kpi/KPISettings.jsx index f4c9e65..ecec5d4 100644 --- a/src/components/kpi/KPISettings.jsx +++ b/src/components/kpi/KPISettings.jsx @@ -1,161 +1,42 @@ -import { useContext, useState, useEffect } from 'react'; +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 { EditableProTable, ProCard, ProFormField } from '@ant-design/pro-components'; +import { Typography, Row, Col, Tabs, } from 'antd'; import SearchForm from './../search/SearchForm'; import { bu, KPIObjects, KPISubjects } from './../../libs/ht'; -import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter } from './../../utils/commons'; +import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt, merge } from './../../utils/commons'; +import ProfitTable from './SubjectTable/Profit'; + +const Todo = (props) => { + return

TODO

; +}; + +const subjectComponents = { + 'sum_profit': ProfitTable, + 'in_order_count': Todo, + 'confirm_order_count': Todo, + 'depart_order_count': Todo, + 'confirm_rates': Todo, + 'praise_rates': Todo, + 'sum_person_num': Todo, +}; export const KPIObjectsMapped = KPIObjects.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {}); const { Text } = Typography; -const numberConvert10K = (number, scale = 10) => { - return fixTo2Decimals(number / (1000 * scale)) + '万'; -}; export default observer((props) => { - const { KPIStore, DictDataStore } = useContext(stores_Context); - const { sort, initialValue, hides, shows, fieldProps } = { + const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context); + const { sort, initialValue, hides, shows, fieldProps: _fieldProps } = { sort: '', - initialValue: {}, - fieldProps: { - years: { hide_vs: true }, - }, + initialValue: searchFormStore.formValues, + fieldProps: {}, hides: [], shows: ['DateType', 'years'], ...props.searchProps, }; + const fieldProps = merge(_fieldProps, { years: { hide_vs: true } }); const { curObject, objects, KPISubjects, onSearchSubmit } = props; - const curObjectItem = KPIObjectsMapped[curObject]; -console.log(curObjectItem, KPIObjectsMapped, curObject, 'cocococo'); - const [dataSource, setDataSource] = useState([]); - const { settingYear } = KPIStore; - - const [editOpen, setEditOpen] = useState(false); // test: - const [editableRowsKeys, setEditableRowKeys] = useState([]); - // console.log(toJS(KPIStore.pageData ), dataSource, '00000'); - - const PercentInput = ({ value, onChange, record, ...extProps }) => { - // console.log(extProps, '22222222'); - const initialPercent = record.kpiDataMapped?.[`M${extProps.month}`]?.percentVal; - const [inputVal, setInputVal] = useState(value); - const calcV = inputVal ? numberConvert10K(fixTo4Decimals((Number(record?.yearValue) * inputVal) / 100)) : 0; - - const handleInputChange = ({ target: { value } }) => { - setInputVal(value); - }; - const handleInputConfirm = () => { - // onChange?.(inputVal); - // setInputVal(''); - }; - return ( - - - {/* onBlur={handleInputConfirm} onPressEnter={handleInputConfirm} */} - {/* 1 */} - {calcV} - - ); - }; - const RenderInput = (row, mon) => { - // console.log(toJS(row), mon); - return ( - -
- {row.kpiDataMapped?.[`M${mon}`]?.percentVal} - % -
-
{row.kpiDataMapped?.[`M${mon}`]?.value}
-
- ); - }; - const monthCol = new Array(12).fill(1).map((_, index) => { - return { - title: `${index + 1}月`, - dataIndex: `M${index + 1}Percent`, - valueType: 'digit', - width: '6.5em', - fieldProps: { min: 0, max: 100, style: { width: '4em' } }, - renderFormItem: ({ dataIndex, ...item }, { record, isEditable, ...e }, form) => { - return ; - }, - render: (_, row) => RenderInput(row, index + 1), - }; - }); - const initialRow = monthCol.reduce((r, v) => ({ ...r, [v.dataIndex]: 0 }), {}); // v.formItemProps.initialValue - const columns = [ - { - title: curObjectItem.label, - dataIndex: 'object_id', - valueType: 'select', - // ...valueEnum - // fieldProps: { labelInValue: true }, - render: (_, r) => r.object_name, - }, - // { - // title: 'Name', - // dataIndex: 'title', - // //...form rules - // formItemProps: { - // rules: [ - // { - // required: true, - // whitespace: true, - // message: '此项是必填项', - // }, - // ], - // }, - // }, - { - title: '年度目标', - dataIndex: 'yearValue', - valueType: 'digit', - fieldProps: { style: { width: '100%' } }, - formItemProps: { - style: { width: '100%' }, - }, - }, - ...monthCol, - // { - // title: '完成进度', - // dataIndex: 'place', - // valueType: 'percent', - // editable: false, - // width: '6em', - // }, - { - title: ( - - 操作 - { - setEditOpen(e); - setEditableRowKeys(e ? dataSource.map((ele) => ele.key) : []); - // KPIStore.setEditableRowsKeys(e ? dataSource.map((ele) => ele.key) : []); - }} - /> - - ), - valueType: 'option', - // width: 250, - render: () => { - return null; - }, - }, - ]; - const onTableChange = (...argrs) => { - console.log(argrs[0], 'who who who'); - setEditableRowKeys(argrs[0].map((ele) => ele.key)); - // KPIStore.setEditableRowsKeys(argrs[0].map((ele) => ele.key)); - setDataSource(argrs[0]); - // KPIStore.handleTableEdit(argrs[0]); - }; return ( <> @@ -168,8 +49,10 @@ console.log(curObjectItem, KPIObjectsMapped, curObject, 'cocococo'); }} confirmText="查询" onSubmit={(_err, obj, form, str) => { - // TradeStore.setStateSearch(form); - // pageRefresh(obj); + console.log('invoke kpi setting search'); + if (typeof onSearchSubmit === 'function') { + onSearchSubmit(obj); + } }} /> @@ -178,74 +61,14 @@ console.log(curObjectItem, KPIObjectsMapped, curObject, 'cocococo'); { + KPIStore.setSettingSubject(sub); + }} items={KPISubjects.map((ele, i) => { - const id = String(i); + const SubjectTableComponent = subjectComponents[ele.key]; return { ...ele, - children: ( - { - // return [ - // { - // setEditOpen(e); - // setEditableRowKeys(e ? dataSource.map((ele) => ele.key) : []); - // // KPIStore.setEditableRowsKeys(e ? dataSource.map((ele) => ele.key) : []); - // }} - // />, - // , - // ]; - // }} - editable={{ - type: 'multiple', - editableKeys: editableRowsKeys, - actionRender: (row, config, defaultDoms) => { - // console.log(row, config, defaultDoms); - return [defaultDoms.delete]; - }, - onValuesChange: (record, recordList) => { - console.log('on edit, onValuesChange'); - onTableChange(recordList); - }, - onChange: (editableKeys, editableRows) => { - console.log('editable onValuesChange'); - onTableChange(editableRows); - // KPIStore.setEditableRowsKeys() - }, - }} - /> - ), + children: }; })} /> diff --git a/src/components/kpi/ObjectPanel.jsx b/src/components/kpi/ObjectPanel.jsx new file mode 100644 index 0000000..a78c231 --- /dev/null +++ b/src/components/kpi/ObjectPanel.jsx @@ -0,0 +1,23 @@ +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 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'] }, +}; + +export default observer((props) => { + const searchProps = searchFormItemSet?.[props.curObject] || {}; + return ( + <> + + + ); +}); diff --git a/src/components/kpi/SubjectTable/Profit.jsx b/src/components/kpi/SubjectTable/Profit.jsx new file mode 100644 index 0000000..777adb4 --- /dev/null +++ b/src/components/kpi/SubjectTable/Profit.jsx @@ -0,0 +1,252 @@ +import { useContext, useState, useEffect, useMemo } from 'react'; +import { observer } from 'mobx-react'; +import moment from 'moment'; +import { stores_Context } from './../../../config'; +import { Button, Switch, Input, Space, Typography, Row, Col, message } from 'antd'; +import { EditableProTable } from '@ant-design/pro-components'; +import { KPIObjects } from './../../../libs/ht'; +import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from './../../../utils/commons'; + +export const KPIObjectsMapped = KPIObjects.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {}); +const { Text } = Typography; +const initialPercentKey = new Array(12).fill(1).reduce((r, v, i) => ({ ...r, [`M${i + 1}Percent`]: [8, 9].includes(i) ? 10 : 8 }), {}); +const numberConvert10K = (number, scale = 10) => { + return fixTo2Decimals(number / (1000 * scale)) + '万'; +}; + +export default observer((props) => { + const { KPIStore, date_picker_store: searchFormStore } = useContext(stores_Context); + const { curObject, objects, onSearchSubmit } = props; + const curObjectItem = KPIObjectsMapped[curObject]; + + const [dataSource, setDataSource] = useState(KPIStore.pageData); + const [editOpen, setEditOpen] = useState(false); + const [editableRowsKeys, setEditableRowKeys] = useState([]); + useEffect(() => { + setDataSource(KPIStore.pageData); + setEditOpen(false); + return () => {}; + }, [KPIStore.pageData]); + + + const PercentInput = useMemo( + () => + // eslint-disable-next-line react/display-name + ({ value, onChange, record, ...extProps }) => { + // // eslint-disable-next-line react-hooks/rules-of-hooks + const [inputValue, setInputValue] = useState(value); + const handleInputChange = (e) => { + setInputValue(e.target.value); + onChange?.(e.target.value); + }; + const calcV = inputValue ? numberConvert10K(fixToInt((Number(record?.yearValue) * inputValue) / 100)) : 0; + return ( + + + {calcV} + + ); + }, + [] + ); + const RenderMonthCell = (row, mon) => { + return ( + +
+ {fixTo2Decimals(row?.[`M${mon}Percent`])} + % +
+
{numberConvert10K(fixTo4Decimals((Number(row?.yearValue) * row?.[`M${mon}Percent`]) / 100))}
+
+ ); + }; + const monthCol = new Array(12).fill(1).map((_, index) => { + return { + title: `${index + 1}月`, + dataIndex: `M${index + 1}Percent`, + valueType: 'digit', + width: '6.5em', + // fieldProps: { min: 0, max: 100, style: { width: '4em' } }, + renderFormItem: ({ dataIndex, ...item }, { record, isEditable, ...e }, form) => { + return ; + }, + render: (_, row) => RenderMonthCell(row, index + 1), + }; + }); + const columns = [ + { + title: curObjectItem.label, + dataIndex: 'object_id', + editable: false, + render: (_, r) => r.object_name, + }, + { + title: '年度目标', + dataIndex: 'yearValue', + valueType: 'digit', + fieldProps: { style: { width: '100%' }, step: 10000*100 }, + formItemProps: { + style: { width: '100%' }, + }, + }, + ...monthCol, + { + title: ( + + 操作 + { + makeInitialTable(e); + }} + /> + + ), + valueType: 'option', + // width: 250, + render: () => { + return null; + }, + }, + ]; + + const onTableChange = (...argrs) => { + setEditableRowKeys(argrs[0].map((ele) => ele.key)); + setDataSource(argrs[0]); + }; + const onTableSubmit = () => { + const tableData = dataSource.reduce((r, curObj) => { + const allMonth = new Array(12).fill(1).map((_, index) => { + const mIndex = index + 1; + const mVal = (Number(curObj.yearValue) * Number(curObj[`M${mIndex}Percent`])) / 100; + const startM = moment([KPIStore.settingYear, index, 1]); + const pick = (({ object, object_name, object_id, subject, date_type }) => ({ + object, + object_name, + object_id, + subject, + date_type, + }))(curObj); + return { + ...pick, + start_date: startM.format('YYYY-MM-DD'), + end_date: startM.endOf('M').format('YYYY-MM-DD HH:mm'), + value: mVal, + kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined, + key: undefined, + }; + }); + 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, + }))(dataSource?.[0] || {}); + tableData.unshift(yearRow); + console.log('sub', tableData, 'del:', delKpiIds); + // return false; // debug: + KPIStore.onSubmit(tableData, { delQueue: delKpiIds }).then((res) => { + if (res) { + message.success('保存成功'); + setEditOpen(false); + setEditableRowKeys([]); + setDelKpiIds([]); + onSearchSubmit(searchFormStore.formValuesToSub); + return false; + } + message.error('失败, 请重试'); + }); + }; + const initialRow = monthCol.reduce( + (r, v) => ({ + ...r, + [v.dataIndex]: 0, + yearValue: 10000 * 100, + object: curObject, + object_name: '', + object_id: -1, + subject: 'sum_profit', + date_type: searchFormStore.formValuesToSub.DateType, + kpiDataMapped: {}, + key: Date.now().toString(32), + }), + {} + ); // v.formItemProps.initialValue + const makeInitialTable = (e) => { + setEditOpen(e); + // todo: 单独设置之后, 清空筛选会导致无法批量设置新的 + 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) : []); + }; + const [delKpiIds, setDelKpiIds] = useState([]); + return ( + <> + + + { + 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) => { + setEditableRowKeys(editableKeys); + }, + }} + /> + + + + + + + ); +}); diff --git a/src/stores/KPI.js b/src/stores/KPI.js index d31f81b..aa3b922 100644 --- a/src/stores/KPI.js +++ b/src/stores/KPI.js @@ -34,10 +34,14 @@ class KPI { start_date: '2020-01-01', end_date: '2024-12-31 23:59:59', ...param, + object_id: [0, 1, 'ALL'].includes(param?.object_id || 0) ? '' : param.object_id, }; + this.listLoading = true; + this.pageData = []; return req.fetchJSON('/service-Analyse2/getkpi', _param).then((json) => { if (json.errcode === 0) { runInAction(() => { + this.listLoading = false; this.originData = json.result; const yearData = parseKPI(json.result, ['subject', 'object_id']); console.log(111, yearData, yearData[this.settingYear]); @@ -53,7 +57,15 @@ class KPI { this.settingYear = v; } + listLoading = false; + setListLoading(v) { + this.listLoading = v; + } + settingSubject = 'sum_profit'; + setSettingSubject(v) { + this.settingSubject = v; + } originData =[]; pageData = []; diff --git a/src/views/KPI.jsx b/src/views/KPI.jsx index 4ecc327..288e041 100644 --- a/src/views/KPI.jsx +++ b/src/views/KPI.jsx @@ -1,27 +1,72 @@ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import { stores_Context } from './../config'; import { observer } from 'mobx-react'; -import { Row, Col, Tabs } from 'antd'; +import { Row, Col, Tabs, Spin } from 'antd'; +import { flush, objectMapper } from './../utils/commons'; import { KPIObjects } from './../libs/ht'; -import BUPanel from './../components/kpi/BUPanel'; +import ObjectPanel from '../components/kpi/ObjectPanel'; import OverviewPanel from './../components/kpi/OverviewPanel'; import './kpi.css'; const objectComponents = { 'overview': OverviewPanel, - 'bu': BUPanel, - 'dept': BUPanel, - 'du': BUPanel, - 'operator': BUPanel, - 'destination': BUPanel, - 'country': BUPanel, + 'bu': ObjectPanel, + 'dept': ObjectPanel, + 'du': ObjectPanel, + 'operator': ObjectPanel, + 'destination': ObjectPanel, + 'country': ObjectPanel, +}; +const objectFilterKey = { + 'bu': 'HTBusinessUnits', + 'dept': 'DepartmentList', + // 'du': 'du', + // 'operator': 'operator', + // 'destination': 'destination', + // 'country': 'country', }; export default observer((props) => { + const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context); // useEffect(() => { // return () => {}; // }, []); const [curObject, setCurObject] = useState('overview'); const onObjectChange = (object) => { setCurObject(object); + setRetObjects([]); + }; + useEffect(() => { + onSearchSubmit(searchFormStore.formValuesToSub); + + return () => {}; + }, [curObject]); + + const [retObjects, setRetObjects] = useState([]); + const onSearchSubmit = (obj, formVal={}) => { + const getkpiParam = objectMapper(obj, { + DateType: { key: 'date_type' }, + Date1: { key: 'start_date' }, + Date2: { key: 'end_date' }, + HTBusinessUnits: { key: 'object_id' }, + DepartmentList: { key: 'object_id' }, + businessUnits: { key: 'object_id' }, + WebCode: { key: 'object_id' }, + operator: { key: 'object_id' }, + country: { key: 'object_id' }, + }); + Object.assign(getkpiParam, { object: curObject }); + KPIStore.setSettingYear(formVal?.year?.year() || KPIStore.settingYear); + console.log('invoke on search', obj, formVal, getkpiParam); + KPIStore.getList(getkpiParam).then((data) => { + // setDataSource(data); + if (objectFilterKey?.[curObject]) { + const selectItem = searchFormStore.formValues[objectFilterKey[curObject]]; + if (selectItem) { + selectItem.value = selectItem.key; + } + setRetObjects(flush([selectItem])); + } + }); }; return ( <> @@ -31,10 +76,14 @@ export default observer((props) => { onChange={onObjectChange} type="card" items={KPIObjects.map((ele, i) => { - const ItemComponent = objectComponents[ele.key]; + const ObjectItemPanel = objectComponents[ele.key]; return { ...ele, - children: , + children: ( + + + + ), }; })} />