feat: 时间轴切换组件
parent
0398f4c08d
commit
f0b3fd5e2a
@ -0,0 +1,17 @@
|
||||
import { Tag } from 'antd';
|
||||
import { CaretUpOutlined, CaretDownOutlined } from '@ant-design/icons';
|
||||
|
||||
export const VSTag = (diffPercent, diffData, data1, data2) => {
|
||||
const CaretIcon = parseInt(diffPercent) < 0 ? CaretDownOutlined : CaretUpOutlined;
|
||||
const tagColor = parseInt(diffPercent) < 0 ? "gold" : "lime";
|
||||
return parseInt(diffPercent) === 0 ? "-" : (
|
||||
<span>
|
||||
<div>
|
||||
{data1} vs {data2}
|
||||
</div>
|
||||
<Tag icon={<CaretIcon />} color={tagColor}>
|
||||
{diffPercent} {diffData}
|
||||
</Tag>
|
||||
</span>
|
||||
);
|
||||
};
|
@ -0,0 +1,132 @@
|
||||
import moment from 'moment';
|
||||
|
||||
export const datePartOptions = [
|
||||
{ label: '日', value: 'day' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '季', value: 'quarter' },
|
||||
{ label: '年', value: 'year' },
|
||||
];
|
||||
export const datePartMethod = {
|
||||
'day': (date) => {
|
||||
return { 'dateKey': date, 'groupKey': date, date };
|
||||
},
|
||||
'week': (date, ...args) => {
|
||||
const dateO = moment(date);
|
||||
const year = dateO.weekYear();
|
||||
const week = dateO.week();
|
||||
// const weekOfMonth = dateO.week() - moment(date).startOf('month').week() + 1;
|
||||
const key = `W${String(week).padStart(2, '0')}`;
|
||||
const dateKey = `${year}-W${String(week).padStart(2, '0')}`;
|
||||
return { dateKey, 'groupKey': dateKey, date };
|
||||
},
|
||||
'quarter': (date) => {
|
||||
const dateO = moment(date);
|
||||
// const key = dateO.format('YYYY-Q');
|
||||
const key = `${dateO.year()}-${String(dateO.quarter()).padStart(2, 'Q')}`;
|
||||
const dateKey = `${dateO.year()}-${String(dateO.quarter()).padStart(2, 'Q')}`;
|
||||
return { dateKey, 'groupKey': key, date };
|
||||
},
|
||||
'month': (date) => {
|
||||
const dateO = moment(date);
|
||||
const key = dateO.format('YYYY-MM');
|
||||
const dateKey = dateO.format('YYYY-MM');
|
||||
return { dateKey, 'groupKey': key, date };
|
||||
},
|
||||
'year': (date) => {
|
||||
const dateO = moment(date);
|
||||
const key = dateO.format('YYYY');
|
||||
const dateKey = dateO.format('YYYY');
|
||||
return { dateKey, 'groupKey': key, date };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array} data 结果数组
|
||||
* @param {*} dateType 切换的日期类型
|
||||
* @param {Object} mapper 映射结果字段
|
||||
* @returns {Object} { data, avg }
|
||||
* * data: 转换后的数组
|
||||
* * avg: 平均值
|
||||
*/
|
||||
export const parseDateType = (data, dateType = 'day', { dateKey, valueKey, seriesKey, _f }) => {
|
||||
const _calcF = {
|
||||
'avg': (sum, len) => sum / len,
|
||||
};
|
||||
const seriesDataMapped = data.reduce((r, v) => {
|
||||
const _k = v[seriesKey];
|
||||
(r[_k] || (r[_k] = [])).push(v);
|
||||
return r;
|
||||
}, {});
|
||||
const everySeries = Object.keys(seriesDataMapped).reduce((rs, _series) => {
|
||||
const e = seriesDataMapped[_series].reduce((r, v) => {
|
||||
const datePart = datePartMethod[dateType](v[dateKey]);
|
||||
const mergeKey = `${datePart.groupKey}@${_series}`;
|
||||
(r[mergeKey] || (r[mergeKey] = [])).push({ ...v, 'dateKey': datePart.dateKey, 'groupKey': datePart.groupKey, datePart });
|
||||
return r;
|
||||
}, {});
|
||||
return { ...rs, ...e };
|
||||
}, {});
|
||||
const resultKeys = Object.keys(everySeries);
|
||||
resultKeys.sort();
|
||||
const dateArr = [];
|
||||
const groupSum = resultKeys.reduce((a, key) => {
|
||||
const [_dateKey, _seriesKey] = key.split('@');
|
||||
dateArr.push(_dateKey);
|
||||
const containDate = everySeries[key].map((ele) => ele.datePart.date);
|
||||
const containDateM = [...new Set(containDate)].map((ele) => moment(ele));
|
||||
const min = moment.min(containDateM).format('YYYY-MM-DD');
|
||||
const max = moment.max(containDateM).format('YYYY-MM-DD');
|
||||
const dateRangeStr = min === max ? min : `${min}~${max}`;
|
||||
const dateRange = [min, max];
|
||||
const summaryVal = everySeries[key].reduce((rows, row) => rows + row[valueKey], 0);
|
||||
const retValue = _f === 'sum' ? summaryVal : _calcF(summaryVal, everySeries[key].length);
|
||||
a.push({ groupKey: key, value: retValue, dateKey: dateRangeStr, dateRange, containDate, [seriesKey]: _seriesKey, [dateKey]: _dateKey });
|
||||
return a;
|
||||
}, []);
|
||||
const avgDiv = [...new Set(dateArr)].length;
|
||||
const avgVal = groupSum.length !== 0 ? groupSum.reduce((s, c) => s + c.value, 0) / avgDiv : 0;
|
||||
return { data: groupSum, avgVal };
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array} dataRaw 结果数组
|
||||
* @param {*} dateGroup 切换的日期类型, radio onchange 的值
|
||||
* @param {Object} dataMapper 映射数据集字段 { data1, data2 }
|
||||
* @param {Object} mapper 映射结果字段 { dateKey, valueKey, _f }
|
||||
* * dateKey: 日期. 'ApplyDate'
|
||||
* * valueKey: 结果. 'orderCount'
|
||||
* * seriesKey: 序列. 'WebCode'
|
||||
* * _f: 计算方法. 'sum' | 'avg
|
||||
* @author LYT
|
||||
*/
|
||||
export const resultDataCb = (dataRaw, dateGroup, { data1, data2 }, fieldMapper, cb) => {
|
||||
const _data1 = data1 ? dataRaw[data1] : dataRaw;
|
||||
const _data2 = data2 ? dataRaw[data2] : [];
|
||||
const parse1 = parseDateType(_data1, dateGroup, fieldMapper);
|
||||
const parseData1 = parse1.data.map((ele) => ({
|
||||
[fieldMapper.dateKey]: ele[fieldMapper.dateKey],
|
||||
[fieldMapper.valueKey]: ele.value,
|
||||
[fieldMapper.seriesKey]: ele[fieldMapper.seriesKey],
|
||||
groups: _data1[0].groups,
|
||||
dateKey: ele.dateKey,
|
||||
dateRange: ele.dateRange,
|
||||
dateGroup: ele[fieldMapper.dateKey],
|
||||
}));
|
||||
const parse2 = parseDateType(_data2, dateGroup, fieldMapper);
|
||||
const parseData2 = parse2.data.map((ele) => ({
|
||||
[fieldMapper.dateKey]: ele.groupKey,
|
||||
[fieldMapper.valueKey]: ele.value,
|
||||
groups: _data2[0].groups,
|
||||
dateKey: ele.dateKey,
|
||||
dateRange: ele.dateRange,
|
||||
dateGroup: ele[fieldMapper.dateKey],
|
||||
}));
|
||||
const useKeys = parseData1.map((ele) => ele[fieldMapper.dateKey]);
|
||||
const reindecData2 = parseData2.map((ele, index) => ({ ...ele, [fieldMapper.dateKey]: useKeys[index] || `X.${ele[fieldMapper.dateKey]}`, dateKey: ele.dateKey }));
|
||||
const retData = [].concat(parseData1, reindecData2);
|
||||
const avg1 = parse1.avgVal;
|
||||
cb(dateGroup, retData, avg1);
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
import { Radio } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { datePartOptions, resultDataCb } from './date';
|
||||
|
||||
export default observer((props) => {
|
||||
const { visible, dataRaw, dataMapper, fieldMapper, onChange, ...extProps } = props;
|
||||
const _dataMapper = dataMapper || { 'data1': null, data2: null };
|
||||
const handleChange = ({ target: { value } }) => {
|
||||
resultDataCb(dataRaw, value, _dataMapper, fieldMapper, onChange);
|
||||
};
|
||||
return <>{visible !== false ? <Radio.Group options={datePartOptions} optionType="button" onChange={handleChange} {...extProps} /> : null}</>;
|
||||
});
|
Loading…
Reference in New Issue