From 5d0c3ebc783a0b2af3c4dff9629c275fe9a7e207 Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Tue, 5 Jul 2022 17:42:30 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BC=9A=E5=91=98?= =?UTF-8?q?=E5=9B=BE=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + .../order/order-member-statement/index.jsx | 313 +++++++++++++++++- .../order/order-member-statement/index.less | 85 ++++- .../order/order-member-statement/service.js | 18 + 4 files changed, 401 insertions(+), 16 deletions(-) create mode 100644 src/pages/order/order-member-statement/service.js 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 + }); + } +}; From 3e900b86f98b2de0081136e649aeb24b4f52de90 Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Tue, 5 Jul 2022 18:13:45 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=BE=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/order-member-statement/index.jsx | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/pages/order/order-member-statement/index.jsx b/src/pages/order/order-member-statement/index.jsx index a4ed26e..631a9cf 100644 --- a/src/pages/order/order-member-statement/index.jsx +++ b/src/pages/order/order-member-statement/index.jsx @@ -30,6 +30,10 @@ export default function Index() { newAddUser: [], repurChase: [] }); + //新增会员数 + const [newMemberNum, setNewMemberNum] = useState(0); + //复购次数 + const [repurChaseNum, setRepurChaseNum] = useState(0); //新增会员图表实例 let newMemberObj = null; @@ -116,6 +120,16 @@ export default function Index() { const response = await marketAPI.getMemberReport(jsonData); setShowLoading(false); if (response.statusCode === 200) { + let sumNewMember = 0; + let sumRepurChase = 0; + response.data.newAddUser.forEach(item => { + sumNewMember += item.userValue + }); + response.data.repurChase.forEach(item => { + sumRepurChase += item.userValue + }); + setNewMemberNum(sumNewMember); + setRepurChaseNum(sumRepurChase); setMemberReport(response.data); } else { message.error(response.errors || '获取会员报表失败'); @@ -133,7 +147,7 @@ export default function Index() { const seriesData = []; memberReport.newAddUser.forEach(item => { const date = new Date(item.userKey); - xAxisData.push(date.toLocaleString()); + xAxisData.push(date.toLocaleDateString()); seriesData.push(item.userValue); }); const option = { @@ -173,6 +187,15 @@ export default function Index() { title: { text: '复购次数' }, + tooltip: { + trigger: 'axis', + axisPointer: { + label: { + show: true, + formatter: '复购{value}次' + } + } + }, xAxis: { type: 'category', data: xAxisData @@ -284,7 +307,7 @@ export default function Index() { 新增会员
- {memberReport.newAddUser.length} + {newMemberNum}
@@ -292,7 +315,7 @@ export default function Index() { 复购数量
- {memberReport.repurChase.length} + {repurChaseNum}
From 95746e5bb51e7ef30e3f122304db1835b45d1a92 Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Wed, 6 Jul 2022 09:36:47 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=9B=BE=E8=A1=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/order/order-member-statement/index.jsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pages/order/order-member-statement/index.jsx b/src/pages/order/order-member-statement/index.jsx index 631a9cf..dfe1e84 100644 --- a/src/pages/order/order-member-statement/index.jsx +++ b/src/pages/order/order-member-statement/index.jsx @@ -240,7 +240,7 @@ export default function Index() { {showLoading ? : null} - + { let tempDate = [ moment(moment(new Date(dateStrings[0])).format('YYYY-MM-DD 00:00:00')), @@ -249,7 +249,7 @@ export default function Index() { setTimeRange(tempDate); }} /> - +
onChangeTime(0)}> 今天
@@ -263,7 +263,7 @@ export default function Index() { 近30天 - + - {/* 新增会员图表 */} -
+ {/* 复购次数图表 */} +
+
- {/* 复购次数图表 */} -
+ {/* 新增会员图表 */} +
From 877034b863c7b755dec7bada5fa5451f70047bad Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Wed, 6 Jul 2022 11:54:00 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=BB=84=E4=BB=B6=E3=80=81=E4=BC=9A=E5=91=98?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ConditionQuery/index.jsx | 79 ++++++ src/components/ConditionQuery/index.less | 93 +++++++ .../order/order-member-statement/index.jsx | 94 ++----- .../order/order-product-report/index.jsx | 261 +++++++++++++++++- .../order/order-product-report/index.less | 18 +- .../order/order-product-report/service.js | 18 ++ 6 files changed, 468 insertions(+), 95 deletions(-) create mode 100644 src/components/ConditionQuery/index.jsx create mode 100644 src/components/ConditionQuery/index.less create mode 100644 src/pages/order/order-product-report/service.js diff --git a/src/components/ConditionQuery/index.jsx b/src/components/ConditionQuery/index.jsx new file mode 100644 index 0000000..7535263 --- /dev/null +++ b/src/components/ConditionQuery/index.jsx @@ -0,0 +1,79 @@ +import React, { useEffect } from 'react'; +import { Button, Card, DatePicker, Col, Row, TreeSelect, Spin } from 'antd'; +import styles from './index.less'; +import moment from 'moment'; +const { RangePicker } = DatePicker; + +/** + * 条件查询通用头部 + * @returns + */ +export default function Index(props) { + + const LoadingCard = () => { + return ( +
+ +
+ ) + } + + return ( +
+ {props.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')), + ] + props.onTimePickerChange(tempDate); + }} /> + + +
props.onChangeTimeIndex(0)}> + 今天 +
+
props.onChangeTimeIndex(1)}> + 昨天 +
+
props.onChangeTimeIndex(2)}> + 近7天 +
+
props.onChangeTimeIndex(3)}> + 近30天 +
+ + + { + if (node.type === 2 || node === 3) { + props.onCurrentOrgChange(node); + } else { + props.onCurrentOrgChange(""); + } + }} + placeholder="请选择组织架构" + treeDefaultExpandAll + /> + + +
+ + +
+ +
+
+
+ ) +} diff --git a/src/components/ConditionQuery/index.less b/src/components/ConditionQuery/index.less new file mode 100644 index 0000000..5b5dbc1 --- /dev/null +++ b/src/components/ConditionQuery/index.less @@ -0,0 +1,93 @@ +.data-search-card { + margin-bottom: 20px; +} + +.data-search-row { + margin-bottom: 10px; +} + +.data-search-item { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.data-search-prefix { + margin-right: 10px; + font-size: 18px; +} + +.search-btn-item { + 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; +} + +.member-row-common { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/src/pages/order/order-member-statement/index.jsx b/src/pages/order/order-member-statement/index.jsx index dfe1e84..38f1763 100644 --- a/src/pages/order/order-member-statement/index.jsx +++ b/src/pages/order/order-member-statement/index.jsx @@ -1,11 +1,11 @@ import React, { useState, useEffect } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; -import { Button, Card, DatePicker, Col, Row, TreeSelect, Spin, message } from 'antd'; +import { Card, Col, Row, message } from 'antd'; +import ConditionQuery from "../../../components/ConditionQuery"; import styles from './index.less'; import moment from 'moment'; import marketAPI from "./service"; import * as echarts from 'echarts'; -const { RangePicker } = DatePicker; /** * 会员报表 @@ -41,7 +41,7 @@ export default function Index() { let repurChaseObj = null; //切换时间 - const onChangeTime = (dayIndex) => { + const onChangeTimeIndex = (dayIndex) => { setSearchDayIndex(dayIndex); let tempDate = []; switch (dayIndex) { @@ -102,17 +102,12 @@ export default function Index() { }); } - const LoadingCard = () => { - return ( -
- -
- ) - } - //初始化会员报表数据 const initMemberReport = async () => { const jsonData = { + "shopIds": [ + currentOrg.key + ], "startTime": timeRange[0], "endTime": timeRange[1] } @@ -219,6 +214,16 @@ export default function Index() { setCurrentOrg(""); } + //子组件切换组织架构 + const onCurrentOrgChange = (curOrg) => { + setCurrentOrg(curOrg) + } + + //子组件时间改变 + const onTimePickerChange = (nowTimeRange) => { + setTimeRange(nowTimeRange); + } + useEffect(() => { onGetOrgTree(); initMemberReport(); @@ -237,60 +242,19 @@ export default function Index() { 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 - /> - - -
- - -
- -
-
+ + diff --git a/src/pages/order/order-product-report/index.jsx b/src/pages/order/order-product-report/index.jsx index 253aa3b..3fd206f 100644 --- a/src/pages/order/order-product-report/index.jsx +++ b/src/pages/order/order-product-report/index.jsx @@ -1,28 +1,259 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; -import { Button, Card, DatePicker } from 'antd'; +import { Card, Col, Row, message } from 'antd'; +import ConditionQuery from "../../../components/ConditionQuery"; +import * as echarts from 'echarts'; +import moment from 'moment'; import styles from './index.less'; -const { RangePicker } = DatePicker; +import productAPI from "./service"; /** * 产品报表 * @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(""); + //产品Top + const [topGoods, setTopGoods] = useState([]); + + //热销商品折线图实例 + let topGoodsLine = null; + //热销商品柱状图实例 + let topGoodsBar = null; + + //切换时间 + const onChangeTimeIndex = (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 productAPI.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 onResetSearch = () => { + setCurrentOrg(""); + } + + //子组件切换组织架构 + const onCurrentOrgChange = (curOrg) => { + setCurrentOrg(curOrg) + } + + //子组件时间改变 + const onTimePickerChange = (nowTimeRange) => { + setTimeRange(nowTimeRange); + } + + //获取热销产品数据 + const onGetTopGoods = async () => { + const jsonData = { + "top": 10, + "shopIds": [ + currentOrg.key + ], + "startTime": timeRange[0], + "endTime": timeRange[1] + } + setShowLoading(true); + const response = await productAPI.getProductEcharts(jsonData); + setShowLoading(false); + if (response.statusCode === 200) { + setTopGoods(response.data.topGoods); + } else { + message.error(response.errors || '获取热销产品数据出错') + } + } + + //初始化热销商品柱状图 + const initHotTopGoods = () => { + const chartDom = document.getElementById('top-goods'); + topGoodsBar = echarts.init(chartDom); + const xData = []; + const seriesData = []; + topGoods.forEach(item => { + xData.push(item.name); + seriesData.push(item.count); + }); + const option = { + title: { + show: true, + text: '热销商品' + }, + tooltip: { + trigger: 'axis', + axisPointer: { + label: { + show: true, + formatter: '热销商品' + } + } + }, + xAxis: { + type: 'category', + data: xData + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: seriesData, + type: 'bar', + showBackground: true, + backgroundStyle: { + color: 'rgba(180, 180, 180, 0.2)' + } + } + ] + }; + option && topGoodsBar.setOption(option); + } + + //初始化热销商品折线图 + const initHotTopLine = () => { + const chartDom = document.getElementById('top-goods-line'); + topGoodsLine = echarts.init(chartDom); + const option = { + title: { + show: true, + text: '热销商品' + }, + tooltip: { + trigger: 'axis', + axisPointer: { + label: { + show: true, + formatter: '热销商品' + } + } + }, + xAxis: { + type: 'category', + data: ['00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00'] + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: [150, 230, 224, 218, 135, 147, 260], + type: 'line' + } + ] + }; + option && topGoodsLine.setOption(option); + } + + useEffect(() => { + onGetOrgTree(); + onGetTopGoods(); + }, []); + + useEffect(() => { + initHotTopGoods(); + initHotTopLine(); + window.onresize = () => { + if (topGoodsLine && topGoodsBar) { + topGoodsLine.resize(); + topGoodsBar.resize(); + } + } + }, [topGoods]); + + return ( - -
- -
- - -
-
-
- - 产品报表 - + + + + + +
+
+ + + +
+
+ +
) } diff --git a/src/pages/order/order-product-report/index.less b/src/pages/order/order-product-report/index.less index 74d0c92..37f49a0 100644 --- a/src/pages/order/order-product-report/index.less +++ b/src/pages/order/order-product-report/index.less @@ -1,16 +1,4 @@ -.data-search-card { - margin-bottom: 20px; +.top-goods , .top-goods-line{ + width: 100%; + height: 300px; } - -.data-search-box { - display: flex; - align-items: center; - justify-content: space-between; -} - -.search-btn-item { - margin-left: 20px; -} - -.table-card { -} \ No newline at end of file diff --git a/src/pages/order/order-product-report/service.js b/src/pages/order/order-product-report/service.js new file mode 100644 index 0000000..33db2fd --- /dev/null +++ b/src/pages/order/order-product-report/service.js @@ -0,0 +1,18 @@ +import { request } from 'umi'; + +export default { + //获取组织架构 + getOrgTree() { + return request(`/kitchen/api/report-statistics/org-tree`, { + method: 'GET', + }); + }, + + //获取产品报表 + getProductEcharts(data) { + return request(`/kitchen/api/report-statistics/productc`, { + method: 'POST', + data + }); + } +}; From 29585760b56476e38ff739d3d71020746c03b6a8 Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Wed, 6 Jul 2022 13:34:33 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=8F=96=E6=B6=88=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E8=AF=A6=E6=83=85=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 8ca8868..656335a 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -582,12 +582,6 @@ export async function getInitialState() { component: './order/order-report', access: 'k31', }, - { - name: '订单报表详情', - path: '/order/order-report/order-report-detail', - component: './order/order-report-detail', - access: 'k31', - }, { name: '营收报表', path: '/order/order-revenue-statement', From b5e61e63e08c0d41212e2b06a57a6e4f30fb7575 Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Wed, 6 Jul 2022 14:14:18 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E9=97=A8=E5=BA=97ID=E8=BD=AC=E5=90=8D?= =?UTF-8?q?=E5=AD=97=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/order/cost-of-sales/index.jsx | 24 +++++++++++++++---- .../order/gross-profit-store-sales/index.jsx | 19 ++++++++++++--- src/pages/order/sales-gross-profit/index.jsx | 19 ++++++++++++--- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/pages/order/cost-of-sales/index.jsx b/src/pages/order/cost-of-sales/index.jsx index bcfce3c..e7ba857 100644 --- a/src/pages/order/cost-of-sales/index.jsx +++ b/src/pages/order/cost-of-sales/index.jsx @@ -33,6 +33,7 @@ export default function Index() { //门店 const [storeIdArray, setStoreIdArray] = useState(""); const [storeSelect, setStoreSelect] = useState([]); + const [storeList, setStoreList] = useState([]); //商品 const [goodsIdArray, setGoodsIdArray] = useState([]); const [goodsIdSelect, setGoodsIdSelect] = useState([]); @@ -52,9 +53,9 @@ export default function Index() { dataIndex: 'storeId', key: 'storeId', render: (text) => { - const findGoods = storeSelect.find(item => item.id === text); + const findGoods = storeList.find(item => item.key === text); if (findGoods) { - return {findGoods.name} + return {findGoods.title} } else { return 暂无门店名称 } @@ -123,10 +124,11 @@ export default function Index() { */ const onQueryStoreList = async () => { setShowLoading(true); - const response = await costSalesAPI.gettree({}); + const response = await costSalesAPI.gettree(); setShowLoading(false); if (response.statusCode === 200) { setStoreSelect(response.data); + setStoreList(treeArrayToFlat(response.data)); } else { message.error('查询店铺列表失败'); } @@ -135,7 +137,7 @@ export default function Index() { //查询商品列表 const onQueryGoodsList = async () => { setShowLoading(true); - const response = await costSalesAPI.goodsList({}); + const response = await costSalesAPI.goodsList(); setShowLoading(false); if (response.statusCode === 200) { setGoodsIdSelect(response.data); @@ -147,7 +149,7 @@ export default function Index() { //查询商品分类类型列表 const onQueryGoodsType = async () => { setShowLoading(true); - const response = await costSalesAPI.goodsTypeList({}); + const response = await costSalesAPI.goodsTypeList(); setShowLoading(false); if (response.statusCode === 200) { setGoodsTypeSelect(response.data); @@ -167,6 +169,18 @@ export default function Index() { ]); } + //树形数据扁平化 + const treeArrayToFlat = (tree, arr = []) => { + tree.forEach(item => { + const { children, ...props } = item; + arr.push(props); + if (children && children.length > 0) { + treeArrayToFlat(children, arr); + } + }); + return arr; + } + useEffect(() => { onQueryReportSalescost(); onQueryStoreList(); diff --git a/src/pages/order/gross-profit-store-sales/index.jsx b/src/pages/order/gross-profit-store-sales/index.jsx index 2797e12..7ced9be 100644 --- a/src/pages/order/gross-profit-store-sales/index.jsx +++ b/src/pages/order/gross-profit-store-sales/index.jsx @@ -35,6 +35,7 @@ export default function Index() { //门店 const [storeIdArray, setStoreIdArray] = useState([]); const [storeSelect, setStoreSelect] = useState([]); + const [storeList, setStoreList] = useState([]); const columns = [ { @@ -42,13 +43,12 @@ export default function Index() { dataIndex: 'storeId', key: 'storeId', render: (text) => { - const findGoods = storeSelect.find(item => item.id === text); + const findGoods = storeList.find(item => item.key === text); if (findGoods) { - return {findGoods.name} + return {findGoods.title} } else { return 暂无门店名称 } - }, }, { @@ -92,6 +92,7 @@ export default function Index() { setShowLoading(false); if (response.statusCode === 200) { setStoreSelect(response.data); + setStoreList(treeArrayToFlat(response.data)); } else { message.error('查询店铺列表失败'); } @@ -106,6 +107,18 @@ export default function Index() { ]); } + //树形数据扁平化 + const treeArrayToFlat = (tree, arr = []) => { + tree.forEach(item => { + const { children, ...props } = item; + arr.push(props); + if (children && children.length > 0) { + treeArrayToFlat(children, arr); + } + }); + return arr; + } + useEffect(() => { onQueryReportSalescost(); onQueryStoreList(); diff --git a/src/pages/order/sales-gross-profit/index.jsx b/src/pages/order/sales-gross-profit/index.jsx index 4eb682b..42c9305 100644 --- a/src/pages/order/sales-gross-profit/index.jsx +++ b/src/pages/order/sales-gross-profit/index.jsx @@ -33,6 +33,7 @@ export default function Index() { //门店 const [storeIdArray, setStoreIdArray] = useState(""); const [storeSelect, setStoreSelect] = useState([]); + const [storeList, setStoreList] = useState([]); //商品 const [goodsIdArray, setGoodsIdArray] = useState([]); const [goodsIdSelect, setGoodsIdSelect] = useState([]); @@ -46,13 +47,12 @@ export default function Index() { dataIndex: 'storeId', key: 'storeId', render: (text) => { - const findGoods = storeSelect.find(item => item.id === text); + const findGoods = storeList.find(item => item.key === text); if (findGoods) { - return {findGoods.name} + return {findGoods.title} } else { return 暂无门店名称 } - }, }, { @@ -112,6 +112,7 @@ export default function Index() { setShowLoading(false); if (response.statusCode === 200) { setStoreSelect(response.data); + setStoreList(treeArrayToFlat(response.data)); } else { message.error('查询店铺列表失败'); } @@ -152,6 +153,18 @@ export default function Index() { ]); } + //树形数据扁平化 + const treeArrayToFlat = (tree, arr = []) => { + tree.forEach(item => { + const { children, ...props } = item; + arr.push(props); + if (children && children.length > 0) { + treeArrayToFlat(children, arr); + } + }); + return arr; + } + useEffect(() => { onQueryReportSalescost(); onQueryStoreList(); From 0e56c696152c4d5891490793b0ac5291e7e8d1cc Mon Sep 17 00:00:00 2001 From: yangwenhua <1289978696@qq.com> Date: Thu, 7 Jul 2022 10:35:19 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=90=A5=E9=94=80?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E3=80=81=E4=BC=98=E5=8C=96=E4=BC=9A=E5=91=98?= =?UTF-8?q?=E3=80=81=E4=BA=A7=E5=93=81=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.js | 4 +- src/app.jsx | 4 +- src/pages/order/order-market-report/index.jsx | 2 +- .../order/order-member-statement/index.jsx | 13 + .../order/order-product-report/index.jsx | 10 + .../order/order-revenue-statement/index.jsx | 289 +++++++++++++++++- .../order/order-revenue-statement/index.less | 4 +- .../order/order-revenue-statement/service.js | 17 ++ 8 files changed, 321 insertions(+), 22 deletions(-) create mode 100644 src/pages/order/order-revenue-statement/service.js diff --git a/config/routes.js b/config/routes.js index a5c011c..64dac7e 100644 --- a/config/routes.js +++ b/config/routes.js @@ -587,7 +587,7 @@ export default [ access: 'k31', }, { - name: '营销报表', + name: '营收报表', path: '/order/order-market-report', component: './order/order-market-report', access: 'k31', @@ -605,7 +605,7 @@ export default [ access: 'k31', }, { - name: '营收报表', + name: '营销报表', path: '/order/order-revenue-statement', component: './order/order-revenue-statement', access: 'k31', diff --git a/src/app.jsx b/src/app.jsx index 656335a..5855dc4 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -571,7 +571,7 @@ export async function getInitialState() { access: 'k32', }, { - name: '营销报表', + name: '营收报表', path: '/order/order-market-report', component: './order/order-market-report', access: 'k31', @@ -583,7 +583,7 @@ export async function getInitialState() { access: 'k31', }, { - name: '营收报表', + name: '营销报表', path: '/order/order-revenue-statement', component: './order/order-revenue-statement', access: 'k31', diff --git a/src/pages/order/order-market-report/index.jsx b/src/pages/order/order-market-report/index.jsx index b9403b5..ea52c2a 100644 --- a/src/pages/order/order-market-report/index.jsx +++ b/src/pages/order/order-market-report/index.jsx @@ -391,7 +391,7 @@ const LoadingCard = () => { } /** - * 营销报表 + * 营收报表 * @returns */ export default function Index() { diff --git a/src/pages/order/order-member-statement/index.jsx b/src/pages/order/order-member-statement/index.jsx index 38f1763..c0ca33d 100644 --- a/src/pages/order/order-member-statement/index.jsx +++ b/src/pages/order/order-member-statement/index.jsx @@ -149,6 +149,14 @@ export default function Index() { title: { text: '新增会员' }, + tooltip: { + trigger: 'axis' + }, + toolbox: { + feature: { + saveAsImage: {} + } + }, xAxis: { type: 'category', data: xAxisData @@ -191,6 +199,11 @@ export default function Index() { } } }, + toolbox: { + feature: { + saveAsImage: {} + } + }, xAxis: { type: 'category', data: xAxisData diff --git a/src/pages/order/order-product-report/index.jsx b/src/pages/order/order-product-report/index.jsx index 3fd206f..17a3110 100644 --- a/src/pages/order/order-product-report/index.jsx +++ b/src/pages/order/order-product-report/index.jsx @@ -154,6 +154,11 @@ export default function Index() { } } }, + toolbox: { + feature: { + saveAsImage: {} + } + }, xAxis: { type: 'category', data: xData @@ -184,6 +189,11 @@ export default function Index() { show: true, text: '热销商品' }, + toolbox: { + feature: { + saveAsImage: {} + } + }, tooltip: { trigger: 'axis', axisPointer: { diff --git a/src/pages/order/order-revenue-statement/index.jsx b/src/pages/order/order-revenue-statement/index.jsx index c2e0ebd..1d325ba 100644 --- a/src/pages/order/order-revenue-statement/index.jsx +++ b/src/pages/order/order-revenue-statement/index.jsx @@ -1,28 +1,285 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; -import { Button, Card, DatePicker } from 'antd'; +import { Card, Col, Row, message } from 'antd'; +import ConditionQuery from "../../../components/ConditionQuery"; +import * as echarts from 'echarts'; +import moment from 'moment'; import styles from './index.less'; -const { RangePicker } = DatePicker; +import reportAPI from "./service"; /** - * 营收报表 + * 营销报表 * @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 [reportActivity, setReportActivity] = useState([]); + //营销优惠券 + const [reportCoupon, setReportCoupon] = useState([]); + + //活动报表图表实例 + let activityEcharts = null; + //优惠券报表图表实例 + let couponEcharts = null; + + //切换时间 + const onChangeTimeIndex = (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 reportAPI.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 onResetSearch = () => { + setCurrentOrg(""); + } + + //子组件切换组织架构 + const onCurrentOrgChange = (curOrg) => { + setCurrentOrg(curOrg) + } + + //子组件时间改变 + const onTimePickerChange = (nowTimeRange) => { + setTimeRange(nowTimeRange); + } + + //获取营销报表列表 + const onQueryMarketing = async () => { + setShowLoading(true); + const response = await reportAPI.getReportStatisticsMarketing(); + setShowLoading(false); + if (response.statusCode === 200) { + setReportActivity(response.data.activity); + setReportCoupon(response.data.coupon); + } else { + message.error(response.errors || '获取营销报表列表出错'); + } + } + + //生成营销报表活动信息柱状图 + const initActivityEcharts = () => { + const chartDom = document.getElementById('activity-echarts'); + activityEcharts = echarts.init(chartDom); + const xAxisData = []; + const seriesData = []; + reportActivity.forEach(item => { + xAxisData.push(item.activityName); + seriesData.push(item.numberParticipants); + }); + const option = { + title: { + text: '活动报表' + }, + tooltip: { + trigger: 'axis' + }, + toolbox: { + feature: { + saveAsImage: {} + } + }, + xAxis: { + type: 'category', + data: xAxisData + }, + yAxis: { + type: 'value' + }, + series: [ + { + data: seriesData, + type: 'bar', + showBackground: true, + backgroundStyle: { + color: 'rgba(180, 180, 180, 0.2)' + } + } + ] + }; + + option && activityEcharts.setOption(option); + } + + //生成优惠券报表折线图 + const initCouponEcharts = () => { + const chartDom = document.getElementById('coupon-ehcarts'); + couponEcharts = echarts.init(chartDom); + const xAxisData = []; + const seriesPreferentialAmount = []; + const seriesSend = []; + const seriesGet = []; + reportCoupon.forEach(item => { + xAxisData.push(item.couponName); + seriesPreferentialAmount.push(item.preferentialAmount); + seriesSend.push(item.send); + seriesGet.push(item.get); + }); + const option = { + title: { + text: '优惠券报表' + }, + tooltip: { + trigger: 'axis' + }, + legend: { + data: ['优惠金额', '发放数量', '领取数量'] + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true + }, + toolbox: { + feature: { + saveAsImage: {} + } + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xAxisData + }, + yAxis: { + type: 'value' + }, + series: [ + { + name: '优惠金额', + type: 'line', + stack: 'Total', + data: seriesPreferentialAmount + }, + { + name: '发放数量', + type: 'line', + stack: 'Total', + data: seriesSend + }, + { + name: '领取数量', + type: 'line', + stack: 'Total', + data: seriesGet + } + ] + }; + option && couponEcharts.setOption(option); + } + + useEffect(() => { + onGetOrgTree(); + onQueryMarketing(); + }, []); + + useEffect(() => { + initActivityEcharts(); + initCouponEcharts(); + window.onresize = () => { + if (activityEcharts && couponEcharts) { + activityEcharts.resize(); + couponEcharts.resize(); + } + } + }, [reportActivity, reportCoupon]); + + return ( - -
- -
- - -
-
-
- - 营收报表 - + + + + + +
+
+ + + +
+
+ +
) } diff --git a/src/pages/order/order-revenue-statement/index.less b/src/pages/order/order-revenue-statement/index.less index 74d0c92..1fcef07 100644 --- a/src/pages/order/order-revenue-statement/index.less +++ b/src/pages/order/order-revenue-statement/index.less @@ -12,5 +12,7 @@ margin-left: 20px; } -.table-card { +.activity-echarts, .coupon-ehcarts { + width: 100%; + height: 300px; } \ No newline at end of file diff --git a/src/pages/order/order-revenue-statement/service.js b/src/pages/order/order-revenue-statement/service.js new file mode 100644 index 0000000..a079fa0 --- /dev/null +++ b/src/pages/order/order-revenue-statement/service.js @@ -0,0 +1,17 @@ +import { request } from 'umi'; + +export default { + //获取组织架构 + getOrgTree() { + return request(`/kitchen/api/report-statistics/org-tree`, { + method: 'GET', + }); + }, + + //获取营销报表 + getReportStatisticsMarketing() { + return request(`/kitchen/api/report-statistics/marketing`, { + method: 'POST', + }); + } +};