feat: 年度业绩: 总数据卡片

feature/2.0-sales-trade
Lei OT 2 years ago
parent 81269016a0
commit 06e266c9c6

@ -1,13 +1,19 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Droid Sans', 'Helvetica Neue', sans-serif;
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
monospace; }
section {
display: block;
}
main section {
margin-top: 1em;
margin-bottom: 1em;
} }

@ -0,0 +1,46 @@
{
"get|/service-web/QueryData/GetTradeSummary": {
"errcode": 0,
"errmsg": "",
"data": null,
"loading": false,
"result1": {
"SumML|999-9999": 27018,
"SumMLrate|-20-100": 23,
"SumMLKPIrate|20-100": 23,
"SumOrder|999-999": 27018,
"SumOrderrate": "@float(-20,20,2,2)",
"SumOrderKPIrate|20-100": 25,
"SumPersonNum|999-999": 27018,
"SumPersonNumrate|-50-1": 33,
"SumPersonNumKPIrate|20-100": 33,
"groups": "2023-05-01~2023-05-31",
"key": "@increment"
},
"result2": {
"SumML|999-9999": 27018,
"SumOrder|999-999": 27018,
"SumPersonNum|999-999": 27018,
"groups": "2022-05-01~2023-05-31",
"key": "@increment"
}
},
"get|/service-web/QueryData/GetYJ": {
"errcode": 0,
"errmsg": "",
"data": null,
"loading": false,
"result1|10": [
{
"AvgML|999-9999": 27018,
"COLI_Department": "1,2,28,7",
"OPI_SN": "@id",
"OPI_Name": "@cname",
"COLI_YJLY|999-99999": 215493,
"groups": "2023-05-01~2023-05-31",
"key": "@increment"
}
],
"result2": []
}
}

@ -10,21 +10,21 @@ const configArray = files.keys().reduce((s, c) => s.concat(files(c)), []);
// 注册所有的mock服务 // 注册所有的mock服务
configArray.forEach((item) => { configArray.forEach((item) => {
for (const [path, target] of Object.entries(item)) { for (const [path, target] of Object.entries(item)) {
const protocol = path.split('|'); const protocol = path.split('|');
// const apiUrl = new RegExp(`${protocol[1]}.*`); // const apiUrl = new RegExp(`${protocol[1]}.*`);
// fetch 不支持mock // fetch 不支持mock
// Mock.mock(apiUrl, protocol[0].toLocaleLowerCase(), target); // Mock.mock(apiUrl, protocol[0].toLocaleLowerCase(), target);
// fetchMock.mock(matcher, response, options) // fetchMock.mock(matcher, response, options)
fetchMock.mock( fetchMock.mock(
`path:${protocol[1]}`, `path:${protocol[1]}`,
() => { () => {
const res = Mock.mock(target); const res = Mock.mock(target);
console.log(`invoke mock`, protocol[1], res); console.log(`invoke mock`, protocol[1], res);
return res; return res;
}, },
{ method: protocol[0] } { method: protocol[0], delay: 1000 }
); );
} }
}); });

@ -10,6 +10,7 @@ import SaleStore from "./SaleStore";
import WechatStore from "./Wechat"; import WechatStore from "./Wechat";
import WhatsAppStore from "./WhatsApp"; import WhatsAppStore from "./WhatsApp";
import CustomerServicesStore from "./CustomerServices"; import CustomerServicesStore from "./CustomerServices";
import TradeStore from "./Trade";
class Index { class Index {
constructor() { constructor() {
@ -24,9 +25,10 @@ class Index {
this.wechatStore = new WechatStore(this); this.wechatStore = new WechatStore(this);
this.whatsAppStore = new WhatsAppStore(this); this.whatsAppStore = new WhatsAppStore(this);
this.customerServicesStore = new CustomerServicesStore(this); this.customerServicesStore = new CustomerServicesStore(this);
this.TradeStore = new TradeStore(this);
makeAutoObservable(this); makeAutoObservable(this);
} }
} }
export default Index; export default Index;

@ -0,0 +1,44 @@
import { makeAutoObservable, runInAction } from 'mobx';
import * as req from '../utils/request';
import { isEmpty } from '../utils/commons';
/**
* 计算变化值
*/
const calcRate = (r1, r2) => {
// 的
};
class Trade {
constructor(rootStore) {
this.rootStore = rootStore;
makeAutoObservable(this);
}
fetchSummaryData() {
this.summaryData.loading = true;
req.fetchJSON('/service-web/QueryData/GetTradeSummary').then((json) => {
if (json.errcode === 0) {
runInAction(() => {
this.summaryData = { loading: false, ...json };
});
}
});
}
fetchTradeData() {
this.yearlyData.loading = true;
req.fetchJSON('/service-web/QueryData/GetYJ').then((json) => {
if (json.errcode === 0) {
runInAction(() => {
this.yearlyData = { loading: false, ...json };
});
}
});
}
summaryData = { loading: false };
yearlyData = { loading: false };
}
export default Trade;

@ -150,10 +150,22 @@ export function isNotEmpty(val) {
return val !== undefined && val !== null && val !== ""; return val !== undefined && val !== null && val !== "";
} }
/**
* ! 不支持计算 Set Map
* @param {*} val
* @example
* true if: 0, [], {}, null, '', undefined
* false if: 'false', 'undefined'
*/
export function isEmpty(val) { export function isEmpty(val) {
return val === undefined || val === null || val === ""; // return val === undefined || val === null || val === "";
return [Object, Array].includes((val || {}).constructor) && !Object.entries((val || {})).length;
} }
/**
* @example
* empty(0) => false
*/
export function empty(a) { export function empty(a) {
if (a === "") return true; // 检验空字符串 if (a === "") return true; // 检验空字符串
if (a === "null") return true; // 检验字符串类型的null if (a === "null") return true; // 检验字符串类型的null
@ -162,7 +174,6 @@ export function empty(a) {
if (Array.prototype.isPrototypeOf(a) && a.length === 0) return true; // 检验空数组 if (Array.prototype.isPrototypeOf(a) && a.length === 0) return true; // 检验空数组
if (Object.prototype.isPrototypeOf(a) && Object.keys(a).length === 0) return true; // 检验空对象 if (Object.prototype.isPrototypeOf(a) && Object.keys(a).length === 0) return true; // 检验空对象
return false; return false;
// return [Object, Array].includes((obj || {}).constructor) && !Object.entries((obj || {})).length;
} }
export function prepareUrl(url) { export function prepareUrl(url) {

@ -1,31 +1,77 @@
import React, {Component, useContext} from 'react'; import { useContext, useEffect } from 'react';
import {observer} from 'mobx-react'; import { observer } from 'mobx-react';
import {Row, Col, Button, Tabs, Table} from 'antd'; import { Row, Col, Spin, Card, Statistic, Progress } from 'antd';
import {stores_Context} from '../config'; import { stores_Context } from '../config';
import {useNavigate} from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import { import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
SlackOutlined, import { isEmpty } from './../utils/commons';
SketchOutlined, import './home.css';
AntCloudOutlined,
RedditOutlined,
GithubOutlined
} from '@ant-design/icons';
const Home = () => { const Home = () => {
const navigate = useNavigate();
const { TradeStore } = useContext(stores_Context);
const { yearlyData, summaryData } = TradeStore;
const navigate = useNavigate(); useEffect(() => {
const {auth_store} = useContext(stores_Context); if (isEmpty(summaryData?.result1)) {
TradeStore.fetchSummaryData();
}
if (isEmpty(yearlyData?.result1)) {
TradeStore.fetchTradeData();
}
return () => {};
}, []);
const StatisticCard = (props) => {
const valueStyle = { color: props.VSrate < 0 ? '#cf1322' : '#3f8600' };
const VSIcon = () => (props.VSrate < 0 ? <ArrowDownOutlined /> : <ArrowUpOutlined />);
return ( return (
<div> <Card>
<SketchOutlined/> <AntCloudOutlined/> <SlackOutlined/> <RedditOutlined/> <GithubOutlined/> <Statistic
<br/> className={'__hn-sta-wrapper'}
这就是一个主页,什么也没写 valueStyle={valueStyle}
<br/> suffix={
<SketchOutlined/> <AntCloudOutlined/> <SlackOutlined/> <RedditOutlined/> <GithubOutlined/> props.VSrate && (
</div> <>
<VSIcon />
<span>{props.VSrate}</span>
<span>%</span>
</>
)
}
{...props}
/>
{props.showProgress !== false && <Progress percent={props.KPIrate} size="small" format={(percent) => `${props.KPIrate}%`} />}
</Card>
); );
};
return (
<>
<h2>年度业绩</h2>
<section>
<Spin spinning={summaryData.loading}>
<Row gutter={16}>
<Col span={6}>
<StatisticCard title="人数" value={summaryData?.result1?.SumPersonNum} VSrate={summaryData?.result1?.SumPersonNumrate} KPIrate={summaryData?.result1?.SumPersonNumKPIrate} />
</Col>
<Col span={6}>
<StatisticCard title="成团" value={summaryData?.result1?.SumOrder} VSrate={summaryData?.result1?.SumOrderrate} KPIrate={summaryData?.result1?.SumOrderKPIrate} />
</Col>
<Col span={6}>
<StatisticCard title="毛利" value={summaryData?.result1?.SumML} VSrate={summaryData?.result1?.SumMLrate} KPIrate={summaryData?.result1?.SumMLKPIrate} />
</Col>
<Col span={6}>
<StatisticCard title="完成率" value={`${summaryData?.result1?.SumMLKPIrate}%`} showProgress={false} />
</Col>
</Row>
</Spin>
</section>
<section>
<Spin spinning={yearlyData.loading}></Spin>
</section>
</>
);
}; };
export default observer(Home); export default observer(Home);

@ -0,0 +1,4 @@
.__hn-sta-wrapper .ant-statistic-content-suffix{
float: right;
font-size: 18px;
}
Loading…
Cancel
Save