|
|
@@ -1,17 +1,22 @@
|
|
|
import { PageContainer } from '@ant-design/pro-components';
|
|
|
-import { Button, Card, Col, DatePicker, Form, Row, Space } from 'antd';
|
|
|
+import { Button, Card, Col, DatePicker, Form, Row, Space, Spin } from 'antd';
|
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
|
import styles from './Welcome.less';
|
|
|
-import { getStatisticData } from '@/services/statistic';
|
|
|
-import bookSum from '../../public/assets/book-sum.png';
|
|
|
-import totalOnlineNum from '../../public/icons/total_online_num.png';
|
|
|
-import activation1 from '../../public/icons/activation_1.png';
|
|
|
-import activation2 from '../../public/icons/activation_2.png';
|
|
|
-import online1 from '../../public/icons/online_1.png';
|
|
|
-import online2 from '../../public/icons/online_2.png';
|
|
|
+import {
|
|
|
+ getAppUserData,
|
|
|
+ getDailyActiveData,
|
|
|
+ getNewUserData,
|
|
|
+ getStatisticData,
|
|
|
+} from '@/services/statistic';
|
|
|
import { ReloadOutlined, SearchOutlined } from '@ant-design/icons';
|
|
|
import moment from 'moment/moment';
|
|
|
import * as echarts from 'echarts';
|
|
|
+import userIcon from '../../public/assets/user-icon.png';
|
|
|
+import appIcon from '../../public/assets/app-icon.png';
|
|
|
+import monthIcon from '../../public/assets/month-icon.png';
|
|
|
+import todayIcon from '../../public/assets/today-icon.png';
|
|
|
+import LineChart from '@/components/EchartsCommon/lineChart';
|
|
|
+import BarChart from '@/components/EchartsCommon/barChart';
|
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
|
@@ -23,40 +28,98 @@ const Welcome: React.FC = () => {
|
|
|
const [form] = Form.useForm();
|
|
|
const [data, setData] = useState<any>(null);
|
|
|
const [searchData, setSearchData] = useState<object | null>({});
|
|
|
+ const [userData, setUserData] = useState<any>(null);
|
|
|
+ const [appUserData, setAppUserData] = useState<any>(null);
|
|
|
+ const [dailyActiveData, setDailyActiveData] = useState<any>(null);
|
|
|
+ const [deviceTypeData, setDeviceType] = useState<any>(null);
|
|
|
const chartRef: any = useRef();
|
|
|
|
|
|
// 获取数据
|
|
|
- const getList = () => {
|
|
|
+ const getDeviceData = () => {
|
|
|
const params = {
|
|
|
...searchData,
|
|
|
};
|
|
|
getStatisticData(params).then((res) => {
|
|
|
if (res && res.code === 0) {
|
|
|
setData(res.data);
|
|
|
+ // 设备类型统计数据
|
|
|
+ if (
|
|
|
+ res.data &&
|
|
|
+ res.data.ffx &&
|
|
|
+ res.data.wuheng &&
|
|
|
+ res.data.xfcs &&
|
|
|
+ res.data.xfjh &&
|
|
|
+ res.data.xfjs
|
|
|
+ ) {
|
|
|
+ const deviceName = ['分风箱', '五恒一体机', '新风除湿', '新风净化', '新风加湿'];
|
|
|
+ const deviceTypeValue = [
|
|
|
+ res.data.ffx,
|
|
|
+ res.data.wuheng,
|
|
|
+ res.data.xfcs,
|
|
|
+ res.data.xfjh,
|
|
|
+ res.data.xfjs,
|
|
|
+ ];
|
|
|
+ setDeviceType({ deviceName, deviceTypeValue });
|
|
|
+ } else {
|
|
|
+ setDeviceType(null);
|
|
|
+ }
|
|
|
+ // 渲染环形图
|
|
|
const chart = echarts.init(chartRef.current);
|
|
|
const region = JSON.parse(res.data.region);
|
|
|
const newData = Object.entries(region).map(([name, value]) => ({ value, name }));
|
|
|
- console.log(newData);
|
|
|
const options = {
|
|
|
tooltip: {
|
|
|
trigger: 'item',
|
|
|
},
|
|
|
legend: {
|
|
|
+ type: 'plain',
|
|
|
+ icon: 'circle',
|
|
|
orient: 'vertical',
|
|
|
- left: 'left',
|
|
|
+ left: 40,
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
type: 'pie',
|
|
|
- radius: '50%',
|
|
|
+ radius: [175, 200],
|
|
|
+ center: ['65%', '50%'],
|
|
|
data: newData,
|
|
|
- emphasis: {
|
|
|
- itemStyle: {
|
|
|
- shadowBlur: 10,
|
|
|
- shadowOffsetX: 0,
|
|
|
- shadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ padAngle: 5,
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: 10,
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ normal: {
|
|
|
+ show: false,
|
|
|
+ position: 'center',
|
|
|
+ formatter: '{text|{c}}\n{b}',
|
|
|
+ rich: {
|
|
|
+ text: {
|
|
|
+ align: 'center',
|
|
|
+ verticalAlign: 'middle',
|
|
|
+ padding: 8,
|
|
|
+ fontSize: 50,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: '#333',
|
|
|
+ },
|
|
|
+ value: {
|
|
|
+ align: 'center',
|
|
|
+ verticalAlign: 'middle',
|
|
|
+ fontSize: 20,
|
|
|
+ color: '#bfbfbf',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ show: true,
|
|
|
+ textStyle: {
|
|
|
+ fontSize: '12',
|
|
|
+ },
|
|
|
},
|
|
|
},
|
|
|
+ labelLine: {
|
|
|
+ show: false,
|
|
|
+ },
|
|
|
},
|
|
|
],
|
|
|
};
|
|
|
@@ -66,13 +129,56 @@ const Welcome: React.FC = () => {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+ // 新增用户数统计
|
|
|
+ const getUserData = () => {
|
|
|
+ getNewUserData().then((res) => {
|
|
|
+ if (res && res.code === 0) {
|
|
|
+ setUserData(res.data);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // app使用次数统计
|
|
|
+ const getAppCount = () => {
|
|
|
+ getAppUserData().then((res) => {
|
|
|
+ if (res && res.code === 0) {
|
|
|
+ setAppUserData(res.data);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取日活用户统计数据
|
|
|
+ const getDailyActiveUser = () => {
|
|
|
+ getDailyActiveData().then((res) => {
|
|
|
+ if (res && res.code === 0) {
|
|
|
+ const list = res.data || [];
|
|
|
+ if (list && list.length > 0) {
|
|
|
+ // 把x轴的数据和y轴的数据进行匹配
|
|
|
+ const xData = list.map(
|
|
|
+ (el: { date: string }) => moment(el.date).format('MM-DD') || '暂无',
|
|
|
+ );
|
|
|
+ const yData = list.map((el: { dau: number }) => el.dau);
|
|
|
+ setDailyActiveData({ xData, yData });
|
|
|
+ } else {
|
|
|
+ setDailyActiveData(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
// 初始化
|
|
|
useEffect(() => {
|
|
|
- getList();
|
|
|
+ getDeviceData();
|
|
|
+ // 用户数据
|
|
|
+ getUserData();
|
|
|
+ // app使用次数统计
|
|
|
+ getAppCount();
|
|
|
+ // 获取日活用户统计
|
|
|
+ getDailyActiveUser();
|
|
|
}, []);
|
|
|
|
|
|
useEffect(() => {
|
|
|
- getList();
|
|
|
+ getDeviceData();
|
|
|
}, [searchData]);
|
|
|
|
|
|
// 搜索
|
|
|
@@ -92,84 +198,222 @@ const Welcome: React.FC = () => {
|
|
|
setSearchData(null);
|
|
|
};
|
|
|
|
|
|
+ // 千分号格式化
|
|
|
+ const formatNumber = (num: any) => {
|
|
|
+ if (num === null || num === undefined || isNaN(num)) return '';
|
|
|
+ return parseFloat(num).toLocaleString(undefined, {
|
|
|
+ maximumFractionDigits: 20,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
<PageContainer>
|
|
|
- <Card style={{ marginBottom: '20px' }}>
|
|
|
- <Form form={form} layout="inline" onFinish={onFinish}>
|
|
|
- <Form.Item name="time" label="时间">
|
|
|
- <RangePicker />
|
|
|
- </Form.Item>
|
|
|
- <Form.Item style={{ marginBottom: '10px' }}>
|
|
|
- <Space>
|
|
|
- <Button type="primary" htmlType="submit">
|
|
|
- <SearchOutlined />
|
|
|
- 查询
|
|
|
- </Button>
|
|
|
- <Button htmlType="button" onClick={onReset}>
|
|
|
- <ReloadOutlined />
|
|
|
- 重置
|
|
|
- </Button>
|
|
|
- </Space>
|
|
|
- </Form.Item>
|
|
|
- </Form>
|
|
|
- </Card>
|
|
|
- <Card className={styles.container}>
|
|
|
- <Row gutter={[30, 20]}>
|
|
|
- <Col span={5} xs={24} sm={12} md={12} lg={12} xl={5} xxl={5}>
|
|
|
- <div className={styles.itemLeft}>
|
|
|
- <span className={styles.leftTitle}>总数</span>
|
|
|
- <div className={styles.imgBox}>
|
|
|
- <img src={bookSum} alt="" />
|
|
|
- </div>
|
|
|
- <span className={styles.leftNumber}>{data?.total}</span>
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={3} xs={24} sm={12} md={12} lg={12} xl={3} xxl={3}>
|
|
|
- <div className={styles.itemCenter}>
|
|
|
- <div className={styles.itemImg}>
|
|
|
- <img src={totalOnlineNum} alt="" />
|
|
|
- </div>
|
|
|
- <div className={styles.itemValue}>{data?.online}</div>
|
|
|
- <div className={styles.itemTitle}>总在线数</div>
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={8} xs={24} sm={12} md={12} lg={12} xl={8} xxl={8}>
|
|
|
- <div className={styles.itemRight}>
|
|
|
- <div className={[styles.imgRightBox, styles.itemRightBg4].join(' ')}>
|
|
|
- <div className={styles.itemRightImg}>
|
|
|
- <img src={online2} alt="" />
|
|
|
+ <Card style={{ marginBottom: '20px' }} hoverable>
|
|
|
+ <div className={styles.welcomeTitle}>Hi,欢迎使用永续绿建运营管理平台!</div>
|
|
|
+ <div className={styles.timeSearch}>
|
|
|
+ <div className={styles.dataOverviewTitle}>数据总览</div>
|
|
|
+ <Form form={form} layout="inline" onFinish={onFinish}>
|
|
|
+ <Form.Item name="time" label="时间">
|
|
|
+ <RangePicker />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item style={{ marginBottom: '10px' }}>
|
|
|
+ <Space>
|
|
|
+ <Button type="primary" htmlType="submit">
|
|
|
+ <SearchOutlined />
|
|
|
+ 查询
|
|
|
+ </Button>
|
|
|
+ <Button htmlType="button" onClick={onReset}>
|
|
|
+ <ReloadOutlined />
|
|
|
+ 重置
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.dataTop}>
|
|
|
+ <Row gutter={[20, 20]}>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card className={styles.dataItem} bordered={false}>
|
|
|
+ <div className={styles.dataTitle}>设备总数</div>
|
|
|
+ <div className={styles.dataValue}>
|
|
|
+ <span className={styles.dataNum}>{data?.total || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>台</span>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card className={styles.dataItem} bordered={false}>
|
|
|
+ <div className={styles.dataTitle}>总在线数</div>
|
|
|
+ <div className={styles.dataValue}>
|
|
|
+ <span className={styles.dataNum}>{data?.online || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>台</span>
|
|
|
</div>
|
|
|
- <span className={styles.itemRightVal}>{data?.wuheng_online}</span>
|
|
|
- <span className={styles.itemRightTitle}>五恒在线数</span>
|
|
|
- </div>
|
|
|
- <div className={[styles.imgRightBox, styles.itemRightBg1].join(' ')}>
|
|
|
- <div className={styles.itemRightImg}>
|
|
|
- <img src={activation2} alt="" />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card className={styles.dataItem} bordered={false}>
|
|
|
+ <div className={styles.dataTitle}>五恒在线数</div>
|
|
|
+ <div className={styles.dataValue}>
|
|
|
+ <span className={styles.dataNum}>{data?.wuheng_online || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>台</span>
|
|
|
</div>
|
|
|
- <span className={styles.itemRightVal}>{data?.wuheng}</span>
|
|
|
- <span className={styles.itemRightTitle}>激活五恒数</span>
|
|
|
- </div>
|
|
|
- <div className={[styles.imgRightBox, styles.itemRightBg3].join(' ')}>
|
|
|
- <div className={styles.itemRightImg}>
|
|
|
- <img src={online1} alt="" />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card className={styles.dataItem} bordered={false}>
|
|
|
+ <div className={styles.dataTitle}>激活五恒数</div>
|
|
|
+ <div className={styles.dataValue}>
|
|
|
+ <span className={styles.dataNum}>{data?.wuheng || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>台</span>
|
|
|
</div>
|
|
|
- <span className={styles.itemRightVal}>{data?.ffx_online}</span>
|
|
|
- <span className={styles.itemRightTitle}>分风箱在线数</span>
|
|
|
- </div>
|
|
|
- <div className={[styles.imgRightBox, styles.itemRightBg2].join(' ')}>
|
|
|
- <div className={styles.itemRightImg}>
|
|
|
- <img src={activation1} alt="" />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card className={styles.dataItem} bordered={false}>
|
|
|
+ <div className={styles.dataTitle}>分风箱在线数</div>
|
|
|
+ <div className={styles.dataValue}>
|
|
|
+ <span className={styles.dataNum}>{data?.ffx_online || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>台</span>
|
|
|
</div>
|
|
|
- <span className={styles.itemRightVal}>{data?.ffx}</span>
|
|
|
- <span className={styles.itemRightTitle}>激活分风箱数</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={8} xs={24} sm={12} md={12} lg={12} xl={8} xxl={8}>
|
|
|
- <div style={{ width: '700px', height: '450px' }} ref={chartRef} />
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </div>
|
|
|
</Card>
|
|
|
+
|
|
|
+ <Row gutter={[20, 20]}>
|
|
|
+ <Col span={16}>
|
|
|
+ <div>
|
|
|
+ <Row gutter={[20, 20]}>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card hoverable>
|
|
|
+ <div className={styles.userDataItem}>
|
|
|
+ <div>
|
|
|
+ <div className={styles.dataItemTitle}>用户数</div>
|
|
|
+ <div>
|
|
|
+ <span className={styles.dataItemValue}>
|
|
|
+ {userData?.total && userData?.total.length > 3
|
|
|
+ ? formatNumber(userData?.total)
|
|
|
+ : userData?.total || 0}
|
|
|
+ </span>
|
|
|
+ <span style={{ color: 'gray' }}>人</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.dataItemIcon}>
|
|
|
+ <img src={userIcon} alt="用户icon" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card hoverable>
|
|
|
+ <div className={styles.userDataItem}>
|
|
|
+ <div>
|
|
|
+ <div className={styles.dataItemTitle}>今日新增用户数</div>
|
|
|
+ <div>
|
|
|
+ <span className={styles.dataItemValue}>{userData?.tnu || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>人</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.dataItemIcon}>
|
|
|
+ <img src={todayIcon} alt="今日新增icon" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card hoverable>
|
|
|
+ <div className={styles.userDataItem}>
|
|
|
+ <div>
|
|
|
+ <div className={styles.dataItemTitle}>本月新增用户数</div>
|
|
|
+ <div>
|
|
|
+ <span className={styles.dataItemValue}>{userData?.mnu || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>人</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.dataItemIcon}>
|
|
|
+ <img src={monthIcon} alt="本月新增icon" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card hoverable>
|
|
|
+ <div className={styles.userDataItem}>
|
|
|
+ <div>
|
|
|
+ <div className={styles.dataItemTitle}>今日App使用次数</div>
|
|
|
+ <div>
|
|
|
+ <span className={styles.dataItemValue}>{appUserData?.total || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>次</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.dataItemIcon}>
|
|
|
+ <img src={appIcon} alt="App统计次数icon" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col flex="1">
|
|
|
+ <Card hoverable>
|
|
|
+ <div className={styles.userDataItem}>
|
|
|
+ <div>
|
|
|
+ <div className={styles.dataItemTitle}>本月日均使用APP次数</div>
|
|
|
+ <div>
|
|
|
+ <span className={styles.dataItemValue}>{appUserData?.month_avg || 0}</span>
|
|
|
+ <span style={{ color: 'gray' }}>次</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles.dataItemIcon}>
|
|
|
+ <img src={appIcon} alt="App统计次数icon" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ <Row gutter={[20, 20]} style={{ marginTop: '20px' }}>
|
|
|
+ <Col span={12}>
|
|
|
+ <Card hoverable>
|
|
|
+ <div style={{ marginBottom: '20px', fontWeight: 'bold' }}>日活用户统计</div>
|
|
|
+ {dailyActiveData && dailyActiveData.xData && dailyActiveData.yData ? (
|
|
|
+ <LineChart
|
|
|
+ color="#1890ff"
|
|
|
+ height="450px"
|
|
|
+ width="700px"
|
|
|
+ xData={dailyActiveData?.xData}
|
|
|
+ yData={dailyActiveData?.yData}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <Spin />
|
|
|
+ )}
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={12}>
|
|
|
+ <Card hoverable>
|
|
|
+ <div style={{ marginBottom: '20px', fontWeight: 'bold' }}>设备类型统计</div>
|
|
|
+ {deviceTypeData && deviceTypeData.deviceName && deviceTypeData.deviceTypeValue ? (
|
|
|
+ <BarChart
|
|
|
+ color="#1890ff"
|
|
|
+ height="450px"
|
|
|
+ width="700px"
|
|
|
+ seriesData={deviceTypeData?.deviceTypeValue}
|
|
|
+ yData={deviceTypeData?.deviceName}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <Spin />
|
|
|
+ )}
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={8}>
|
|
|
+ <Card hoverable>
|
|
|
+ <div style={{ marginBottom: '20px', fontWeight: 'bold' }}>设备使用地区统计</div>
|
|
|
+ <div style={{ width: '700px', height: '620px' }} ref={chartRef} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
</PageContainer>
|
|
|
);
|
|
|
};
|