kpi
parent
81313ec86c
commit
33751b3eca
@ -1,13 +1,18 @@
|
|||||||
import { useContext } from 'react';
|
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 { Table } from 'antd';
|
import { Table } from 'antd';
|
||||||
|
import KPISettings from './KPISettings';
|
||||||
|
import { bu, KPISubjects } from './../../libs/ht';
|
||||||
|
|
||||||
export default observer((props) => {
|
export default observer((props) => {
|
||||||
// const { } = useContext(stores_Context);
|
// const { } = useContext(stores_Context);
|
||||||
|
const searchProps = {
|
||||||
|
// shows: ['DateType', 'years', 'HTBusinessUnits'],
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props.title}
|
<KPISettings {...{ searchProps, objects: bu, KPISubjects }} {...props} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,37 +1,263 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext, useState, useEffect, useMemo } from 'react';
|
||||||
import { observer } from "mobx-react";
|
import { observer } from 'mobx-react';
|
||||||
// import { stores_Context } from '../config';
|
import { toJS } from 'mobx';
|
||||||
import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio, Tabs } from 'antd';
|
import moment from 'moment';
|
||||||
|
import { stores_Context } from './../../config';
|
||||||
|
import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio, Tabs, message } from 'antd';
|
||||||
|
import { EditableProTable, ProCard, ProFormField } from '@ant-design/pro-components';
|
||||||
import SearchForm from './../search/SearchForm';
|
import SearchForm from './../search/SearchForm';
|
||||||
import { bu } from './../../libs/ht';
|
import { bu } from './../../libs/ht';
|
||||||
|
import { isEmpty, objectMapper, fixToInt, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter } from './../../utils/commons';
|
||||||
|
|
||||||
|
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) => {
|
export default observer((props) => {
|
||||||
// const { } = useContext(stores_Context);
|
const { KPIStore, date_picker_store: searchFormStore } = useContext(stores_Context);
|
||||||
|
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'),
|
||||||
|
});
|
||||||
|
return () => {};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSearchSubmit = (obj, formVal={}) => {
|
||||||
|
const getkpiParam = objectMapper(obj, { DateType: { key: 'date_type' }, Date1: { key: 'start_date' }, Date2: { key: 'end_date' } });
|
||||||
|
Object.assign(getkpiParam, { object: curObject });
|
||||||
|
KPIStore.setSettingYear(formVal?.year?.year() || KPIStore.settingYear);
|
||||||
|
KPIStore.getList(getkpiParam).then((data) => {
|
||||||
|
setDataSource(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
|
const [editableRowsKeys, setEditableRowKeys] = useState([]);
|
||||||
|
|
||||||
|
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(fixTo4Decimals((Number(record?.yearValue) * inputValue) / 100)) : 0;
|
||||||
|
return (
|
||||||
|
<Space direction={'vertical'}>
|
||||||
|
<Input key={'input'} suffix="%" type={'number'} value={inputValue} onChange={handleInputChange} step={0.1} />
|
||||||
|
<Text type={'secondary'}>{calcV}</Text>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const RenderMonthCell = (row, mon) => {
|
||||||
|
return (
|
||||||
|
<Space direction={'vertical'}>
|
||||||
|
<div>
|
||||||
|
{fixTo2Decimals(row?.[`M${mon}Percent`])}
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<div>{numberConvert10K(fixTo4Decimals((Number(row?.yearValue) * row?.[`M${mon}Percent`]) / 100))}</div>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
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 <PercentInput {...{ record }} month={index + 1} key={`M${index + 1}`} />;
|
||||||
|
},
|
||||||
|
render: (_, row) => RenderMonthCell(row, index + 1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '年度目标',
|
||||||
|
dataIndex: 'yearValue',
|
||||||
|
valueType: 'digit',
|
||||||
|
fieldProps: { style: { width: '100%' }, step: 10000 * 100 },
|
||||||
|
formItemProps: {
|
||||||
|
style: { width: '100%' },
|
||||||
|
},
|
||||||
|
render: (_, row, ...a) => numberConvert10K(row.yearValue),
|
||||||
|
},
|
||||||
|
...monthCol,
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
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, 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);
|
||||||
|
if (e && isEmpty(dataSource)) {
|
||||||
|
const _initialRow = Object.assign({}, initialRow, initialPercentKey);
|
||||||
|
setDataSource([_initialRow]);
|
||||||
|
setEditableRowKeys([_initialRow.key]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setEditableRowKeys(e ? dataSource.map((ele) => ele.key) : []);
|
||||||
|
};
|
||||||
|
const [delKpiIds, setDelKpiIds] = useState([]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row gutter={16} style={{ margin: '0 0 1em' }}>
|
<Row gutter={16} className="mb-1" style1={{ margin: '0 0 1em' }}>
|
||||||
<Col className="gutter-row" span={24} style={{ margin: '0 0 -16px 0', padding: 0 }}>
|
<Col className="gutter-row m-n1 p-none" span={24} style1={{ margin: '0 0 -16px 0', padding: 0 }}>
|
||||||
<SearchForm
|
<SearchForm
|
||||||
defaultValue={{
|
defaultValue={{
|
||||||
'initialValue': {
|
'initialValue': {},
|
||||||
// ...searchPayloadHome,
|
shows: ['DateType', 'years'],
|
||||||
},
|
|
||||||
// hides: ['businessUnits', 'months', 'WebCode', 'dates'],
|
|
||||||
shows: ['DateType', 'DepartmentList', 'WebCode', 'years'],
|
|
||||||
'fieldProps': {
|
'fieldProps': {
|
||||||
DepartmentList: { show_all: false },
|
DepartmentList: { show_all: false },
|
||||||
WebCode: { show_all: false },
|
WebCode: { show_all: false },
|
||||||
years: { hide_vs: true },
|
years: { hide_vs: true },
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
confirmText='查询'
|
confirmText="查询"
|
||||||
onSubmit={(_err, obj, form, str) => {
|
onSubmit={(_err, obj, form, str) => {
|
||||||
// TradeStore.setStateSearch(form);
|
// TradeStore.setStateSearch(form);
|
||||||
// pageRefresh(obj);
|
// pageRefresh(obj);
|
||||||
|
onSearchSubmit(obj, form);
|
||||||
|
setEditOpen(false);
|
||||||
|
setEditableRowKeys([]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<EditableProTable
|
||||||
|
key={KPIStore.settingYear}
|
||||||
|
headerTitle={`毛利`}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="key"
|
||||||
|
scroll={{
|
||||||
|
x: 1000,
|
||||||
|
}}
|
||||||
|
value={dataSource}
|
||||||
|
onChange={onTableChange}
|
||||||
|
recordCreatorProps={false}
|
||||||
|
toolBarRender={() => {
|
||||||
|
return [
|
||||||
|
<Switch
|
||||||
|
unCheckedChildren="查看"
|
||||||
|
checkedChildren="编辑"
|
||||||
|
key={'openEdit'}
|
||||||
|
checked={editOpen}
|
||||||
|
onChange={(e) => {
|
||||||
|
makeInitialTable(e);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
<Button disabled={!editOpen} type="primary" key="save" onClick={onTableSubmit}>
|
||||||
|
保存数据
|
||||||
|
</Button>,
|
||||||
|
];
|
||||||
|
}}
|
||||||
|
editable={{
|
||||||
|
type: 'multiple',
|
||||||
|
editableKeys: editableRowsKeys,
|
||||||
|
actionRender: (row, config, defaultDoms) => {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export default observer((props) => {
|
||||||
|
// const { } = useContext(stores_Context);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row gutter={16} className="mb-1">
|
||||||
|
<Col className="gutter-row mb-n1 p-none" span={24}>
|
||||||
|
<SearchForm
|
||||||
|
defaultValue={{
|
||||||
|
'initialValue': {
|
||||||
|
// ...searchPayloadHome,
|
||||||
|
},
|
||||||
|
// hides: ['businessUnits', 'months', 'WebCode', 'dates'],
|
||||||
|
shows: ['HTBusinessUnits', 'DateType', 'DepartmentList', 'years'],
|
||||||
|
'fieldProps': {
|
||||||
|
HTBusinessUnits: { show_all: true },
|
||||||
|
DepartmentList: { show_all: true },
|
||||||
|
WebCode: { show_all: false },
|
||||||
|
years: { hide_vs: true },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
confirmText="查询"
|
||||||
|
onSubmit={(_err, obj, form, str) => {
|
||||||
|
// TradeStore.setStateSearch(form);
|
||||||
|
// pageRefresh(obj);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16} className="mb-1 p-s1">
|
||||||
|
<Col className="gutter-row mb-n1 p-none" span={24} style={{ backgroundColor: '#ffffff' }}>
|
||||||
|
<Tabs
|
||||||
|
tabPosition={'left'}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
// height: 220,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items={KPIObjects.map((ele, i) => {
|
||||||
|
const id = String(i);
|
||||||
|
return {
|
||||||
|
...ele,
|
||||||
|
children: `Content of tab ${id}`,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,36 @@
|
|||||||
|
import React, {Component} from 'react';
|
||||||
|
import {Select} from 'antd';
|
||||||
|
import {observer} from "mobx-react";
|
||||||
|
import { biz, bu } from '../../libs/ht';
|
||||||
|
|
||||||
|
|
||||||
|
const Business_unit = (props) => {
|
||||||
|
const { store, mode, value, onChange, show_all, ...extProps } = props;
|
||||||
|
const _show_all = ['tags', 'multiple'].includes(mode) ? false : show_all;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
mode={mode || null}
|
||||||
|
allowClear
|
||||||
|
style={{width: '100%',}}
|
||||||
|
placeholder="选择事业部"
|
||||||
|
value={value || undefined } // { key: '-1', label: 'ALL 事业部' }
|
||||||
|
onChange={(value) => {
|
||||||
|
if (typeof onChange === 'function') {
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
// store?.bu_handleChange(value);
|
||||||
|
}}
|
||||||
|
labelInValue={true}
|
||||||
|
{...extProps}
|
||||||
|
>
|
||||||
|
{_show_all ? <Select.Option key="-1" value="ALL">ALL 事业部</Select.Option> : ''}
|
||||||
|
{bu.map(ele => <Select.Option key={ele.key} value={ele.label}>{ele.label}</Select.Option>)}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* HT的事业部
|
||||||
|
*/
|
||||||
|
export default observer(Business_unit);
|
@ -1,3 +1,17 @@
|
|||||||
.ant-tabs.ant-tabs-card > .ant-tabs-nav {
|
.ant-tabs.ant-tabs-card > .ant-tabs-nav {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
.ant-tabs.ant-tabs-left .ant-tabs-content-holder{
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.ant-tabs-content.ant-tabs-content-left .ant-tabs-tabpane.ant-tabs-tabpane-active{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.ant-tabs-content.ant-tabs-content-left .ant-tabs-tabpane .ant-pro-table .ant-pro-card-body{
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-control-input .ant-input-affix-wrapper{
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue