소스 검색

Merge branch 'dev' of http://10.2.1.24:10244/bpa/BPA.KitChen.Web into dev

tags/小炒逻辑变更前
txb 2 년 전
부모
커밋
339ad493ed
18개의 변경된 파일1203개의 추가작업 그리고 88개의 파일을 삭제
  1. +2
    -2
      config/routes.js
  2. +1
    -0
      package.json
  3. +2
    -8
      src/app.jsx
  4. +79
    -0
      src/components/ConditionQuery/index.jsx
  5. +93
    -0
      src/components/ConditionQuery/index.less
  6. +19
    -5
      src/pages/order/cost-of-sales/index.jsx
  7. +16
    -3
      src/pages/order/gross-profit-store-sales/index.jsx
  8. +1
    -1
      src/pages/order/order-market-report/index.jsx
  9. +305
    -15
      src/pages/order/order-member-statement/index.jsx
  10. +81
    -4
      src/pages/order/order-member-statement/index.less
  11. +18
    -0
      src/pages/order/order-member-statement/service.js
  12. +256
    -15
      src/pages/order/order-product-report/index.jsx
  13. +3
    -15
      src/pages/order/order-product-report/index.less
  14. +18
    -0
      src/pages/order/order-product-report/service.js
  15. +273
    -16
      src/pages/order/order-revenue-statement/index.jsx
  16. +3
    -1
      src/pages/order/order-revenue-statement/index.less
  17. +17
    -0
      src/pages/order/order-revenue-statement/service.js
  18. +16
    -3
      src/pages/order/sales-gross-profit/index.jsx

+ 2
- 2
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',


+ 1
- 0
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",


+ 2
- 8
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,13 +583,7 @@ export async function getInitialState() {
access: 'k31',
},
{
name: '订单报表详情',
path: '/order/order-report/order-report-detail',
component: './order/order-report-detail',
access: 'k31',
},
{
name: '营收报表',
name: '营销报表',
path: '/order/order-revenue-statement',
component: './order/order-revenue-statement',
access: 'k31',


+ 79
- 0
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 (
<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>
)
}

+ 93
- 0
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;
}

+ 19
- 5
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 <span>{findGoods.name}</span>
return <span>{findGoods.title}</span>
} else {
return <span>暂无门店名称</span>
}
@@ -124,10 +125,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('查询店铺列表失败');
}
@@ -136,7 +138,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);
@@ -148,7 +150,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);
@@ -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(() => {
onQueryReportSalescost();
onQueryStoreList();


+ 16
- 3
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 <span>{findGoods.name}</span>
return <span>{findGoods.title}</span>
} else {
return <span>暂无门店名称</span>
}
},
},
{
@@ -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();


+ 1
- 1
src/pages/order/order-market-report/index.jsx 파일 보기

@@ -391,7 +391,7 @@ const LoadingCard = () => {
}

/**
* 营报表
* 营报表
* @returns
*/
export default function Index() {


+ 305
- 15
src/pages/order/order-member-statement/index.jsx 파일 보기

@@ -1,28 +1,318 @@
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 styles from './index.less';
const { RangePicker } = DatePicker;
import moment from 'moment';
import marketAPI from "./service";
import * as echarts from 'echarts';

/**
* 会员报表
* @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: []
});
//新增会员数
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 (
<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>
)
}

+ 81
- 4
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;
}

+ 18
- 0
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
});
}
};

+ 256
- 15
src/pages/order/order-product-report/index.jsx 파일 보기

@@ -1,28 +1,269 @@
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: '热销商品'
}
}
},
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 (
<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>
)
}

+ 3
- 15
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 {
}

+ 18
- 0
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
});
}
};

+ 273
- 16
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 (
<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>
)
}

+ 3
- 1
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;
}

+ 17
- 0
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',
});
}
};

+ 16
- 3
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 <span>{findGoods.name}</span>
return <span>{findGoods.title}</span>
} else {
return <span>暂无门店名称</span>
}
},
},
{
@@ -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();


불러오는 중...
취소
저장