diff --git a/package-lock.json b/package-lock.json index 5faf0c6..6533d9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "haina-dashboard", - "version": "2.10.0", + "version": "2.10.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "haina-dashboard", - "version": "2.10.0", + "version": "2.10.7", "dependencies": { "@ant-design/charts": "^1.4.2", "@ant-design/pro-components": "^2.6.16", diff --git a/package.json b/package.json index 865e081..c34ea2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "haina-dashboard", - "version": "2.10.0", + "version": "2.10.7", "private": true, "dependencies": { "@ant-design/charts": "^1.4.2", diff --git a/src/App.jsx b/src/App.jsx index 1c4bec5..5ccfc8f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -53,7 +53,7 @@ import { stores_Context, APP_VERSION } from './config'; import { WaterMark } from '@ant-design/pro-components'; import CooperationIcon from './components/icons/CooperationIcon'; import OPDashboard from './views/OPDashboard'; -import OPActivity from './views/OPActivity'; +import OPProcess from './views/OPProcess'; import OPRisk from './views/OPRisk'; import Cruise from './views/Cruise'; import Hotel from './views/Hotel'; @@ -171,7 +171,7 @@ const App = () => { children: [ // { key: 'xx', type: 'divider' }, { key: 'op_dashboard', label: 结果 }, - // { key: 'op_activity', label: 过程 }, + { key: 'op_process', label: 过程 }, // { key: 'op_risk', label: 提升 }, ], }, @@ -278,7 +278,7 @@ const App = () => { } /> } /> } /> - } /> + } /> } /> diff --git a/src/charts/Customer_care_regular.jsx b/src/charts/Customer_care_regular.jsx index 930d8f8..75e7ec6 100644 --- a/src/charts/Customer_care_regular.jsx +++ b/src/charts/Customer_care_regular.jsx @@ -1,16 +1,17 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import { Row, Col, Divider, Table, Tooltip } from 'antd'; import { InfoCircleOutlined } from '@ant-design/icons'; import { utils, writeFileXLSX } from 'xlsx'; import { stores_Context } from '../config'; import { observer } from 'mobx-react'; import SearchForm from './../components/search/SearchForm'; +import LineWithAvg from '../components/LineWithAvg'; const Customer_care_regular = () => { const { orders_store, date_picker_store, customer_store } = useContext(stores_Context); const regular_data = customer_store.regular_data; - useEffect(() => {}, []); + // useEffect(() => {}, []); return (
@@ -66,7 +67,7 @@ const Customer_care_regular = () => { <> {text}   { - {index === 0 && regular_data.total_data_tips!=='' && } + {index === 0 && regular_data.total_data_tips!=='' && } } ), @@ -98,6 +99,11 @@ const Customer_care_regular = () => { rowKey={(record) => record.ItemName} /> + + + + + { { key: 'COLI_ApplyDate', }, { - title: '订单状态', + title: '订单状态',width: '4rem', dataIndex: 'OrderState', key: 'OrderState', render: (text, record) => {text == 1 ? '成行' : '未成行'}, @@ -188,6 +194,21 @@ const Customer_care_regular = () => { dataIndex: 'COLI_LineClass', key: 'COLI_LineClass', }, + { title: '上次 订单号', dataIndex: 'coli_id_Last', key: 'coli_id_Last', width: '4em', + render: (_, r) => ({ + props: { style: { backgroundColor: '#5B8FF9'+'1A' } }, + children: _, + }), }, + { title: '上次 走团日期', dataIndex: 'COLI_OrderStartDate_Last', key: 'COLI_OrderStartDate_Last',width: '4em', + render: (_, r) => ({ + props: { style: { backgroundColor: '#5B8FF9'+'1A' } }, + children: _, + }), }, + { title: '上次 小组', dataIndex: 'Department_Last', key: 'Department_Last',width: '4em', + render: (_, r) => ({ + props: { style: { backgroundColor: '#5B8FF9'+'1A' } }, + children: _, + }), }, ]} size="small" rowKey={(record) => record.COLI_ID} diff --git a/src/components/LineWithAvg.jsx b/src/components/LineWithAvg.jsx new file mode 100644 index 0000000..4ca77f1 --- /dev/null +++ b/src/components/LineWithAvg.jsx @@ -0,0 +1,168 @@ +import React, { useEffect, useState } from 'react'; +import { Row, Col, Spin } from 'antd'; +import { Line } from '@ant-design/plots'; +import { observer } from 'mobx-react'; +import { dataFieldAlias } from '../libs/ht'; +import DateGroupRadio from '../components/DateGroupRadio'; +import { cloneDeep, groupBy, sortBy } from '../utils/commons'; + +export default observer((props) => { + const { dataSource: rawData, showAVG, showSUM, loading, ...config } = props; + const { xField, yField, yFieldAlias, seriesField } = config; + + const [dataBeforeXChange, setDataBeforeXChange] = useState([]); + + const [dataSource, setDataSource] = useState([]); + const [sumSeries, setSumSeries] = useState([]); + + const line_config = { + // data: dataSource, + padding: 'auto', + xField, + yField, + seriesField, + // seriesField: 'rowLabel', + // xAxis: { + // type: 'timeCat', + // }, + yAxis: { + min: 0, + maxTickInterval: 5, + }, + meta: { + ...cloneDeep(dataFieldAlias), + }, + // smooth: true, + label: {}, // 显示标签 + legend: { + position: 'right-top', + // title: { + // text: '总合计 ' + dataSource.reduce((a, b) => a + b.SumOrder, 0), + // }, + itemMarginBottom: 12, // 垂直间距 + }, + tooltip: { + customItems: (originalItems) => { + return originalItems + .map((ele) => ({ ...ele, valueR: ele.data[yField] })) + .sort(sortBy('valueR')) + .reverse(); + }, + }, + }; + + const [lineConfig, setLineConfig] = useState(cloneDeep(line_config)); + + useEffect(() => { + resetX(); + + return () => {}; + }, [rawData]); + + + useEffect(() => { + if (lineChartX === 'day') { + setDataBeforeXChange(dataSource); + } + + return () => {}; + }, [dataSource]); + + // 日月年切换 + const [lineChartX, setLineChartX] = useState('day'); + const orderCountDataMapper = { data1: 'data1', data2: undefined }; + const orderCountDataFieldMapper = { 'dateKey': xField, 'valueKey': yField, 'seriesKey': seriesField, _f: 'sum' }; + const resetX = () => { + setLineChartX('day'); + setDataSource(rawData); + setDataBeforeXChange(rawData); + // 初始化`平均`线, `总计`线 + const byDays = groupBy(rawData, xField); + const sumY = rawData.reduce((a, b) => a + b[yField], 0); + const avgVal = Math.round(sumY / (Object.keys(byDays).length)); + const avgLine = [ + { type: 'text', position: ['start', avgVal], content: avgVal, offsetX: -15, style: { fill: '#F4664A', textBaseline: 'bottom' } }, + { type: 'line', start: [-10, avgVal], end: ['max', avgVal], style: { stroke: '#F4664A', lineDash: [2, 2] } }, + ]; + setLineConfig({ ...lineConfig, yField, xField, annotations: avgLine }); + if (showSUM) { + const _sumLine = Object.keys(byDays).reduce((r, _d) => { + const summaryVal = byDays[_d].reduce((rows, row) => rows + row[yField], 0); + r.push({ ...byDays[_d][0], [yField]: summaryVal, [seriesField]: '总计' }); + return r; + }, []); + // console.log(_sumLine.map((ele) => ele[yField])); + setSumSeries(_sumLine); + } + }; + + const onChangeXDateFieldGroup = (value, data, avg1) => { + // console.log(value, data, avg1); + const _sumLine = []; + const { xField, yField, seriesField } = lineConfig; + const groupByDate = data.reduce((r, v) => { + (r[v[xField]] || (r[v[xField]] = [])).push(v); + return r; + }, {}); + // console.log(groupByDate); + const _data = Object.keys(groupByDate).reduce((r, _d) => { + const summaryVal = groupByDate[_d].reduce((rows, row) => rows + row[yField], 0); + _sumLine.push({ ...groupByDate[_d][0], [yField]: summaryVal, [seriesField]: '总计' }); + + const xAxisGroup = groupByDate[_d].reduce((a, v) => { + (a[v[seriesField]] || (a[v[seriesField]] = [])).push(v); + return a; + }, {}); + // console.log(xAxisGroup); + Object.keys(xAxisGroup).map((_group) => { + const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row[yField], 0); + r.push({ ...xAxisGroup[_group][0], [yField]: summaryVal, }); + return _group; + }); + return r; + }, []); + // const _sum = Object.values(groupBy(_data, 'dateGroup')).reduce((ac, b) => ({...b, [yField]: 0}), {}); + // console.log(xField, avg1); + // console.log('date source=====', _data); + setLineChartX(value); + setDataSource(_data); + if (showSUM) { + setSumSeries(_sumLine); + } + + // setAvgLine1(avg1); + const avg1Int = Math.round(avg1); + const mergedConfig = { ...lineConfig, + annotations: [ + { type: 'text', position: ['start', avg1Int], content: avg1Int, offsetX: -15, style: { fill: '#F4664A', textBaseline: 'bottom' } }, + { type: 'line', start: [-10, avg1Int], end: ['max', avg1Int], style: { stroke: '#F4664A', lineDash: [2, 2] } }, + ], + }; + // console.log(mergedConfig); + setLineConfig(mergedConfig); + }; + return ( +
+ +
+

+ 走势: {dataFieldAlias[lineConfig.yField].label} +

+ + + + + + + + + + ); +}); diff --git a/src/components/search/SearchForm.jsx b/src/components/search/SearchForm.jsx index 59b58b5..6a7b55f 100644 --- a/src/components/search/SearchForm.jsx +++ b/src/components/search/SearchForm.jsx @@ -15,6 +15,7 @@ import DatePickerCharts from './DatePickerCharts'; import YearPickerCharts from './YearPickerCharts'; import SearchInput from './Input'; import { objectMapper, at, empty, isEmpty } from './../../utils/commons'; +import { departureDateTypes } from './../../libs/ht'; import './search.css'; import HotelStarSelect from './HotelStarSelect'; @@ -53,6 +54,11 @@ export default observer((props) => { transform: (value) => value?.key || '', default: '', }, + 'departureDateType': { + key: 'DateType', + transform: (value) => value?.key || '', + default: '', + }, 'HTBusinessUnits': { key: 'HTBusinessUnits', transform: (value) => { @@ -92,6 +98,11 @@ export default observer((props) => { transform: (value) => Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '', default: '', }, + 'date': { + key: 'date', + transform: (value) => (value ? moment(value).format(SMALL_DATETIME_FORMAT) : undefined), + default: '', + }, 'applyDate': [ { key: 'Date1', @@ -172,7 +183,7 @@ export default observer((props) => { }, }; let dest = {}; - const { applyDate, applyDate2, year, yearDiff, dates, months, ...omittedValue } = values; + const { departureDateType, applyDate, applyDate2, year, yearDiff, dates, months, date, ...omittedValue } = values; dest = { ...omittedValue, ...objectMapper(values, destinationObject) }; for (const key in dest) { if (Object.prototype.hasOwnProperty.call(dest, key)) { @@ -392,6 +403,23 @@ function getFields(props) { , fieldProps?.DateType?.col || 3 ), + item( + 'departureDateType', + 99, + + + , + fieldProps?.departureDateType?.col || 3 + ), item( 'years', 99, @@ -417,6 +445,14 @@ function getFields(props) { , fieldProps?.dates?.col || midCol ), + item( + 'date', + 99, + + + , + fieldProps?.date?.col || midCol + ), item( 'operator', 99, diff --git a/src/libs/ht.js b/src/libs/ht.js index a1fa130..0131a2a 100644 --- a/src/libs/ht.js +++ b/src/libs/ht.js @@ -66,8 +66,10 @@ export const overviewGroup = groups.slice(0, 3); // todo: 花梨鹰 APP Trippest export const sites = [ { value: '2', key: '2', label: 'CHT', code: 'CHT' }, { value: '8', key: '8', label: 'AH', code: 'AH' }, + { value: '186', key: '186', label: 'JH', code: 'JH' }, { value: '163', key: '163', label: 'GH', code: 'GH' }, - { value: '184', key: '184', label: '站外渠道', code: 'ZWQD' }, + { value: '184', key: '184', label: 'GH站外渠道 (中国)', code: 'ZWQD' }, + { value: '185', key: '185', label: 'GH站外渠道 (海外)', code: 'GH_ZWQD_HW' }, { value: '28', key: '28', label: '客运中国', code: 'GHKYZG' }, { value: '7', key: '7', label: '客运海外', code: 'GHKYHW' }, { value: '172', key: '172', label: 'GHToB 海外', code: 'GHTOBHW' }, @@ -102,6 +104,10 @@ export const dateTypes = [ { key: 'confirmDate', value: 'confirmDate', label: '确认日期' }, { key: 'startDate', value: 'startDate', label: '走团日期' }, ]; +export const departureDateTypes = [ + ...dateTypes, + { key: 'departureDate', value: 'departureDate', label: '抵达日期' }, +]; /** * 结果字段 @@ -203,17 +209,70 @@ const calcPPPriceRange = (value) => { if (value < 0) { return '--'; } - const step = 30; // step = 30 USD const start = Math.floor(value / step) * step; const end = start + step; - if (value >= 301) { - return `301-Infinity`; + return `≥301`; } - return `${start === 0 ? start : (start+1)}-${end}`; }; + +function calculateRangeScale(data, numScales = 36) { + if (!data || data.length === 0 || numScales <= 0) { + return []; + } + const sortedData = [...data].sort((a, b) => a - b); + const min = sortedData[0]; + const max = sortedData[sortedData.length - 1]; + if (min === max) { + return [roundToNice(min), roundToNice(min)]; + } + const scales = [roundToNice(min)]; + const scaleSize = sortedData.length / numScales; + for (let i = 1; i < numScales; i++) { + const index = Math.floor(i * scaleSize); + scales.push(roundToNice(sortedData[Math.min(index, sortedData.length - 1)])); + } + scales.push(roundToNice(max)); + return [...new Set(scales)]; +} + +function roundToNice(value) { + if (value === 0) { + return 0; + } + const magnitude = Math.pow(10, Math.floor(Math.log10(Math.abs(value)))); + const normalized = value / magnitude; + let rounded; + if (normalized < 1.5) { + rounded = Math.floor(normalized); + } else if (normalized < 3) { + rounded = Math.floor(normalized * 2) / 2; // round to 0.5 + } else if (normalized < 5) { + rounded = Math.floor(normalized/2) * 2; // round to 2 + } else if (normalized < 7.5) { + rounded = Math.floor(normalized / 5) * 5; + } else { + // rounded = Math.floor(normalized / 5) * 5; + rounded = Math.floor(normalized / 10) * 10; + } + return rounded * magnitude; +}; +const findRange = (value, scale) => { + if (value < scale[0]) { + return `0-${scale[0]}`; // `Value ${value} is below the scale range.`; + } + for (let i = 1; i < scale.length; i++) { + if (value >= scale[i - 1] && value < scale[i]) { + return `${scale[i - 1]}-${scale[i]}`; // `Value ${value} is in the range [${scale[i - 1]}, ${scale[i]})`; + } + } + if (value >= scale[scale.length - 1]) { + return `≥${scale[scale.length - 1]}`; // `Value ${value} is in the range [${scale[scale.length - 1]}, Infinity)`; + } +}; +const SumML_range = [1, 1.5, 2, 3, 4].map(v => v * 10000); /** * 数据透视计算 * @param {object[]} data @@ -227,19 +286,24 @@ export const pivotBy = (_data, [rows, columns, date]) => { // if (groupbyKeys.includes('PPPriceRange')) { // } // 补充计算的字段 + const RTXF_WB_values = cloneDeep(_data).map(ele => ele.RTXF_WB); + // const max_RTXF_WB = Math.max(...RTXF_WB_values); + const RTXF_WB_range = calculateRangeScale(RTXF_WB_values); let data = cloneDeep(_data).map(ele => { - ele.PPPrice = (Number(ele.orderState) === 1 && ele.tourdays && ele.personNum) ? fixToInt(ele.quotePrice / ele.tourdays / ele.personNum) : -1; + ele.PPPrice = (Number(ele.orderState) === 1 && ele.tourdays && ele.personNum) ? fixToInt(ele.quotePrice / ele.tourdays / ele.personNum) : -1; // 报价: 人均天 ele.PPPriceRange = calcPPPriceRange(ele.PPPrice); + ele.RTXF_WB_range = findRange(ele.RTXF_WB, RTXF_WB_range); ele.IsOld_txt = ele.IsOld === '1' ? '老客户' : '否'; ele.isCusCommend_txt = ele.isCusCommend === '1' ? '老客户推荐' : '否'; const hasOld = (ele.IsOld === '1' || ele.isCusCommend === '1') ? 1 : 0; ele.hasOld = hasOld; ele.hasOld_txt = hasOld === 1 ? '老客户(推荐)' : ''; - ele.SumML_ctxt1 = ele.ML > 10000 ? '1W+' : '1W-'; - ele.SumML_ctxt1_5 = ele.ML > 15000 ? '1.5W+' : '1.5W-'; - ele.SumML_ctxt2 = ele.ML > 20000 ? '2W+' : '2W-'; - ele.SumML_ctxt3 = ele.ML > 30000 ? '3W+' : '3W-'; - ele.SumML_ctxt4 = ele.ML > 40000 ? '4W+' : '4W-'; + // ele.SumML_ctxt1 = ele.ML > 10000 ? '1W+' : '1W-'; + // ele.SumML_ctxt1_5 = ele.ML > 15000 ? '1.5W+' : '1.5W-'; + // ele.SumML_ctxt2 = ele.ML > 20000 ? '2W+' : '2W-'; + // ele.SumML_ctxt3 = ele.ML > 30000 ? '3W+' : '3W-'; + // ele.SumML_ctxt4 = ele.ML > 40000 ? '4W+' : '4W-'; + ele.SumML_ctxt = findRange(ele.ML, SumML_range); return ele; }); // 数组的字段值, 拆分处理 @@ -255,6 +319,18 @@ export const pivotBy = (_data, [rows, columns, date]) => { return r; }, []); } + if (groupbyKeys.includes('destinations_AsJOSN')) { + data = _data.reduce((r, v, i) => { + const vjson = isEmpty(v.destinations_AsJOSN) ? [] : v.destinations_AsJOSN; + const xv = (vjson).reduce((rv, cv, vi) => { + rv.push({...v, destinations_AsJOSN: cv, key: vi === 0 ? v.key : `${v.key}@${cv}`}); + return rv; + }, []); + + r = r.concat(xv); + return r; + }, []); + } const getKeys = (keys) => keys.map((keyField) => [...new Set(data.map((f) => f[keyField]))]); const [rowsKeys, columnsKeys, dateKeys] = [getKeys(rows), getKeys(columns), [getKeys([date])[0].filter(s => s)]]; diff --git a/src/stores/CustomerServices.js b/src/stores/CustomerServices.js index e7ed2d4..1e05bc3 100644 --- a/src/stores/CustomerServices.js +++ b/src/stores/CustomerServices.js @@ -3,7 +3,7 @@ import moment from "moment"; import { NavLink } from "react-router-dom"; import * as config from "../config"; import * as req from '../utils/request'; -import { prepareUrl } from '../utils/commons'; +import { groupBy, prepareUrl } from '../utils/commons'; class CustomerServices { @@ -13,7 +13,7 @@ class CustomerServices { this.endDate = moment().endOf('week').subtract(7, 'days'); this.startDateString = this.startDate.format(config.DATE_FORMAT); this.endDateString = this.endDate.format(config.DATE_FORMAT) + '%2023:59'; - this.dateType = 'startDate'; + this.dateType = 'departureDate'; this.inProgress = false; this.selectedAgent = ''; this.selectedTeam = ''; @@ -43,8 +43,8 @@ class CustomerServices { .append('DateType', this.dateType) .append('Date1', this.startDateString) .append('Date2', this.endDateString) - .append('OldDate1', this.startDateString) - .append('OldDate2', this.endDateString) + .append('OldDate1', this.startDateDiffString) + .append('OldDate2', this.endDateDiffString) .append('VEI_SN', this.selectedAgent) .append('DepList', this.selectedTeam) .append('Country', this.selectedCountry) @@ -53,8 +53,9 @@ class CustomerServices { .then(json => { if (json.errcode === 0) { runInAction(() => { - this.agentGroupList = json.result1; - const total1 = json.total1; + const splitTotalList = groupBy(json.result1, row => row.EOI_ObjSN === -1 ? '0' : '1'); + this.agentGroupList = splitTotalList['1']; + const total1 = splitTotalList['0']?.[0] || {}; // json.total1; this.agentGroupListColumns = [ { title: '地接社名称', @@ -293,8 +294,10 @@ class CustomerServices { .then(json => { if (json.errcode === 0) { runInAction(() => { - this.destinationGroupCount = json.result1; - const total1 = json.total1; + const splitTotalList = groupBy(json.result1, row => row.COLD_ServiceCity === -1 ? '0' : '1'); + this.agentGroupList = splitTotalList['1']; + const total1 = splitTotalList['0']?.[0] || {}; // json.total1; + this.destinationGroupCount = splitTotalList['1']; this.destinationGroupCountColumns = [ { title: '城市', @@ -342,7 +345,7 @@ class CustomerServices { dataIndex: 'TotalCost', sorter: (a, b) => a.TotalCost - b.TotalCost, children: [{ - title: total1.totalcost, + title: total1.TotalCost, dataIndex: 'TotalCost' } ] @@ -352,7 +355,7 @@ class CustomerServices { dataIndex: 'TotalPrice', sorter: (a, b) => a.TotalPrice - b.TotalPrice, children: [{ - title: total1.totalprice, + title: total1.TotalPrice, dataIndex: 'TotalPrice' } ] @@ -458,8 +461,8 @@ class CustomerServices { } searchValues = { - DateType: { key: 'startDate', label: '走团日期'}, - countryArea: { key: 'china', label: '国内' }, + DateType: { key: 'departureDate', label: '抵达日期'}, + // departureDateType: { key: 'departureDate', label: '抵达日期'}, }; setSearchValues(obj, values) { @@ -467,6 +470,8 @@ class CustomerServices { this.selectedAgent = obj.agency; this.startDateString = obj.Date1; this.endDateString = obj.Date2; + this.startDateDiffString = obj.DateDiff1; + this.endDateDiffString = obj.DateDiff2; this.selectedCountry = obj.countryArea; this.selectedTeam = obj.DepartmentList.replace('ALL', ''); this.selectedOrderStatus = obj.orderStatus; diff --git a/src/stores/CustomerStore.js b/src/stores/CustomerStore.js index e6686ea..fbda875 100644 --- a/src/stores/CustomerStore.js +++ b/src/stores/CustomerStore.js @@ -2,6 +2,8 @@ import {makeAutoObservable, runInAction} from "mobx"; import { fetchJSON } from '../utils/request'; import * as config from "../config"; import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht'; +import { sortBy } from "../utils/commons"; +import moment from 'moment'; /** * 用于透视的数据 @@ -100,7 +102,10 @@ class CustomerStore { // 老客户 beign regular_customer_order(get_detail = false) { + let pivotByOrder = 'SumOrder'; + let pivotByDate = 'applyDate'; this.regular_data.loading = true; + this.regular_data.detail_loading = get_detail; const date_picker_store = this.rootStore.date_picker_store; let url = '/service-tourdesign/RegularCusOrder'; url += '?Website=' + this.regular_data.webcode.toString() + '&DEI_SNList=' + this.regular_data.groups.toString(); @@ -108,8 +113,12 @@ class CustomerStore { url += '&ApplydateCheck=1&EntrancedateCheck=0&ConfirmDateCheck=0'; } else if(String(this.regular_data.date_type).toLowerCase() === 'confirmdate'){ url += '&ApplydateCheck=0&EntrancedateCheck=0&ConfirmDateCheck=1'; + pivotByOrder = 'ConfirmOrder'; + pivotByDate = 'confirmDate'; }else { url += '&ApplydateCheck=0&EntrancedateCheck=1&ConfirmDateCheck=0'; + pivotByOrder = 'ConfirmOrder'; + pivotByDate = 'startDate'; } url += '&ApplydateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&ApplydateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT); url += '&EntrancedateStart=' + date_picker_store.start_date.format(config.DATE_FORMAT) + '&EntrancedateEnd=' + date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT); @@ -126,9 +135,30 @@ class CustomerStore { .then((json) => { runInAction(() => { if (get_detail) { + this.regular_data.detail_loading = false; this.regular_data.data_detail = json; const dump_l = (json || []).filter(ele => ele.COLI_IsOld !== '' && ele.COLI_IsCusCommend !== '').length; this.regular_data.total_data_tips = dump_l > 0 ? `包含 ${dump_l} 条同时勾选的数据` : ''; + /** 使用明细数据画图 */ + const data_detail = (json || []).map((ele) => ({ + ...ele, + key: ele.COLI_ID, + orderState: ele.OrderState, + applyDate: moment(ele.COLI_ApplyDate).format('YYYY-MM-DD'), + startDate: ele.COLI_OrderStartDate, + confirmDate: moment(ele.COLI_ConfirmDate).format('YYYY-MM-DD'), + })); + const { data: IsOldData, } = pivotBy(data_detail.filter(ele => ele.COLI_IsOld === '是'), [['COLI_IsOld', ], [], pivotByDate]); + const { data: isCusCommendData, } = pivotBy(data_detail.filter(ele => ele.COLI_IsCusCommend === '是'), [['COLI_IsCusCommend', ], [], pivotByDate]); + // console.log('IsOldData====', IsOldData, '\nisCusCommend', isCusCommendData); + // 合并成两个系列 + const seriesData = [].concat(IsOldData.map(ele => ({...ele, _ylabel: '老客户'})), isCusCommendData.map(ele => ({...ele, _ylabel: '老客户推荐'})),).sort(sortBy(pivotByDate)); + // console.log('seriesData====', seriesData); + + this.regular_data.pivotData = seriesData; // { IsOldData, isCusCommendData, }; + this.regular_data.pivotY = pivotByOrder; + this.regular_data.pivotX = pivotByDate; + } else { this.regular_data.data = json; } @@ -159,6 +189,7 @@ class CustomerStore { regular_data = { loading: false, + detail_loading: false, data: [], data_detail: [], total_data_tips: '', @@ -179,6 +210,10 @@ class CustomerStore { WebCode: undefined, // ['ALL'].map(kk => sitesMappedByCode[kk]), DateType: { key: 'applyDate', label: '提交日期'}, }, + + pivotData: [], + pivotY: 'SumOrder', + pivotX: 'applyDate', }; // 老客户 end diff --git a/src/stores/MeetingData2025.js b/src/stores/MeetingData2025.js index d786e0a..0f1b0e7 100644 --- a/src/stores/MeetingData2025.js +++ b/src/stores/MeetingData2025.js @@ -286,77 +286,82 @@ class MeetingData { dataGHSales = async (param) => { this.GHSalesLoading = true; const salesParam = { ...param, DateType: 'confirmDate', WebCode: 'CHT,AH,GH,GHKYZG,GHKYHW,ZWQD', OrderType:'ALL', }; // WebCode: 不含分销 + const partnerParam = { WebCode: 'GHTOBHW,GHTOBZG' }; const [ - { total1: CHSalesData }, // 不含分销 - { total1: AHSalesData }, // 不含分销 - { total1: GHSalesData }, // 不含分销 - { total1: partnerSalesData }, - { total1: totalSalesData }, + { total1: CHSalesDataTotal }, + { total1: CHPartnerSalesData }, + { total1: AHSalesDataTotal }, + { total1: AHpartnerSalesData }, + { total1: GHSalesDataTotal }, + { total1: GHpartnerSalesData }, ] = await Promise.all([ - getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', }), - getDepartmentOrderMLByType({...salesParam, DepartmentList: '28', }), - getDepartmentOrderMLByType({...salesParam, DepartmentList: '33', }), - getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2,28,33',WebCode: 'GHTOBHW,GHTOBZG',}), - getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2,28,33',WebCode: 'ALL',}) + getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', ...partnerParam }), + getDepartmentOrderMLByType({...salesParam, DepartmentList: '28', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, DepartmentList: '28', ...partnerParam }), + getDepartmentOrderMLByType({...salesParam, DepartmentList: '33', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, DepartmentList: '33', ...partnerParam }), ]); - // const { total1: CHSalesDataTotal } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', WebCode: 'ALL',}); - // const { total1: CHPartnerSalesData } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '1,2', WebCode: 'GHTOBHW,GHTOBZG', }); - // const { total1: AHSalesDataTotal } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '28',WebCode: 'ALL',}); - // const { total1: AHpartnerSalesData } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '28', WebCode: 'GHTOBHW,GHTOBZG', }); - // const { total1: GHSalesDataTotal } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '33',WebCode: 'ALL',}); - // const { total1: GHpartnerSalesData } = await getDepartmentOrderMLByType({...salesParam, DepartmentList: '33', WebCode: 'GHTOBHW,GHTOBZG', }); - // 分销 - // const CHSalesData0 = {'COLI_CJCount': CHSalesDataTotal.COLI_CJCount-CHPartnerSalesData.COLI_CJCount, 'COLI_ML2': CHSalesDataTotal.COLI_ML2-CHPartnerSalesData.COLI_ML2}; - // const AHSalesData0 = {'COLI_CJCount': AHSalesDataTotal.COLI_CJCount-AHpartnerSalesData.COLI_CJCount, 'COLI_ML2': AHSalesDataTotal.COLI_ML2-AHpartnerSalesData.COLI_ML2}; - // const GHSalesData0 = {'COLI_CJCount': GHSalesDataTotal.COLI_CJCount-GHpartnerSalesData.COLI_CJCount, 'COLI_ML2': GHSalesDataTotal.COLI_ML2-GHpartnerSalesData.COLI_ML2}; - // const partnerSalesData0 = {'COLI_CJCount': CHPartnerSalesData.COLI_CJCount+AHpartnerSalesData.COLI_CJCount, 'COLI_ML2': CHPartnerSalesData.COLI_ML2+AHpartnerSalesData.COLI_ML2}; - // const totalSalesData0 = { - // 'COLI_CJCount': CHSalesDataTotal.COLI_CJCount + AHSalesDataTotal.COLI_CJCount + GHSalesDataTotal.COLI_CJCount, - // 'COLI_ML2': CHSalesDataTotal.COLI_ML2 + AHSalesDataTotal.COLI_ML2 + GHSalesDataTotal.COLI_ML2, - // }; + // 不含分销 = 总额 - 分销 + const CHSalesData = {'COLI_CJCount': CHSalesDataTotal.COLI_CJCount-CHPartnerSalesData.COLI_CJCount, 'COLI_ML2': CHSalesDataTotal.COLI_ML2-CHPartnerSalesData.COLI_ML2}; + const AHSalesData = {'COLI_CJCount': AHSalesDataTotal.COLI_CJCount-AHpartnerSalesData.COLI_CJCount, 'COLI_ML2': AHSalesDataTotal.COLI_ML2-AHpartnerSalesData.COLI_ML2}; + const GHSalesData = {'COLI_CJCount': GHSalesDataTotal.COLI_CJCount-GHpartnerSalesData.COLI_CJCount, 'COLI_ML2': GHSalesDataTotal.COLI_ML2-GHpartnerSalesData.COLI_ML2}; + const partnerSalesData = {'COLI_CJCount': CHPartnerSalesData.COLI_CJCount+AHpartnerSalesData.COLI_CJCount, 'COLI_ML2': CHPartnerSalesData.COLI_ML2+AHpartnerSalesData.COLI_ML2}; + const totalSalesData = { + 'COLI_CJCount': CHSalesDataTotal.COLI_CJCount + AHSalesDataTotal.COLI_CJCount + GHSalesDataTotal.COLI_CJCount, + 'COLI_ML2': CHSalesDataTotal.COLI_ML2 + AHSalesDataTotal.COLI_ML2 + GHSalesDataTotal.COLI_ML2, + }; const yearStart = moment().startOf("year").format(DATE_FORMAT); const yearEnd = moment().endOf("year").format(SMALL_DATETIME_FORMAT); /** 截至今年 - 成交 */ const [ - { total1: CHDataYear }, - { total1: AHDataYear }, - { total1: GHDataYear }, - { total1: partnerDataYear }, - { total1: totalDataYear }, + { total1: CHSalesYearTotal }, + { total1: CHPartnerSalesYear }, + { total1: AHSalesYearTotal }, + { total1: AHpartnerSalesYear }, + { total1: GHSalesYearTotal }, + { total1: GHpartnerSalesYear }, ] = await Promise.all([ - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2', }), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '28', }), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '33', }), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2,28,7,33',WebCode: 'GHTOBHW,GHTOBZG',}), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2,28,7,33',WebCode: 'ALL',}) + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2', ...partnerParam }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '28', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '28', ...partnerParam }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '33', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '33', ...partnerParam }), ]); - // const { total1: CHDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2', }); - // const { total1: AHDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '28', }); - // const { total1: GHDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '33', }); - // const { total1: partnerDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2,28,7,33', WebCode: 'GHTOBHW,GHTOBZG', }); - // const { total1: totalDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart, DepartmentList: '1,2,28,7,33', WebCode: 'ALL', }); + const CHDataYear = {'COLI_CJCount': CHSalesYearTotal.COLI_CJCount-CHPartnerSalesYear.COLI_CJCount, 'COLI_ML2': CHSalesYearTotal.COLI_ML2-CHPartnerSalesYear.COLI_ML2}; + const AHDataYear = {'COLI_CJCount': AHSalesYearTotal.COLI_CJCount-AHpartnerSalesYear.COLI_CJCount, 'COLI_ML2': AHSalesYearTotal.COLI_ML2-AHpartnerSalesYear.COLI_ML2}; + const GHDataYear = {'COLI_CJCount': GHSalesYearTotal.COLI_CJCount-GHpartnerSalesYear.COLI_CJCount, 'COLI_ML2': GHSalesYearTotal.COLI_ML2-GHpartnerSalesYear.COLI_ML2}; + const partnerDataYear = {'COLI_CJCount': CHPartnerSalesYear.COLI_CJCount+AHpartnerSalesYear.COLI_CJCount, 'COLI_ML2': CHPartnerSalesYear.COLI_ML2+AHpartnerSalesYear.COLI_ML2}; + const totalDataYear = { + 'COLI_CJCount': CHSalesYearTotal.COLI_CJCount + AHSalesYearTotal.COLI_CJCount + GHSalesYearTotal.COLI_CJCount, + 'COLI_ML2': CHSalesYearTotal.COLI_ML2 + AHSalesYearTotal.COLI_ML2 + GHSalesYearTotal.COLI_ML2, + }; /** 截至今年 - 走团 */ const [ - { total1: CHStartDataYear }, - { total1: AHStartDataYear }, - { total1: GHStartDataYear }, - { total1: partnerStartDataYear }, - { total1: totalStartDataYear }, + { total1: CHStartDataYearTotal }, + { total1: CHPartnerStartDataYear }, + { total1: AHStartDataYearTotal }, + { total1: AHpartnerStartDataYear }, + { total1: GHStartDataYearTotal }, + { total1: GHpartnerStartDataYear }, ] = await Promise.all([ - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DepartmentList: '1,2', DateType: 'startDate' }), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DepartmentList: '28', DateType: 'startDate' }), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DepartmentList: '33', DateType: 'startDate' }), - /** 截至今年 - 走团 - 分销 */ - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DepartmentList: '1,2,28,7,33',WebCode: 'GHTOBHW,GHTOBZG', DateType: 'startDate',}), - getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DepartmentList: '1,2,28,7,33',WebCode: 'ALL', DateType: 'startDate',}) + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '1,2', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '1,2', ...partnerParam }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '28', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '28', ...partnerParam }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '33', WebCode: 'ALL', }), + getDepartmentOrderMLByType({...salesParam, Date1: yearStart, Date2:yearEnd, DateType: 'startDate', DepartmentList: '33', ...partnerParam }), ]); - // const { total1: CHStartDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart,DepartmentList: '1,2', DateType: 'startDate' }); - // const { total1: AHStartDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart,DepartmentList: '28', DateType: 'startDate' }); - // const { total1: GHStartDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart,DepartmentList: '33', DateType: 'startDate' }); - /** 截至今年 - 走团 - 分销 */ - // const { total1: partnerStartDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart,DepartmentList: '1,2,28,7,33', WebCode: 'GHTOBHW,GHTOBZG', DateType: 'startDate' }); - // const { total1: totalStartDataYear } = await getDepartmentOrderMLByType({...salesParam, Date1: yearStart,DepartmentList: '1,2,28,7,33', WebCode: 'ALL', DateType: 'startDate' }); + const CHStartDataYear = {'COLI_CJCount': CHStartDataYearTotal.COLI_CJCount-CHPartnerStartDataYear.COLI_CJCount, 'COLI_ML2': CHStartDataYearTotal.COLI_ML2-CHPartnerStartDataYear.COLI_ML2}; + const AHStartDataYear = {'COLI_CJCount': AHStartDataYearTotal.COLI_CJCount-AHpartnerStartDataYear.COLI_CJCount, 'COLI_ML2': AHStartDataYearTotal.COLI_ML2-AHpartnerStartDataYear.COLI_ML2}; + const GHStartDataYear = {'COLI_CJCount': GHStartDataYearTotal.COLI_CJCount-GHpartnerStartDataYear.COLI_CJCount, 'COLI_ML2': GHStartDataYearTotal.COLI_ML2-GHpartnerStartDataYear.COLI_ML2}; + const partnerStartDataYear = {'COLI_CJCount': CHPartnerStartDataYear.COLI_CJCount+AHpartnerStartDataYear.COLI_CJCount, 'COLI_ML2': CHPartnerStartDataYear.COLI_ML2+AHpartnerStartDataYear.COLI_ML2}; + const totalStartDataYear = { + 'COLI_CJCount': CHStartDataYearTotal.COLI_CJCount + AHStartDataYearTotal.COLI_CJCount + GHStartDataYearTotal.COLI_CJCount, + 'COLI_ML2': CHStartDataYearTotal.COLI_ML2 + AHStartDataYearTotal.COLI_ML2 + GHStartDataYearTotal.COLI_ML2, + }; const rows = [ { diff --git a/src/stores/SalesCRMData.js b/src/stores/SalesCRMData.js index da4cbc6..c0b9610 100644 --- a/src/stores/SalesCRMData.js +++ b/src/stores/SalesCRMData.js @@ -1,6 +1,6 @@ import { makeAutoObservable, runInAction, toJS } from 'mobx'; import { fetchJSON } from '../utils/request'; -import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique } from '../utils/commons'; +import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique, cloneDeep } from '../utils/commons'; import { groupsMappedByCode, dataFieldAlias } from './../libs/ht'; import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config'; import moment from 'moment'; @@ -19,6 +19,20 @@ const fetchResultsData = async (param) => { return json.errcode === 0 ? json.result : []; }; +const fetchProcessData = async (param) => { + const defaultParam = { + WebCode: 'All', + DepartmentList: '', + opisn: -1, + Date1: '', + Date2: '', + groupType: '', + groupDateType: '', + }; + const json = await fetchJSON('/service-Analyse2/sales_crm_process', { ...defaultParam, ...param }); + return json.errcode === 0 ? json.result : []; +}; + class SalesCRMData { constructor(appStore) { this.appStore = appStore; @@ -30,17 +44,11 @@ class SalesCRMData { const retKey = param.groupDateType === '' ? (param.groupType === 'overview' ? 'dataSource' : 'details') : 'byDate'; this.results.loading = true; - const date90={ - Date1: moment().subtract(90, 'days').format(DATE_FORMAT), - Date2: moment().subtract(30, 'days').format(SMALL_DATETIME_FORMAT), - }; - const date180={ - Date1: moment().subtract(180, 'days').format(DATE_FORMAT), - Date2: moment().subtract(50, 'days').format(SMALL_DATETIME_FORMAT), - }; + const date90=this.searchValues.date90; + const date180=this.searchValues.date180; const [result90, result180] = await Promise.all([ - fetchResultsData({ ...this.searchValues, ...date90, ...param }), - fetchResultsData({ ...this.searchValues, ...date180, ...param }), + fetchResultsData({ ...this.searchValuesToSub, ...date90, ...param }), + fetchResultsData({ ...this.searchValuesToSub, ...date180, ...param }), ]); const _90O = groupBy(result90, 'groupsKey'); const _180O = groupBy(result180, 'groupsKey'); @@ -63,12 +71,11 @@ class SalesCRMData { return this.results; } - async getResultData(param = {}) { const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate'; this.results.loading = true; this.results[retKey] = []; - const res = await fetchResultsData({ ...this.searchValues, ...param }); + const res = await fetchResultsData({ ...this.searchValuesToSub, ...param }); runInAction(() => { this.results.loading = false; this.results[retKey] = retKey === 'byOperator' ? res.filter(ele => ele.SumML > 0).sort(sortDescBy('SumML')) : res; @@ -76,23 +83,56 @@ class SalesCRMData { return this.results; }; + async getProcessData(param = {}) { + // const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate'; + const retKey = param.groupDateType === '' ? (param.groupType !== 'operator' ? 'dataSource' : 'details') : 'byDate'; + this.process.loading = true; + this.process[retKey] = []; + const res = await fetchProcessData({ ...this.searchValuesToSub, ...param }); + runInAction(() => { + this.process.loading = false; + this.process[retKey] = [].concat(this.process[retKey], res); + // this.process[retKey] =retKey === 'byOperator' ? res.filter(ele => ele.) + }); + } + searchValues = { - // DateType: { key: 'confirmDate', label: '确认日期'}, + date: moment(), + DateType: { key: 'applyDate', label: '提交日期'}, WebCode: { key: 'All', label: '所有来源' }, // IncludeTickets: { key: '1', label: '含门票'}, - DepartmentList: [groupsMappedByCode.GH], // test: GH + DepartmentList: [groupsMappedByCode.GH], operator: '-1', opisn: '-1', + date90: { + Date1: moment().subtract(90, 'days').format(DATE_FORMAT), + Date2: moment().subtract(30, 'days').format(SMALL_DATETIME_FORMAT), + }, + date180: { + Date1: moment().subtract(180, 'days').format(DATE_FORMAT), + Date2: moment().subtract(50, 'days').format(SMALL_DATETIME_FORMAT), + } }; searchValuesToSub = {}; setSearchValues(obj, values) { this.searchValues = { ...this.searchValues, ...values }; + if (values.date) { + this.searchValues.date90 = { + Date1: (values.date.clone()).subtract(90, 'days').format(DATE_FORMAT), + Date2: (values.date.clone()).subtract(30, 'days').format(SMALL_DATETIME_FORMAT), + }; + this.searchValues.date180 = { + Date1: (values.date.clone()).subtract(180, 'days').format(DATE_FORMAT), + Date2: (values.date.clone()).subtract(50, 'days').format(SMALL_DATETIME_FORMAT), + }; + } this.searchValuesToSub = obj; } results = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] }; + process = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] }; resetData = () => { this.results.loading = false; for (const key of Object.keys(this.results)) { @@ -100,6 +140,11 @@ class SalesCRMData { this.results[key] = []; } } + for (const key of Object.keys(this.process)) { + if (key !== 'loading') { + this.process[key] = []; + } + } }; } export default SalesCRMData; diff --git a/src/views/AgentGroupCount.jsx b/src/views/AgentGroupCount.jsx index c4e0dae..211243a 100644 --- a/src/views/AgentGroupCount.jsx +++ b/src/views/AgentGroupCount.jsx @@ -28,12 +28,12 @@ const AgentGroupCount = () => { ...date_picker_store.formValues, ...customerServicesStore.searchValues, }, - shows: ['agency', 'DateType', 'DepartmentList', 'countryArea', 'dates'], + shows: ['agency', 'departureDateType', 'DepartmentList', 'countryArea', 'dates'], fieldProps: { DepartmentList: { show_all: true, mode: 'multiple' }, WebCode: { show_all: false, mode: 'multiple' }, dates: { hide_vs: true }, - DateType: { disabledKeys: ['applyDate'] }, + departureDateType: { disabledKeys: ['applyDate'] }, }, }} onSubmit={(_err, obj, form) => { @@ -46,6 +46,9 @@ const AgentGroupCount = () => { 地接社团信息 + + +
{ pagination={false} scroll={{ x: 1000 }} /> - - - diff --git a/src/views/AgentGroupList.jsx b/src/views/AgentGroupList.jsx index 1a23268..545c8ac 100644 --- a/src/views/AgentGroupList.jsx +++ b/src/views/AgentGroupList.jsx @@ -32,12 +32,12 @@ const AgentGroupList = () => { ...date_picker_store.formValues, ...customerServicesStore.searchValues, }, - shows: ['DateType', 'DepartmentList', 'dates'], + shows: ['departureDateType', 'DepartmentList', 'dates'], fieldProps: { DepartmentList: { show_all: true }, WebCode: { show_all: false, mode: 'multiple' }, dates: { hide_vs: true }, - DateType: { disabledKeys: ['applyDate'] }, + departureDateType: { disabledKeys: ['applyDate'] }, }, }} onSubmit={(_err, obj, form, str) => { diff --git a/src/views/DataPivot.jsx b/src/views/DataPivot.jsx index 1fb9cd9..1fac8f3 100644 --- a/src/views/DataPivot.jsx +++ b/src/views/DataPivot.jsx @@ -26,15 +26,18 @@ const filterFields = [ { key: 'IsOld_txt', label: '是否老客户' }, { key: 'isCusCommend_txt', label: '是否老客户推荐' }, { key: 'hasOld_txt', label: '老客户(推荐)' }, + { key: 'HotelStar', label: '酒店星级' }, { key: 'destinationCountry_AsJOSN', label: '目的地国籍' }, - { key: 'PPPriceRange', label: '人均天/单区间(外币)' }, + { key: 'destinations_AsJOSN', label: '目的地城市' }, + { key: 'RTXF_WB_range', label: '人天消费(外币)' }, + { key: 'PPPriceRange', label: '人均天/单(外币)' }, // { key: 'unitPPPriceRange', label: '人均天(外币)' }, - // todo: 目的地, 目的地国家, - { key: 'SumML_ctxt1', label: '毛利范围[1W]' }, - { key: 'SumML_ctxt1_5', label: '毛利范围[1.5W]' }, - { key: 'SumML_ctxt2', label: '毛利范围[2W]' }, - { key: 'SumML_ctxt3', label: '毛利范围[3W]' }, - { key: 'SumML_ctxt4', label: '毛利范围[4W]' }, + // { key: 'SumML_ctxt1', label: '毛利范围[1W]' }, + // { key: 'SumML_ctxt1_5', label: '毛利范围[1.5W]' }, + // { key: 'SumML_ctxt2', label: '毛利范围[2W]' }, + // { key: 'SumML_ctxt3', label: '毛利范围[3W]' }, + // { key: 'SumML_ctxt4', label: '毛利范围[4W]' }, + { key: 'SumML_ctxt', label: '毛利' }, ]; const filterFieldsMapped = filterFields.reduce((r, v) => ({ ...r, [v.key]: v }), {}); @@ -706,6 +709,7 @@ export default observer((props) => { /> + { !pivotDateColumns[0].includes('destinationCountry_AsJOSN') && !pivotDateColumns[0].includes('destinations_AsJOSN') &&

@@ -721,6 +725,7 @@ export default observer((props) => {

+ } ); }); diff --git a/src/views/DestinationGroupCount.jsx b/src/views/DestinationGroupCount.jsx index 401d6e4..5c305c4 100644 --- a/src/views/DestinationGroupCount.jsx +++ b/src/views/DestinationGroupCount.jsx @@ -25,13 +25,13 @@ const DestinationGroupCount = () => { countryArea: { key: 'china', label: '国内' }, ...customerServicesStore.searchValues, }, - shows: ['DateType', 'DepartmentList', 'countryArea', 'orderStatus', 'dates'], + shows: ['departureDateType', 'DepartmentList', 'countryArea', 'orderStatus', 'dates'], fieldProps: { DepartmentList: { show_all: true, mode: 'multiple' }, orderStatus: { show_all: true }, countryArea: { show_all: false }, dates: { hide_vs: true }, - DateType: { disabledKeys: ['applyDate'] }, + departureDateType: { disabledKeys: ['applyDate'] }, }, }} onSubmit={(_err, obj, form) => { @@ -44,6 +44,9 @@ const DestinationGroupCount = () => { 目的地团信息 + + +
{ pagination={false} scroll={{ x: 1000 }} /> - - - diff --git a/src/views/DestinationGroupList.jsx b/src/views/DestinationGroupList.jsx index 7dbf37b..0e27fad 100644 --- a/src/views/DestinationGroupList.jsx +++ b/src/views/DestinationGroupList.jsx @@ -33,13 +33,13 @@ const DestinationGroupList = () => { countryArea: { key: 'china', label: '国内' }, ...customerServicesStore.searchValues, }, - shows: ['DateType', 'DepartmentList', 'dates'], + shows: ['departureDateType', 'DepartmentList', 'dates'], fieldProps: { DepartmentList: { show_all: true }, orderStatus: { show_all: true }, countryArea: { show_all: false }, dates: { hide_vs: true }, - DateType: { disabledKeys: ['applyDate'] }, + departureDateType: { disabledKeys: ['applyDate'] }, }, }} onSubmit={(_err, obj, form) => { diff --git a/src/views/Distribution.jsx b/src/views/Distribution.jsx index 124100d..e884022 100644 --- a/src/views/Distribution.jsx +++ b/src/views/Distribution.jsx @@ -83,21 +83,15 @@ export default observer(() => { dataIndex: 'SumOrder', render: (v, r) => ( <> - - - {v} - - - + {v} + 同比: 环比: - - ), }, @@ -106,21 +100,15 @@ export default observer(() => { dataIndex: 'ConfirmOrder', render: (v, r) => ( <> - - - {v} - - - + {v} + 同比: 环比: - - ), }, @@ -129,21 +117,15 @@ export default observer(() => { dataIndex: 'SumML', render: (v, r) => ( <> - - - {dataFieldAlias.SumML.formatter(v)} - - - + {dataFieldAlias.SumML.formatter(v)} + 同比: 环比: - - ), }, diff --git a/src/views/OPActivity.jsx b/src/views/OPActivity.jsx deleted file mode 100644 index 9c42e41..0000000 --- a/src/views/OPActivity.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { Children, useContext, useState } from 'react'; -import { observer } from 'mobx-react'; -import { stores_Context } from '../config'; -import moment from 'moment'; -import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined } from '@ant-design/icons'; -import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd'; -import SearchForm from './../components/search/SearchForm'; - -export default observer((props) => { - const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context); - - const { formValues, siderBroken } = searchFormStore; - - const activityTableProps = { - columns: [ - { title: '', dataIndex: 'op', key: 'op' }, // 维度: 顾问 - { - title: '顾问动作', - key: 'date', - children: [ - { title: '首次响应率24H', dataIndex: 'action', key: 'action' }, - { title: '48H内报价率', dataIndex: 'action', key: 'action' }, - { title: '一次报价率', dataIndex: 'action', key: 'action' }, - { title: '二次报价率', dataIndex: 'action', key: 'action' }, - { title: '>50条会话', dataIndex: 'action', key: 'action' }, - { title: '违规数', dataIndex: 'action', key: 'action' }, - ], - }, - { - title: '客人回复', - key: 'department', - children: [ - { title: '首次回复率24H', dataIndex: 'action', key: 'action' }, - { title: '48H内报价回复率', dataIndex: 'action', key: 'action' }, - { title: '一次报价回复率', dataIndex: 'action', key: 'action' }, - { title: '二次报价回复率', dataIndex: 'action', key: 'action' }, - ], - }, - ], - }; - - const riskTableProps = { - columns: [ - { title: '', dataIndex: 'date', key: 'date' }, // 维度 - { title: '>24H回复', dataIndex: 'action', key: 'action' }, - { title: '首次报价周期>48h', dataIndex: 'action', key: 'action' }, - { title: '报价次数<1次', dataIndex: 'action' }, - { title: '报价次数<2次', dataIndex: 'action' }, - ], - }; - - return ( - <> - - - { - sale_store.setSearchValues(obj, form); - // pageRefresh(obj); - }} - /> - - -
-
- -
-

未成行订单

-
- - - ); -}); diff --git a/src/views/OPDashboard.jsx b/src/views/OPDashboard.jsx index 772e50a..68a5eb5 100644 --- a/src/views/OPDashboard.jsx +++ b/src/views/OPDashboard.jsx @@ -10,6 +10,19 @@ import { cloneDeep, fixTo2Decimals, sortBy, isEmpty, pick } from '../utils/commo import Column from '../components/Column'; import { groupsMappedByKey } from './../libs/ht'; +const COLOR_SETS = [ + "#FFFFFF", + // "#5B8FF9", + // "#FF6B3B", + "#9FB40F", + "#76523B", + "#DAD5B5", + "#E19348", + "#F383A2", +]; + +const transparentHex = '1A'; + const numberConvert10K = (number, scale = 10) => { return fixTo2Decimals((number/(1000*scale))); }; @@ -34,7 +47,7 @@ export default observer((props) => { const separateParam = []; if (includeCH) { const inCH = deptList.filter(k => ['1', '2', '7'].includes(k)).join(','); - separateParam.push({ DepartmentList: inCH, retLabel: 'CH'}); + separateParam.push({ DepartmentList: inCH, retLabel: 'CH'}); } if (includeAH) { separateParam.push({ DepartmentList: '28', retLabel: 'AH'}); @@ -103,14 +116,17 @@ export default observer((props) => { * 业绩数据列 * ! 成行率: CH个人成行率<12%, AH个人成行率<8% */ - const dataFields = (suffix) => [ + const dataFields = (suffix, colRootIndex) => [ { key: 'ConfirmRates' + suffix, title: '成行率', dataIndex: [suffix, 'ConfirmRates'], width: '5em', // CH个人成行率<12%, AH个人成行率<8%刷红 - render: (val, r) => ({ props: { style: { color: val < (retPropsMinRatesSet?.[r?.retProps || 'default'] || retPropsMinRatesSet.default) ? 'red' : 'green' } }, children: val ? `${val}%` : '' }), + render: (val, r) => ({ + props: { style: { color: val < (retPropsMinRatesSet?.[r?.retProps || 'default'] || retPropsMinRatesSet.default) ? 'red' : 'green', backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } }, + children: val ? `${val}%` : '', + }), sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmRates - b[suffix].ConfirmRates), }, { @@ -118,7 +134,10 @@ export default observer((props) => { title: '业绩/万', dataIndex: [suffix, 'SumML'], width: '5em', - render: (_, r) => numberConvert10K(_), + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } }, + children: numberConvert10K(_), + }), sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumML - b[suffix].SumML), }, { @@ -126,6 +145,10 @@ export default observer((props) => { title: '团数', dataIndex: [suffix, 'ConfirmOrder'], width: '5em', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } }, + children: _, + }), sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmOrder - b[suffix].ConfirmOrder), }, { @@ -133,6 +156,10 @@ export default observer((props) => { title: '订单数', dataIndex: [suffix, 'SumOrder'], width: '5em', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } }, + children: _, + }), sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumOrder - b[suffix].SumOrder), }, { @@ -140,6 +167,10 @@ export default observer((props) => { title: '老客户团数', dataIndex: [suffix, 'ResumeOrder'], width: '5em', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } }, + children: _, + }), sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeOrder - b[suffix].ResumeOrder), }, { @@ -147,7 +178,10 @@ export default observer((props) => { title: '老客户成行率', dataIndex: [suffix, 'ResumeRates'], width: '5em', - render: (val, r) => ({ children: val ? `${val}%` : '' }), + render: (val, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } }, + children: val ? `${val}%` : '' + }), sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeRates - b[suffix].ResumeRates), }, ]; @@ -168,12 +202,12 @@ export default observer((props) => { { title: '前90 -30天', key: 'date', - children: dataFields('result90'), + children: dataFields('result90', 0), }, { title: '前180 -50天', key: 'department', - children: dataFields('result180'), + children: dataFields('result180', 1), }, ], rowClassName: (record, rowIndex) => { @@ -231,7 +265,7 @@ export default observer((props) => { ...formValues, ...SalesCRMDataStore.searchValues, }, - shows: ['DepartmentList', 'WebCode'], + shows: ['DepartmentList', 'WebCode', 'date'], fieldProps: { DepartmentList: { show_all: false, mode: 'multiple', col: 5 }, WebCode: { show_all: false, mode: 'multiple', col: 5 }, diff --git a/src/views/OPProcess.jsx b/src/views/OPProcess.jsx new file mode 100644 index 0000000..78d2bee --- /dev/null +++ b/src/views/OPProcess.jsx @@ -0,0 +1,257 @@ +import React, { useContext } from 'react'; +import { observer } from 'mobx-react'; +import { stores_Context } from '../config'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Row, Col, Table, Tooltip } from 'antd'; +import SearchForm from '../components/search/SearchForm'; +import { pick } from '../utils/commons'; + +const COLOR_SETS = [ + "#FFFFFF", + // "#5B8FF9", + // "#FF6B3B", + "#9FB40F", + "#76523B", + "#DAD5B5", + "#E19348", + "#F383A2", +]; + +const transparentHex = '1A'; + +export default observer((props) => { + const { SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context); + + // const { formValues, siderBroken } = searchFormStore; + const { formValues, formValuesToSub, siderBroken } = searchFormStore; + const _formValuesToSub = pick(formValuesToSub, ['DepartmentList', 'WebCode']); + const { resetData, process } = SalesCRMDataStore; + const operatorObjects = process.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel })); + + const pageRefresh = async (obj) => { + resetData(); + Promise.allSettled([ + SalesCRMDataStore.getProcessData({ + ...(obj || _formValuesToSub), + // ...subParam, + // groupType: 'overview', + groupType: 'dept', + groupDateType: '', + }), + SalesCRMDataStore.getProcessData({ + ...(obj || _formValuesToSub), + // ...subParam, + groupType: 'operator', + groupDateType: '', + }), + ]); + }; + + const tableSorter = (a, b, colName) => (a.groupType !== 'operator' ? -1 : b.groupType !== 'operator' ? 0 : a[colName] - b[colName]); + const percentageRender = val => val ? `${val}%` : ''; + + const activityTableProps = { + rowKey: 'groupsKey', + pagination: { pageSize: 10, showSizeChanger: false }, + size: 'small', + rowClassName: (record, rowIndex) => { + return record.groupType === 'operator' ? '': 'ant-tag-blue'; + }, + showSorterTooltip: false, + columns: [ + { title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem', + filterSearch: true, + filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)), + onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator', + }, // 维度: 顾问 + { + title: '顾问动作', + key: 'date', + children: [ + { title: () => ( + <> + 首次响应率24H{' '} + + + + + ), dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender, + sorter: (a, b) => tableSorter(a, b, 'firstTouch24'), + }, + { title: () => ( + <> + 48H内报价率{' '} + + + + + ), dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender , + sorter: (a, b) => tableSorter(a, b, 'firstQuote48'), }, + { title: () => ( + <> + 一次报价率{' '} + + + + + ), dataIndex: 'quote1', key: 'quote1',render: percentageRender , + sorter: (a, b) => tableSorter(a, b, 'quote1'), }, + { title: () => ( + <> + 二次报价率{' '} + + + + + ), dataIndex: 'quote2', key: 'quote2',render: percentageRender , + sorter: (a, b) => tableSorter(a, b, 'quote2'), }, + { title: () => ( + <> + >50条会话{' '} + + + + + ), dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender , + sorter: (a, b) => tableSorter(a, b, 'turnsGT50'), }, + { title: () => ( + <> + 违规数{' '} + + + + + ), dataIndex: 'violations', key: 'violations', + sorter: (a, b) => tableSorter(a, b, 'violations'), }, + ], + }, + { + title: '客人回复', + key: 'department', + children: [ + { + title: () => ( + <> + 首次回复率24H{' '} + + + + + ), dataIndex: 'firstReply24', key: 'firstReply24', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } }, + children: percentageRender(_), + }), + sorter: (a, b) => tableSorter(a, b, 'firstReply24'), }, + { + title: () => ( + <> + 48H内报价回复率{' '} + + + + + ), dataIndex: 'replyQuote48', key: 'replyQuote48', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } }, + children: percentageRender(_), + }), + sorter: (a, b) => tableSorter(a, b, 'replyQuote48'), }, + { + title: () => ( + <> + 一次报价回复率{' '} + + + + + ), dataIndex: 'replyQuote1', key: 'replyQuote1', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } }, + children: percentageRender(_), + }), + sorter: (a, b) => tableSorter(a, b, 'replyQuote1'), }, + { + title: () => ( + <> + 二次报价回复率{' '} + + + + + ), dataIndex: 'replyQuote2', key: 'replyQuote2', + render: (_, r) => ({ + props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } }, + children: percentageRender(_), + }), + sorter: (a, b) => tableSorter(a, b, 'replyQuote2'), }, + ], + }, + ], + }; + + const riskTableProps = { + rowKey: 'groupsKey', + pagination: { pageSize: 10, showSizeChanger: false }, + size: 'small', + rowClassName: (record, rowIndex) => { + return record.groupType === 'operator' ? '': 'ant-tag-blue'; + }, + showSorterTooltip: false, + columns: [ + { title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem', + filterSearch: true, + filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)), + onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator', + // render: (text, record) => { // todo: 点击查看不成行订单明细 + // }, + }, // 维度: 顾问 + { title: '>24H回复', dataIndex: 'lostTouch24', key: 'lostTouch24', + sorter: (a, b) => tableSorter(a, b, 'lostTouch24'), + }, + { title: '首次报价周期>48h', dataIndex: 'lostQuote48', key: 'lostQuote48', + sorter: (a, b) => tableSorter(a, b, 'lostQuote48'), + }, + { title: '报价次数<1次', dataIndex: 'lostQuote1', key: 'lostQuote1', + sorter: (a, b) => tableSorter(a, b, 'lostQuote1'), + }, + { title: '报价次数<2次', dataIndex: 'lostQuote2', key: 'lostQuote2', + sorter: (a, b) => tableSorter(a, b, 'lostQuote2'), + }, + ], + }; + + return ( + <> + + + { + SalesCRMDataStore.setSearchValues(obj, form); + pageRefresh(obj); + }} + /> + + +
+
+ +
+

未成行订单 数量

+
+ + + ); +}); diff --git a/src/views/Orders.jsx b/src/views/Orders.jsx index 7f899ab..b0df4cb 100644 --- a/src/views/Orders.jsx +++ b/src/views/Orders.jsx @@ -1,6 +1,6 @@ import React, { Component } from "react"; import { Row, Col, Tabs, Table, Divider, Spin } from "antd"; -import { ContainerOutlined, BlockOutlined, SmileOutlined, TagsOutlined, GlobalOutlined, FullscreenOutlined, DingtalkOutlined, CarryOutOutlined, CoffeeOutlined } from "@ant-design/icons"; +import { ContainerOutlined, BlockOutlined, SmileOutlined, TagsOutlined, GlobalOutlined, FullscreenOutlined, DingtalkOutlined, CarryOutOutlined, CoffeeOutlined, ClockCircleOutlined, HeartOutlined, IdcardOutlined, ContactsOutlined } from "@ant-design/icons"; import { stores_Context } from "../config"; import { Line, Pie } from "@ant-design/charts"; import { observer } from "mobx-react"; @@ -472,11 +472,14 @@ class Orders extends Component { key: 'ToB', label: ( - + 客运类别 ), }, + {key: 'FoodRequirement',label: (饮食要求),}, + {key: 'hobbies',label: (兴趣爱好),}, + {key: 'ages',label: (年龄段),}, ].map((ele) => { return { ...ele,