@@ -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; | |||
} |
@@ -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 ( | |||
<div className={styles['loading-card']}> | |||
<Spin size="large" /> | |||
</div> | |||
) | |||
} | |||
//初始化会员报表数据 | |||
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 ( | |||
<PageContainer> | |||
{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={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')), | |||
] | |||
setTimeRange(tempDate); | |||
}} /> | |||
</Col> | |||
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}> | |||
<div className={searchDayIndex === 0 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => onChangeTime(0)}> | |||
今天 | |||
</div> | |||
<div className={searchDayIndex === 1 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => onChangeTime(1)}> | |||
昨天 | |||
</div> | |||
<div className={searchDayIndex === 2 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => onChangeTime(2)}> | |||
近7天 | |||
</div> | |||
<div className={searchDayIndex === 3 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => onChangeTime(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={currentOrg.title} | |||
treeData={orgTree} | |||
onSelect={(value, node) => { | |||
if (node.type === 2 || node === 3) { | |||
setCurrentOrg(node); | |||
} else { | |||
setCurrentOrg(""); | |||
} | |||
}} | |||
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={onResetSearch}>重置</Button> | |||
<Button className={styles['search-btn-item']} type="primary" onClick={initMemberReport}>查询</Button> | |||
</div> | |||
</Col> | |||
</Row> | |||
</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']}> | |||
@@ -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 ( | |||
<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> | |||
) | |||
} |
@@ -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 | |||
}); | |||
} | |||
}; |