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

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

@ -30,7 +30,7 @@ export default observer((props) => {
const config = merge({
color: {
range: [ '#FFF3E1', '#FFe0b0', '#bfeec8'], // '#FFbcb8', '#FFe0b0',
range: [ '#FFF3E1', '#FFF3E1', '#FFe0b0', '#bfeec8'], // '#FFbcb8', '#FFe0b0',
measure: '#5B8FF9',
target: '#FF9845',
},

@ -29,14 +29,14 @@ export default observer((props) => {
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});
r.push({...v, [config.yField]: v[kpiKey], [config.seriesField]: dataFieldAlias[kpiKey].label, extraLine: true,});
}
return r;
}, []);
const mergeLineConfig = merge({
color: ['#598cf3', '#F4664A', '#FAAD14'],
lineStyle: (data) => {
console.log(data);
// console.log(data);
if (data[config.seriesField].includes('目标')) {
return {
lineDash: [4, 4],

@ -6,6 +6,8 @@ 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 Count from './SubjectTable/Count';
import Rates from './SubjectTable/Rates';
import { toJS } from 'mobx';
const Todo = (props) => {
@ -14,12 +16,12 @@ const Todo = (props) => {
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,
'in_order_count': Count,
'confirm_order_count': Count,
'depart_order_count': Count,
'confirm_rates': Rates,
'praise_rates': Rates,
'sum_person_num': Count,
};
export const KPIObjectsMapped = KPIObjects.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {});
@ -64,7 +66,7 @@ export default observer((props) => {
tabPosition={'left'}
onChange={(sub) => {
KPIStore.setSettingSubject(sub);
// onSearchSubmit(searchFormStore.formValuesToSub);
onSearchSubmit(searchFormStore.formValuesToSub);
}}
items={KPISubjects.map((ele, i) => {
const SubjectTableComponent = subjectComponents[ele.key];

@ -0,0 +1,263 @@
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 = 1) => {
return fixTo2Decimals(number / (1000 * scale)) + 'K';
};
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);
setEditableRowKeys([]);
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 (
<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: curObjectItem.label,
dataIndex: 'object_id',
editable: false,
render: (_, r) => r.object_name,
},
{
title: '年度目标',
dataIndex: 'yearValue',
valueType: 'digit',
fieldProps: { style: { width: '100%' }, step: 10000 * 1 },
formItemProps: {
style: { width: '100%' },
},
},
...monthCol,
{
title: (
<Space>
<span>操作</span>
<Switch
unCheckedChildren="查看"
checkedChildren="编辑"
key={'openEdit'}
// defaultChecked={true}
checked={editOpen}
onChange={(e) => {
makeInitialTable(e);
}}
/>
</Space>
),
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,
group_date_type: 'month',
};
});
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);
}, []);
// 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 * 1,
object: curObject,
object_name: '',
object_id: -1,
subject: KPIStore.settingSubject,
date_type: searchFormStore.formValuesToSub.DateType,
kpiDataMapped: {},
key: Date.now().toString(32),
group_date_type: 'month',
}),
{}
); // v.formItemProps.initialValue
const makeInitialTable = (e) => {
setEditOpen(e);
// test: ,
const _initialRow = Object.assign({}, initialRow, initialPercentKey);
const _objects = isEmpty(objects) ? curObjectItem?.data || [] : objects;
const _initialTable = _objects.map((obj) => ({
...cloneDeep(_initialRow),
object_name: obj.label,
object_id: obj.value,
key: Date.now().toString(32) + obj.value,
}));
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)) {
setDataSource(_initialTable);
setEditableRowKeys(_initialTable.map((ele) => ele.key));
return false;
}
setDataSource(mergePageData);
setEditableRowKeys(e ? mergePageData.map((ele) => ele.key) : []);
};
const [delKpiIds, setDelKpiIds] = useState([]);
return (
<>
<Row gutter={16} className="mb-1 ">
<Col className="gutter-row mb-n1 p-none" span={24}>
<EditableProTable
key={KPIStore.settingYear}
// headerTitle={``}
columns={columns}
rowKey="key"
scroll={{
x: 1000,
}}
value={dataSource}
onChange={onTableChange}
recordCreatorProps={false}
editable={{
type: 'multiple',
editableKeys: editableRowsKeys,
actionRender: (row, config, defaultDoms) => {
return [defaultDoms.delete];
},
onDelete: (_key, _row) => {
const rowKpiIds = (_row?.kpiData || []).map((ele) => ele.kpi_id);
rowKpiIds.push(_row?.kpiYear?.kpi_id);
setDelKpiIds(rowKpiIds);
},
onValuesChange: (record, recordList) => {
onTableChange(recordList);
},
onChange: (editableKeys, editableRows) => {
setEditableRowKeys(editableKeys);
},
}}
/>
</Col>
<Col className="gutter-row mb-n1 p-none mt-1 align_center" span={24}>
<Space size={'large'}>
<Button className="mt-1 mb-1 align_center" disabled={!editOpen} type="primary" key="save" onClick={onTableSubmit}>
保存数据
</Button>
{!editOpen && <Button className="mt-1 mb-1 align_center" disabled={false} type={'ghost'} key="initTable" onClick={() => {makeInitialTable(true);}}>
编辑设置
</Button>}
</Space>
</Col>
</Row>
</>
);
});

@ -155,7 +155,7 @@ export default observer((props) => {
}))(curObj);
return r.concat(allMonth, yearRow);
}, []);
console.log('sub', tableData, 'del:', delKpiIds);
// console.log('sub', tableData, 'del:', delKpiIds);
// return false; // debug:
KPIStore.onSubmit(tableData, { delQueue: delKpiIds }).then((res) => {
if (res) {

@ -0,0 +1,263 @@
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}Val`]: [8, 9].includes(i) ? 10 : 8 }), {});
const numberConvert10K = (number, scale = 1) => {
return fixTo2Decimals(number / (1000 * scale)) + 'K';
};
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(() => {
// console.log(KPIStore.pageData);
setDataSource(KPIStore.pageData);
setEditableRowKeys([]);
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);
};
return (
<Input key={'input'} suffix="%" type={'number'} value={inputValue} onChange={handleInputChange} step={0.1} />
);
},
[]
);
const RenderMonthCell = (row, mon) => {
return (
<Space direction={'vertical'}>
<div>
{fixTo2Decimals(row?.[`M${mon}Val`])}
<span>%</span>
</div>
</Space>
);
};
const monthCol = new Array(12).fill(1).map((_, index) => {
return {
title: `${index + 1}`,
dataIndex: `M${index + 1}Val`,
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: curObjectItem.label,
dataIndex: 'object_id',
editable: false,
render: (_, r) => r.object_name,
},
{
title: '年度目标',
dataIndex: 'yearValue',
valueType: 'percent',
formItemProps: {
style: { width: '100%' },
},
renderFormItem: () => <PercentInput />
},
...monthCol,
{
title: (
<Space>
<span>操作</span>
<Switch
unCheckedChildren="查看"
checkedChildren="编辑"
key={'openEdit'}
// defaultChecked={true}
checked={editOpen}
onChange={(e) => {
makeInitialTable(e);
}}
/>
</Space>
),
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 mVal = Number(curObj[`M${mIndex}Val`]);
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,
group_date_type: 'month',
unit: '%',
};
});
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',
unit: '%',
}))(curObj);
return r.concat(allMonth, 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: 15,
object: curObject,
object_name: '',
object_id: -1,
subject: KPIStore.settingSubject,
date_type: searchFormStore.formValuesToSub.DateType,
kpiDataMapped: {},
key: Date.now().toString(32),
group_date_type: 'month',
unit: '%',
}),
{}
); // v.formItemProps.initialValue
const makeInitialTable = (e) => {
setEditOpen(e);
// test: ,
const _initialRow = Object.assign({}, initialRow, initialPercentKey);
const _objects = isEmpty(objects) ? curObjectItem?.data || [] : objects;
const _initialTable = _objects.map((obj) => ({
...cloneDeep(_initialRow),
object_name: obj.label,
object_id: obj.value,
key: Date.now().toString(32) + obj.value,
}));
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)) {
setDataSource(_initialTable);
setEditableRowKeys(_initialTable.map((ele) => ele.key));
return false;
}
setDataSource(mergePageData);
setEditableRowKeys(e ? mergePageData.map((ele) => ele.key) : []);
};
const [delKpiIds, setDelKpiIds] = useState([]);
return (
<>
<Row gutter={16} className="mb-1 ">
<Col className="gutter-row mb-n1 p-none" span={24}>
<EditableProTable
key={KPIStore.settingYear}
// headerTitle={``}
columns={columns}
rowKey="key"
scroll={{
x: 1000,
}}
value={dataSource}
onChange={onTableChange}
recordCreatorProps={false}
editable={{
type: 'multiple',
editableKeys: editableRowsKeys,
actionRender: (row, config, defaultDoms) => {
return [defaultDoms.delete];
},
onDelete: (_key, _row) => {
const rowKpiIds = (_row?.kpiData || []).map((ele) => ele.kpi_id);
rowKpiIds.push(_row?.kpiYear?.kpi_id);
setDelKpiIds(rowKpiIds);
},
onValuesChange: (record, recordList) => {
onTableChange(recordList);
},
onChange: (editableKeys, editableRows) => {
setEditableRowKeys(editableKeys);
},
}}
/>
</Col>
<Col className="gutter-row mb-n1 p-none mt-1 align_center" span={24}>
<Space size={'large'}>
<Button className="mt-1 mb-1 align_center" disabled={!editOpen} type="primary" key="save" onClick={onTableSubmit}>
保存数据
</Button>
{!editOpen && <Button className="mt-1 mb-1 align_center" disabled={false} type={'ghost'} key="initTable" onClick={() => {makeInitialTable(true);}}>
编辑设置
</Button>}
</Space>
</Col>
</Row>
</>
);
});

@ -99,7 +99,7 @@ class SearchInput extends React.Component {
};
handleChange = (value, option) => {
console.log('invoke denpendencies change', value);
// console.log('invoke denpendencies change', value);
this.setState({ value }, () => this.props.onChange(value, option));
};

@ -117,7 +117,7 @@ export const dataFieldAlias = dataFieldOptions.reduce(
*/
export const KPIObjects = [
{ key: 'overview', value: 'overview', label: '海纳' },
{ key: 'bu', value: 'bu', label: '事业部', data: bu },
{ key: 'bu', value: 'bu', label: 'HT事业部', data: bu },
{ key: 'dept', value: 'dept', label: '小组', data: leafGroup },
{ key: 'du', value: 'du', label: '销售小组', data: deptUnits },
{ key: 'operator', value: 'operator', label: '顾问' },

@ -22,13 +22,14 @@ class KPI {
}
async onSubmit(tableData, { delQueue }) {
this.listLoading = true;
const flushData = tableData.filter(row => !isEmpty(row.value) || !isEmpty(row?.kpi_id));
const postRes = isEmpty(flushData) ? true : await this.saveOrUpdate(flushData);
const delRes = isEmpty(flush(delQueue)) ? true : await this.delByID(delQueue);
return postRes && delRes;
}
getList(param = {}) {
async getList(param = {}) {
const _param = {
date_type: 'applyDate',
start_date: '2020-01-01',
@ -38,7 +39,7 @@ class KPI {
};
this.listLoading = true;
this.pageData = [];
return req.fetchJSON('/service-Analyse2/getkpi', _param).then((json) => {
const json = await req.fetchJSON('/service-Analyse2/getkpi', _param);
if (json.errcode === 0) {
runInAction(() => {
this.listLoading = false;
@ -49,7 +50,6 @@ class KPI {
});
}
return this.pageData;
});
}
settingYear = moment().year();

@ -130,11 +130,6 @@ export default observer(() => {
legend: false,
meta: {
...cloneDeep(dataFieldAlias),
// [extProps.yField]: {
// alias: dataFieldAlias[extProps.yField]?.alias || extProps.yField,
// formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v,
// max: Math.ceil(yMax / 0.95),
// },
},
};
const [timeDataField, setTimeDataField] = useState('SumML');
@ -147,7 +142,7 @@ export default observer(() => {
tooltip: {
customItems: (originalItems) => {
// process originalItems,
const items = originalItems.map((ele) => ({ ...ele, name: dataFieldAlias[key]?.alias || key }));
const items = originalItems.map((ele) => ({ ...ele, name: ele.data?.extraLine ? ele.name : (dataFieldAlias[key]?.alias || key) }));
return items;
},
},

@ -58,6 +58,7 @@ export default observer((props) => {
KPIStore.setSettingYear(formVal?.year?.year() || KPIStore.settingYear);
// console.log('invoke on search', obj, formVal, getkpiParam);
if (curObject === 'operator') {
KPIStore.setListLoading(true);
const searchOperator = await DictDataStore.fetchDictData('operator', {
is_assign: 1,
dept_id: (obj?.DepartmentList || '').replace('ALL', ''),
@ -71,11 +72,12 @@ export default observer((props) => {
delete getkpiParam.dept_id;
}
}
getKPIList(getkpiParam);
await getKPIList(getkpiParam);
};
const getKPIList = (getkpiParam) => {
KPIStore.getList(getkpiParam).then((data) => {
// setDataSource(data);
const getKPIList = async (getkpiParam) => {
const _data = await KPIStore.getList(getkpiParam);
// KPIStore.getList(getkpiParam).then((data) => {
// // setDataSource(data);
if (objectFilterKey?.[curObject]) {
const selectItem = searchFormStore.formValues[objectFilterKey[curObject]];
if (selectItem) {
@ -83,7 +85,7 @@ export default observer((props) => {
}
setRetObjects(flush([selectItem]));
}
});
// });
};
return (
<>

Loading…
Cancel
Save