@@ -587,7 +587,7 @@ export default [ | |||||
access: 'k31', | access: 'k31', | ||||
}, | }, | ||||
{ | { | ||||
name: '营销报表', | |||||
name: '营收报表', | |||||
path: '/order/order-market-report', | path: '/order/order-market-report', | ||||
component: './order/order-market-report', | component: './order/order-market-report', | ||||
access: 'k31', | access: 'k31', | ||||
@@ -605,7 +605,7 @@ export default [ | |||||
access: 'k31', | access: 'k31', | ||||
}, | }, | ||||
{ | { | ||||
name: '营收报表', | |||||
name: '营销报表', | |||||
path: '/order/order-revenue-statement', | path: '/order/order-revenue-statement', | ||||
component: './order/order-revenue-statement', | component: './order/order-revenue-statement', | ||||
access: 'k31', | access: 'k31', | ||||
@@ -63,6 +63,7 @@ | |||||
"braft-editor": "^2.3.9", | "braft-editor": "^2.3.9", | ||||
"classnames": "^2.2.6", | "classnames": "^2.2.6", | ||||
"cos-js-sdk-v5": "^1.3.5", | "cos-js-sdk-v5": "^1.3.5", | ||||
"echarts": "^5.3.3", | |||||
"js-export-excel": "^1.1.4", | "js-export-excel": "^1.1.4", | ||||
"linq": "^4.0.0", | "linq": "^4.0.0", | ||||
"lodash": "^4.17.11", | "lodash": "^4.17.11", | ||||
@@ -571,7 +571,7 @@ export async function getInitialState() { | |||||
access: 'k32', | access: 'k32', | ||||
}, | }, | ||||
{ | { | ||||
name: '营销报表', | |||||
name: '营收报表', | |||||
path: '/order/order-market-report', | path: '/order/order-market-report', | ||||
component: './order/order-market-report', | component: './order/order-market-report', | ||||
access: 'k31', | access: 'k31', | ||||
@@ -583,13 +583,7 @@ export async function getInitialState() { | |||||
access: 'k31', | access: 'k31', | ||||
}, | }, | ||||
{ | { | ||||
name: '订单报表详情', | |||||
path: '/order/order-report/order-report-detail', | |||||
component: './order/order-report-detail', | |||||
access: 'k31', | |||||
}, | |||||
{ | |||||
name: '营收报表', | |||||
name: '营销报表', | |||||
path: '/order/order-revenue-statement', | path: '/order/order-revenue-statement', | ||||
component: './order/order-revenue-statement', | component: './order/order-revenue-statement', | ||||
access: 'k31', | access: 'k31', | ||||
@@ -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 ( | |||||
<div className={styles['loading-card']}> | |||||
<Spin size="large" /> | |||||
</div> | |||||
) | |||||
} | |||||
return ( | |||||
<div> | |||||
{props.showLoading ? <LoadingCard></LoadingCard> : null} | |||||
<Card className={styles['data-search-card']}> | |||||
<Row gutter={20} className={styles['data-search-row']}> | |||||
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}> | |||||
<RangePicker size='middle' className={styles['data-search-sufixx']} value={props.timeRange} onChange={(date, dateStrings) => { | |||||
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); | |||||
}} /> | |||||
</Col> | |||||
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}> | |||||
<div className={props.searchDayIndex === 0 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(0)}> | |||||
今天 | |||||
</div> | |||||
<div className={props.searchDayIndex === 1 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(1)}> | |||||
昨天 | |||||
</div> | |||||
<div className={props.searchDayIndex === 2 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(2)}> | |||||
近7天 | |||||
</div> | |||||
<div className={props.searchDayIndex === 3 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(3)}> | |||||
近30天 | |||||
</div> | |||||
</Col> | |||||
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}> | |||||
<TreeSelect | |||||
className={styles['data-search-sufixx']} | |||||
dropdownStyle={{ | |||||
maxHeight: 400, | |||||
overflow: 'auto', | |||||
}} | |||||
value={props.currentOrg.title} | |||||
treeData={props.orgTree} | |||||
onSelect={(value, node) => { | |||||
if (node.type === 2 || node === 3) { | |||||
props.onCurrentOrgChange(node); | |||||
} else { | |||||
props.onCurrentOrgChange(""); | |||||
} | |||||
}} | |||||
placeholder="请选择组织架构" | |||||
treeDefaultExpandAll | |||||
/> | |||||
</Col> | |||||
<Col xs={24} sm={24} md={12} lg={12} xl={6} className={styles['data-search-item']}> | |||||
<div className={styles['data-search-btns']}> | |||||
<Button className={styles['search-btn-item']} onClick={props.onResetSearch}>重置</Button> | |||||
<Button className={styles['search-btn-item']} type="primary" onClick={props.onQueryBtn}>查询</Button> | |||||
</div> | |||||
</Col> | |||||
</Row> | |||||
</Card> | |||||
</div> | |||||
) | |||||
} |
@@ -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; | |||||
} |
@@ -33,6 +33,7 @@ export default function Index() { | |||||
//门店 | //门店 | ||||
const [storeIdArray, setStoreIdArray] = useState(""); | const [storeIdArray, setStoreIdArray] = useState(""); | ||||
const [storeSelect, setStoreSelect] = useState([]); | const [storeSelect, setStoreSelect] = useState([]); | ||||
const [storeList, setStoreList] = useState([]); | |||||
//商品 | //商品 | ||||
const [goodsIdArray, setGoodsIdArray] = useState([]); | const [goodsIdArray, setGoodsIdArray] = useState([]); | ||||
const [goodsIdSelect, setGoodsIdSelect] = useState([]); | const [goodsIdSelect, setGoodsIdSelect] = useState([]); | ||||
@@ -52,9 +53,9 @@ export default function Index() { | |||||
dataIndex: 'storeId', | dataIndex: 'storeId', | ||||
key: 'storeId', | key: 'storeId', | ||||
render: (text) => { | render: (text) => { | ||||
const findGoods = storeSelect.find(item => item.id === text); | |||||
const findGoods = storeList.find(item => item.key === text); | |||||
if (findGoods) { | if (findGoods) { | ||||
return <span>{findGoods.name}</span> | |||||
return <span>{findGoods.title}</span> | |||||
} else { | } else { | ||||
return <span>暂无门店名称</span> | return <span>暂无门店名称</span> | ||||
} | } | ||||
@@ -124,10 +125,11 @@ export default function Index() { | |||||
*/ | */ | ||||
const onQueryStoreList = async () => { | const onQueryStoreList = async () => { | ||||
setShowLoading(true); | setShowLoading(true); | ||||
const response = await costSalesAPI.gettree({}); | |||||
const response = await costSalesAPI.gettree(); | |||||
setShowLoading(false); | setShowLoading(false); | ||||
if (response.statusCode === 200) { | if (response.statusCode === 200) { | ||||
setStoreSelect(response.data); | setStoreSelect(response.data); | ||||
setStoreList(treeArrayToFlat(response.data)); | |||||
} else { | } else { | ||||
message.error('查询店铺列表失败'); | message.error('查询店铺列表失败'); | ||||
} | } | ||||
@@ -136,7 +138,7 @@ export default function Index() { | |||||
//查询商品列表 | //查询商品列表 | ||||
const onQueryGoodsList = async () => { | const onQueryGoodsList = async () => { | ||||
setShowLoading(true); | setShowLoading(true); | ||||
const response = await costSalesAPI.goodsList({}); | |||||
const response = await costSalesAPI.goodsList(); | |||||
setShowLoading(false); | setShowLoading(false); | ||||
if (response.statusCode === 200) { | if (response.statusCode === 200) { | ||||
setGoodsIdSelect(response.data); | setGoodsIdSelect(response.data); | ||||
@@ -148,7 +150,7 @@ export default function Index() { | |||||
//查询商品分类类型列表 | //查询商品分类类型列表 | ||||
const onQueryGoodsType = async () => { | const onQueryGoodsType = async () => { | ||||
setShowLoading(true); | setShowLoading(true); | ||||
const response = await costSalesAPI.goodsTypeList({}); | |||||
const response = await costSalesAPI.goodsTypeList(); | |||||
setShowLoading(false); | setShowLoading(false); | ||||
if (response.statusCode === 200) { | if (response.statusCode === 200) { | ||||
setGoodsTypeSelect(response.data); | setGoodsTypeSelect(response.data); | ||||
@@ -168,6 +170,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(() => { | useEffect(() => { | ||||
onQueryReportSalescost(); | onQueryReportSalescost(); | ||||
onQueryStoreList(); | onQueryStoreList(); | ||||
@@ -35,6 +35,7 @@ export default function Index() { | |||||
//门店 | //门店 | ||||
const [storeIdArray, setStoreIdArray] = useState([]); | const [storeIdArray, setStoreIdArray] = useState([]); | ||||
const [storeSelect, setStoreSelect] = useState([]); | const [storeSelect, setStoreSelect] = useState([]); | ||||
const [storeList, setStoreList] = useState([]); | |||||
const columns = [ | const columns = [ | ||||
{ | { | ||||
@@ -42,13 +43,12 @@ export default function Index() { | |||||
dataIndex: 'storeId', | dataIndex: 'storeId', | ||||
key: 'storeId', | key: 'storeId', | ||||
render: (text) => { | render: (text) => { | ||||
const findGoods = storeSelect.find(item => item.id === text); | |||||
const findGoods = storeList.find(item => item.key === text); | |||||
if (findGoods) { | if (findGoods) { | ||||
return <span>{findGoods.name}</span> | |||||
return <span>{findGoods.title}</span> | |||||
} else { | } else { | ||||
return <span>暂无门店名称</span> | return <span>暂无门店名称</span> | ||||
} | } | ||||
}, | }, | ||||
}, | }, | ||||
{ | { | ||||
@@ -92,6 +92,7 @@ export default function Index() { | |||||
setShowLoading(false); | setShowLoading(false); | ||||
if (response.statusCode === 200) { | if (response.statusCode === 200) { | ||||
setStoreSelect(response.data); | setStoreSelect(response.data); | ||||
setStoreList(treeArrayToFlat(response.data)); | |||||
} else { | } else { | ||||
message.error('查询店铺列表失败'); | 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(() => { | useEffect(() => { | ||||
onQueryReportSalescost(); | onQueryReportSalescost(); | ||||
onQueryStoreList(); | onQueryStoreList(); | ||||
@@ -391,7 +391,7 @@ const LoadingCard = () => { | |||||
} | } | ||||
/** | /** | ||||
* 营销报表 | |||||
* 营收报表 | |||||
* @returns | * @returns | ||||
*/ | */ | ||||
export default function Index() { | export default function Index() { | ||||
@@ -1,28 +1,318 @@ | |||||
import React from 'react'; | |||||
import React, { useState, useEffect } from 'react'; | |||||
import { PageContainer } from '@ant-design/pro-layout'; | 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 styles from './index.less'; | import styles from './index.less'; | ||||
const { RangePicker } = DatePicker; | |||||
import moment from 'moment'; | |||||
import marketAPI from "./service"; | |||||
import * as echarts from 'echarts'; | |||||
/** | /** | ||||
* 会员报表 | * 会员报表 | ||||
* @returns | * @returns | ||||
*/ | */ | ||||
export default function Index() { | 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: [] | |||||
}); | |||||
//新增会员数 | |||||
const [newMemberNum, setNewMemberNum] = useState(0); | |||||
//复购次数 | |||||
const [repurChaseNum, setRepurChaseNum] = useState(0); | |||||
//新增会员图表实例 | |||||
let newMemberObj = null; | |||||
//复购图表实例 | |||||
let repurChaseObj = 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 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 initMemberReport = async () => { | |||||
const jsonData = { | |||||
"shopIds": [ | |||||
currentOrg.key | |||||
], | |||||
"startTime": timeRange[0], | |||||
"endTime": timeRange[1] | |||||
} | |||||
setShowLoading(true); | |||||
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 || '获取会员报表失败'); | |||||
} | |||||
} | |||||
//初始化新增会员图表 | |||||
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.toLocaleDateString()); | |||||
seriesData.push(item.userValue); | |||||
}); | |||||
const option = { | |||||
title: { | |||||
text: '新增会员' | |||||
}, | |||||
tooltip: { | |||||
trigger: 'axis' | |||||
}, | |||||
toolbox: { | |||||
feature: { | |||||
saveAsImage: {} | |||||
} | |||||
}, | |||||
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: '复购次数' | |||||
}, | |||||
tooltip: { | |||||
trigger: 'axis', | |||||
axisPointer: { | |||||
label: { | |||||
show: true, | |||||
formatter: '复购{value}次' | |||||
} | |||||
} | |||||
}, | |||||
toolbox: { | |||||
feature: { | |||||
saveAsImage: {} | |||||
} | |||||
}, | |||||
xAxis: { | |||||
type: 'category', | |||||
data: xAxisData | |||||
}, | |||||
yAxis: { | |||||
type: 'value' | |||||
}, | |||||
series: [ | |||||
{ | |||||
data: seriesData, | |||||
type: 'line', | |||||
smooth: true | |||||
} | |||||
] | |||||
}; | |||||
repurChaseObj.setOption(option); | |||||
} | |||||
//重置 | |||||
const onResetSearch = () => { | |||||
setCurrentOrg(""); | |||||
} | |||||
//子组件切换组织架构 | |||||
const onCurrentOrgChange = (curOrg) => { | |||||
setCurrentOrg(curOrg) | |||||
} | |||||
//子组件时间改变 | |||||
const onTimePickerChange = (nowTimeRange) => { | |||||
setTimeRange(nowTimeRange); | |||||
} | |||||
useEffect(() => { | |||||
onGetOrgTree(); | |||||
initMemberReport(); | |||||
}, []); | |||||
useEffect(() => { | |||||
initNewMemberCharts(); | |||||
initRepurChaseCharts(); | |||||
window.onresize = () => { | |||||
if (newMemberObj && repurChaseObj) { | |||||
newMemberObj.resize(); | |||||
repurChaseObj.resize(); | |||||
} | |||||
} | |||||
}, [memberReport]); | |||||
return ( | return ( | ||||
<PageContainer> | <PageContainer> | ||||
<Card className={styles['data-search-card']}> | |||||
<div className={styles['data-search-box']}> | |||||
<RangePicker /> | |||||
<div className={styles['data-search-btns']}> | |||||
<Button className={styles['search-btn-item']}>重置</Button> | |||||
<Button className={styles['search-btn-item']} type="primary">查询</Button> | |||||
</div> | |||||
</div> | |||||
</Card> | |||||
<Card className={styles['table-card']}> | |||||
会员报表 | |||||
</Card> | |||||
<ConditionQuery | |||||
orgTree={orgTree} | |||||
timeRange={timeRange} | |||||
searchDayIndex={searchDayIndex} | |||||
currentOrg={currentOrg} | |||||
showLoading={showLoading} | |||||
onTimePickerChange={onTimePickerChange} | |||||
onChangeTimeIndex={onChangeTimeIndex} | |||||
onCurrentOrgChange={onCurrentOrgChange} | |||||
onResetSearch={onResetSearch} | |||||
onQueryBtn={initMemberReport} | |||||
> | |||||
</ConditionQuery> | |||||
<Row gutter={10} className={styles['member-row-common']}> | |||||
<Col xs={24} sm={24} md={24} lg={8} xl={6}> | |||||
<Card className={styles['member-card']}> | |||||
<div className={styles['member-card-box']}> | |||||
<div className={styles['member-card-prefix']}> | |||||
会员总量 | |||||
</div> | |||||
<div className={styles['member-card-sufixx']}> | |||||
{memberReport.userCount} | |||||
</div> | |||||
</div> | |||||
<div className={styles['member-card-box']}> | |||||
<div className={styles['member-card-prefix']}> | |||||
新增会员 | |||||
</div> | |||||
<div className={styles['member-card-sufixx']}> | |||||
{newMemberNum} | |||||
</div> | |||||
</div> | |||||
<div className={styles['member-card-box']}> | |||||
<div className={styles['member-card-prefix']}> | |||||
复购数量 | |||||
</div> | |||||
<div className={styles['member-card-sufixx']}> | |||||
{repurChaseNum} | |||||
</div> | |||||
</div> | |||||
</Card> | |||||
</Col> | |||||
<Col xs={24} sm={24} md={24} lg={16} xl={18}> | |||||
<Card> | |||||
{/* 复购次数图表 */} | |||||
<div id="repur-chase" className={styles['repur-chase']}></div> | |||||
</Card> | |||||
</Col> | |||||
</Row> | |||||
<Row> | |||||
<Col xs={24} sm={24} md={24} lg={24} xl={24} > | |||||
<Card> | |||||
{/* 新增会员图表 */} | |||||
<div id="new-member" className={styles['new-member']}></div> | |||||
</Card> | |||||
</Col> | |||||
</Row> | |||||
</PageContainer> | </PageContainer> | ||||
) | ) | ||||
} | } |
@@ -2,15 +2,92 @@ | |||||
margin-bottom: 20px; | margin-bottom: 20px; | ||||
} | } | ||||
.data-search-box { | |||||
.data-search-row { | |||||
margin-bottom: 10px; | |||||
} | |||||
.data-search-item { | |||||
display: flex; | display: flex; | ||||
align-items: center; | align-items: center; | ||||
justify-content: space-between; | |||||
margin-bottom: 10px; | |||||
} | |||||
.data-search-prefix { | |||||
margin-right: 10px; | |||||
font-size: 18px; | |||||
} | } | ||||
.search-btn-item { | .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; | |||||
} | } |
@@ -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 | |||||
}); | |||||
} | |||||
}; |
@@ -1,28 +1,269 @@ | |||||
import React from 'react'; | |||||
import React, { useState, useEffect } from 'react'; | |||||
import { PageContainer } from '@ant-design/pro-layout'; | 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'; | import styles from './index.less'; | ||||
const { RangePicker } = DatePicker; | |||||
import productAPI from "./service"; | |||||
/** | /** | ||||
* 产品报表 | * 产品报表 | ||||
* @returns | * @returns | ||||
*/ | */ | ||||
export default function Index() { | 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: '热销商品' | |||||
} | |||||
} | |||||
}, | |||||
toolbox: { | |||||
feature: { | |||||
saveAsImage: {} | |||||
} | |||||
}, | |||||
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: '热销商品' | |||||
}, | |||||
toolbox: { | |||||
feature: { | |||||
saveAsImage: {} | |||||
} | |||||
}, | |||||
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 ( | return ( | ||||
<PageContainer> | <PageContainer> | ||||
<Card className={styles['data-search-card']}> | |||||
<div className={styles['data-search-box']}> | |||||
<RangePicker /> | |||||
<div className={styles['data-search-btns']}> | |||||
<Button className={styles['search-btn-item']}>重置</Button> | |||||
<Button className={styles['search-btn-item']} type="primary">查询</Button> | |||||
</div> | |||||
</div> | |||||
</Card> | |||||
<Card className={styles['table-card']}> | |||||
产品报表 | |||||
</Card> | |||||
<ConditionQuery | |||||
orgTree={orgTree} | |||||
timeRange={timeRange} | |||||
searchDayIndex={searchDayIndex} | |||||
currentOrg={currentOrg} | |||||
showLoading={showLoading} | |||||
onTimePickerChange={onTimePickerChange} | |||||
onChangeTimeIndex={onChangeTimeIndex} | |||||
onCurrentOrgChange={onCurrentOrgChange} | |||||
onResetSearch={onResetSearch} | |||||
onQueryBtn={onGetTopGoods} | |||||
> | |||||
</ConditionQuery> | |||||
<Row gutter={10}> | |||||
<Col xs={24} sm={24} md={24} lg={8} xl={8}> | |||||
<Card> | |||||
<div id="top-goods" className={styles['top-goods']}></div> | |||||
</Card> | |||||
</Col> | |||||
<Col xs={24} sm={24} md={24} lg={16} xl={16}> | |||||
<Card> | |||||
<div id="top-goods-line" className={styles['top-goods-line']}></div> | |||||
</Card> | |||||
</Col> | |||||
</Row> | |||||
</PageContainer> | </PageContainer> | ||||
) | ) | ||||
} | } |
@@ -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 { | |||||
} |
@@ -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 | |||||
}); | |||||
} | |||||
}; |
@@ -1,28 +1,285 @@ | |||||
import React from 'react'; | |||||
import React, { useState, useEffect } from 'react'; | |||||
import { PageContainer } from '@ant-design/pro-layout'; | 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'; | import styles from './index.less'; | ||||
const { RangePicker } = DatePicker; | |||||
import reportAPI from "./service"; | |||||
/** | /** | ||||
* 营收报表 | |||||
* 营销报表 | |||||
* @returns | * @returns | ||||
*/ | */ | ||||
export default function Index() { | 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 ( | return ( | ||||
<PageContainer> | <PageContainer> | ||||
<Card className={styles['data-search-card']}> | |||||
<div className={styles['data-search-box']}> | |||||
<RangePicker /> | |||||
<div className={styles['data-search-btns']}> | |||||
<Button className={styles['search-btn-item']}>重置</Button> | |||||
<Button className={styles['search-btn-item']} type="primary">查询</Button> | |||||
</div> | |||||
</div> | |||||
</Card> | |||||
<Card className={styles['table-card']}> | |||||
营收报表 | |||||
</Card> | |||||
<ConditionQuery | |||||
orgTree={orgTree} | |||||
timeRange={timeRange} | |||||
searchDayIndex={searchDayIndex} | |||||
currentOrg={currentOrg} | |||||
showLoading={showLoading} | |||||
onTimePickerChange={onTimePickerChange} | |||||
onChangeTimeIndex={onChangeTimeIndex} | |||||
onCurrentOrgChange={onCurrentOrgChange} | |||||
onResetSearch={onResetSearch} | |||||
onQueryBtn={onQueryMarketing} | |||||
> | |||||
</ConditionQuery> | |||||
<Row gutter={10}> | |||||
<Col xs={24} sm={24} md={24} lg={8} xl={8}> | |||||
<Card> | |||||
<div id="activity-echarts" className={styles['activity-echarts']}></div> | |||||
</Card> | |||||
</Col> | |||||
<Col xs={24} sm={24} md={24} lg={16} xl={16}> | |||||
<Card> | |||||
<div id="coupon-ehcarts" className={styles['coupon-ehcarts']}></div> | |||||
</Card> | |||||
</Col> | |||||
</Row> | |||||
</PageContainer> | </PageContainer> | ||||
) | ) | ||||
} | } |
@@ -12,5 +12,7 @@ | |||||
margin-left: 20px; | margin-left: 20px; | ||||
} | } | ||||
.table-card { | |||||
.activity-echarts, .coupon-ehcarts { | |||||
width: 100%; | |||||
height: 300px; | |||||
} | } |
@@ -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', | |||||
}); | |||||
} | |||||
}; |
@@ -33,6 +33,7 @@ export default function Index() { | |||||
//门店 | //门店 | ||||
const [storeIdArray, setStoreIdArray] = useState(""); | const [storeIdArray, setStoreIdArray] = useState(""); | ||||
const [storeSelect, setStoreSelect] = useState([]); | const [storeSelect, setStoreSelect] = useState([]); | ||||
const [storeList, setStoreList] = useState([]); | |||||
//商品 | //商品 | ||||
const [goodsIdArray, setGoodsIdArray] = useState([]); | const [goodsIdArray, setGoodsIdArray] = useState([]); | ||||
const [goodsIdSelect, setGoodsIdSelect] = useState([]); | const [goodsIdSelect, setGoodsIdSelect] = useState([]); | ||||
@@ -46,13 +47,12 @@ export default function Index() { | |||||
dataIndex: 'storeId', | dataIndex: 'storeId', | ||||
key: 'storeId', | key: 'storeId', | ||||
render: (text) => { | render: (text) => { | ||||
const findGoods = storeSelect.find(item => item.id === text); | |||||
const findGoods = storeList.find(item => item.key === text); | |||||
if (findGoods) { | if (findGoods) { | ||||
return <span>{findGoods.name}</span> | |||||
return <span>{findGoods.title}</span> | |||||
} else { | } else { | ||||
return <span>暂无门店名称</span> | return <span>暂无门店名称</span> | ||||
} | } | ||||
}, | }, | ||||
}, | }, | ||||
{ | { | ||||
@@ -112,6 +112,7 @@ export default function Index() { | |||||
setShowLoading(false); | setShowLoading(false); | ||||
if (response.statusCode === 200) { | if (response.statusCode === 200) { | ||||
setStoreSelect(response.data); | setStoreSelect(response.data); | ||||
setStoreList(treeArrayToFlat(response.data)); | |||||
} else { | } else { | ||||
message.error('查询店铺列表失败'); | 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(() => { | useEffect(() => { | ||||
onQueryReportSalescost(); | onQueryReportSalescost(); | ||||
onQueryStoreList(); | onQueryStoreList(); | ||||