diff --git a/package.json b/package.json index 883d2d0..23ce52f 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "braft-editor": "^2.3.9", "classnames": "^2.2.6", "cos-js-sdk-v5": "^1.3.5", + "echarts": "^5.3.3", "js-export-excel": "^1.1.4", "linq": "^4.0.0", "lodash": "^4.17.11", diff --git a/src/pages/order/order-member-statement/index.jsx b/src/pages/order/order-member-statement/index.jsx index 8c62138..a4ed26e 100644 --- a/src/pages/order/order-member-statement/index.jsx +++ b/src/pages/order/order-member-statement/index.jsx @@ -1,28 +1,317 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; -import { Button, Card, DatePicker } from 'antd'; +import { Button, Card, DatePicker, Col, Row, TreeSelect, Spin, message } from 'antd'; import styles from './index.less'; +import moment from 'moment'; +import marketAPI from "./service"; +import * as echarts from 'echarts'; const { RangePicker } = DatePicker; + /** * 会员报表 * @returns */ export default function Index() { + //日期选择下标:0:今天、1:昨天、2:近7天、3、近30天 + const [searchDayIndex, setSearchDayIndex] = useState(0); + //日期选择器 + const [timeRange, setTimeRange] = useState([ + moment(moment(new Date(Date.now())).format('YYYY-MM-DD 00:00:00')), + moment(moment(new Date(Date.now())).format('YYYY-MM-DD 23:59:59')) + ]); + const [orgTree, setOrgTree] = useState([]); + const [showLoading, setShowLoading] = useState(false); + const [currentOrg, setCurrentOrg] = useState(""); + + //会员图表 + const [memberReport, setMemberReport] = useState({ + userCount: 0, + newAddUser: [], + repurChase: [] + }); + + //新增会员图表实例 + let newMemberObj = null; + //复购图表实例 + let repurChaseObj = null; + + //切换时间 + const onChangeTime = (dayIndex) => { + setSearchDayIndex(dayIndex); + let tempDate = []; + switch (dayIndex) { + case 0: + tempDate = [ + moment(moment(new Date(Date.now())).format('YYYY-MM-DD 00:00:00')), + moment(moment(new Date(Date.now())).format('YYYY-MM-DD 23:59:59')) + ] + break; + case 1: + tempDate = [ + moment(moment(new Date(Date.now() - 24 * 60 * 60 * 1000)).format('YYYY-MM-DD 00:00:00')), + moment(moment(new Date(Date.now() - 24 * 60 * 60 * 1000)).format('YYYY-MM-DD 23:59:59')) + ] + break; + case 2: + tempDate = [ + moment(moment(new Date(Date.now() - 24 * 60 * 60 * 1000 * 7)).format('YYYY-MM-DD 00:00:00')), + moment(moment(new Date(Date.now())).format('YYYY-MM-DD 23:59:59')), + ] + break; + case 3: + tempDate = [ + moment(moment(new Date(Date.now() - 24 * 60 * 60 * 1000 * 30)).format('YYYY-MM-DD 00:00:00')), + moment(moment(new Date(Date.now())).format('YYYY-MM-DD 23:59:59')), + ] + break; + } + setTimeRange(tempDate); + } + + //获取组织树 + const onGetOrgTree = async () => { + setShowLoading(true); + const response = await marketAPI.getOrgTree(); + setShowLoading(false); + if (response.statusCode === 200) { + const originTree = response.data; + onSetOrgTreeStatus(originTree); + setOrgTree(originTree); + } else { + message.error(response.errors || '获取组织树出错'); + } + } + + //设置组织树不可选择状态 + const onSetOrgTreeStatus = (originTree) => { + originTree.forEach(treeItem => { + if (treeItem.children && treeItem.children.length > 0) { + onSetOrgTreeStatus(treeItem.children); + } else { + if (treeItem.type === 2 || treeItem.type === 3) { + treeItem.disabled = false; + } else { + treeItem.disabled = true; + } + } + }); + } + + const LoadingCard = () => { + return ( +
+ +
+ ) + } + + //初始化会员报表数据 + const initMemberReport = async () => { + const jsonData = { + "startTime": timeRange[0], + "endTime": timeRange[1] + } + setShowLoading(true); + const response = await marketAPI.getMemberReport(jsonData); + setShowLoading(false); + if (response.statusCode === 200) { + setMemberReport(response.data); + } else { + message.error(response.errors || '获取会员报表失败'); + } + } + + + //初始化新增会员图表 + const initNewMemberCharts = () => { + const chartDom = document.getElementById('new-member'); + if (!newMemberObj) { + newMemberObj = echarts.init(chartDom); + } + const xAxisData = []; + const seriesData = []; + memberReport.newAddUser.forEach(item => { + const date = new Date(item.userKey); + xAxisData.push(date.toLocaleString()); + seriesData.push(item.userValue); + }); + const option = { + title: { + text: '新增会员' + }, + xAxis: { + type: 'category', + data: xAxisData + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: seriesData, + type: 'bar' + } + ] + }; + newMemberObj.setOption(option); + } + + //复购次数图表 + const initRepurChaseCharts = () => { + const chartDom = document.getElementById('repur-chase'); + if (!repurChaseObj) { + repurChaseObj = echarts.init(chartDom); + } + const xAxisData = []; + const seriesData = []; + memberReport.repurChase.forEach(item => { + xAxisData.push(item.userKey); + seriesData.push(item.userValue); + }); + const option = { + title: { + text: '复购次数' + }, + xAxis: { + type: 'category', + data: xAxisData + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: seriesData, + type: 'line', + smooth: true + } + ] + }; + repurChaseObj.setOption(option); + } + + //重置 + const onResetSearch = () => { + setCurrentOrg(""); + } + + useEffect(() => { + onGetOrgTree(); + initMemberReport(); + }, []); + + useEffect(() => { + initNewMemberCharts(); + initRepurChaseCharts(); + window.onresize = () => { + if (newMemberObj && repurChaseObj) { + newMemberObj.resize(); + repurChaseObj.resize(); + } + } + }, [memberReport]); + return ( + {showLoading ? : null} -
- -
- - -
-
-
- - 会员报表 + + + { + let tempDate = [ + moment(moment(new Date(dateStrings[0])).format('YYYY-MM-DD 00:00:00')), + moment(moment(new Date(dateStrings[1])).format('YYYY-MM-DD 23:59:59')), + ] + setTimeRange(tempDate); + }} /> + + +
onChangeTime(0)}> + 今天 +
+
onChangeTime(1)}> + 昨天 +
+
onChangeTime(2)}> + 近7天 +
+
onChangeTime(3)}> + 近30天 +
+ + + { + if (node.type === 2 || node === 3) { + setCurrentOrg(node); + } else { + setCurrentOrg(""); + } + }} + placeholder="请选择组织架构" + treeDefaultExpandAll + /> + + +
+ + +
+ +
+ + + +
+
+ 会员总量 +
+
+ {memberReport.userCount} +
+
+
+
+ 新增会员 +
+
+ {memberReport.newAddUser.length} +
+
+
+
+ 复购数量 +
+
+ {memberReport.repurChase.length} +
+
+
+ + + + {/* 新增会员图表 */} +
+
+ +
+ + + + {/* 复购次数图表 */} +
+
+ +
) } diff --git a/src/pages/order/order-member-statement/index.less b/src/pages/order/order-member-statement/index.less index 74d0c92..5b5dbc1 100644 --- a/src/pages/order/order-member-statement/index.less +++ b/src/pages/order/order-member-statement/index.less @@ -2,15 +2,92 @@ margin-bottom: 20px; } -.data-search-box { +.data-search-row { + margin-bottom: 10px; +} + +.data-search-item { display: flex; align-items: center; - justify-content: space-between; + margin-bottom: 10px; +} + +.data-search-prefix { + margin-right: 10px; + font-size: 18px; } .search-btn-item { - margin-left: 20px; + margin-right: 20px; +} + +.data-search-day { + padding: 5px 20px; + border: 1px solid #dedede; + cursor: pointer; +} + +.data-search-day:nth-child(1), .data-search-day:nth-child(2), .data-search-day:nth-child(3) { + border-right: none; +} + +.search-day-selected { + border: 1px solid #FA541C !important; + color: #FA541C; +} + +.data-search-sufixx { + width: 100%; +} + +// 加载中 +.loading-card { + position: fixed; + display: flex; + align-items: center; + justify-content: center; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 999; + background-color: rgba(0, 0, 0, 0.5); +} + +.member-card-box { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; +} + +.member-card-prefix { + font-size: 20px; + font-weight: 700; + color: #999; + font-family: '楷体'; +} + +.member-card-sufixx { + font-size: 22px; +} + +.new-member { + width: 100%; + height: 250px; +} + +.repur-chase { + width: 100%; + height: 250px; +} + +.member-card { + height: 300px; } -.table-card { +.member-row-common { + margin-bottom: 10px; } \ No newline at end of file diff --git a/src/pages/order/order-member-statement/service.js b/src/pages/order/order-member-statement/service.js new file mode 100644 index 0000000..063d1c6 --- /dev/null +++ b/src/pages/order/order-member-statement/service.js @@ -0,0 +1,18 @@ +import { request } from 'umi'; + +export default { + //获取组织架构 + getOrgTree() { + return request(`/kitchen/api/report-statistics/org-tree`, { + method: 'GET', + }); + }, + + //获取会员报表 + getMemberReport(data) { + return request(`/kitchen/api/report-statistics/user-report`, { + method: 'POST', + data + }); + } +};