diff --git a/src/components/Data.jsx b/src/components/Data.jsx new file mode 100644 index 0000000..f907dd2 --- /dev/null +++ b/src/components/Data.jsx @@ -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 ? "-" : ( + +
+ {data1} vs {data2} +
+ } color={tagColor}> + {diffPercent} {diffData} + +
+ ); +}; diff --git a/src/components/DateGroupRadio/date.js b/src/components/DateGroupRadio/date.js new file mode 100644 index 0000000..904f5d5 --- /dev/null +++ b/src/components/DateGroupRadio/date.js @@ -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); +}; diff --git a/src/components/DateGroupRadio/index.jsx b/src/components/DateGroupRadio/index.jsx new file mode 100644 index 0000000..e8b1fd2 --- /dev/null +++ b/src/components/DateGroupRadio/index.jsx @@ -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 ? : null}; +});