feat: add admin statistics (#59)

This commit is contained in:
Buer
2024-02-01 18:45:53 +08:00
committed by GitHub
parent f2aafab0d9
commit 332b6fd397
20 changed files with 1225 additions and 65 deletions

View File

@@ -0,0 +1,104 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Stack, Typography } from '@mui/material';
import { LocalizationProvider, DatePicker } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
require('dayjs/locale/zh-cn');
export default class DateRangePicker extends React.Component {
state = {
startDate: this.props.defaultValue.start,
endDate: this.props.defaultValue.end,
localeText: this.props.localeText,
startOpen: false,
endOpen: false,
views: this.props?.views
};
handleStartChange = (date) => {
// 将 date设置当天的 00:00:00
date = date.startOf('day');
this.setState({ startDate: date });
};
handleEndChange = (date) => {
// 将 date设置当天的 23:59:59
date = date.endOf('day');
this.setState({ endDate: date });
};
handleStartOpen = () => {
this.setState({ startOpen: true });
};
handleStartClose = () => {
this.setState({ startOpen: false, endOpen: true });
};
handleEndClose = () => {
this.setState({ endOpen: false }, () => {
const { startDate, endDate } = this.state;
const { defaultValue, onChange } = this.props;
if (!onChange) return;
if (startDate !== defaultValue.start || endDate !== defaultValue.end) {
onChange({ start: startDate, end: endDate });
}
});
};
render() {
const { startOpen, endOpen, startDate, endDate, localeText } = this.state;
return (
<Stack direction="row" spacing={2} alignItems="center">
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={'zh-cn'}>
<DatePicker
label={localeText?.start || ''}
name="start_date"
defaultValue={startDate}
open={startOpen}
onChange={this.handleStartChange}
onOpen={this.handleStartOpen}
onClose={this.handleStartClose}
disableFuture
disableHighlightToday
slotProps={{
textField: {
readOnly: true,
onClick: this.handleStartOpen
}
}}
views={this.views}
/>
<Typography variant="body"> </Typography>
<DatePicker
label={localeText?.end || ''}
name="end_date"
defaultValue={endDate}
open={endOpen}
onChange={this.handleEndChange}
onOpen={this.handleStartOpen}
onClose={this.handleEndClose}
minDate={startDate}
disableFuture
disableHighlightToday
slotProps={{
textField: {
readOnly: true,
onClick: this.handleStartOpen
}
}}
views={this.views}
/>
</LocalizationProvider>
</Stack>
);
}
}
DateRangePicker.propTypes = {
defaultValue: PropTypes.object,
onChange: PropTypes.func,
localeText: PropTypes.object,
views: PropTypes.array
};

View File

@@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import SubCard from 'ui-component/cards/SubCard';
import { Typography, Tooltip, Divider } from '@mui/material';
import SkeletonDataCard from 'ui-component/cards/Skeleton/DataCard';
export default function DataCard({ isLoading, title, content, tip, subContent }) {
return (
<>
{isLoading ? (
<SkeletonDataCard />
) : (
<SubCard sx={{ height: '160px' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
{title}
</Typography>
<Typography variant="h3" sx={{ fontSize: '2rem', lineHeight: 1.5, fontWeight: 700 }}>
{tip ? (
<Tooltip title={tip} placement="top">
<span>{content}</span>
</Tooltip>
) : (
content
)}
</Typography>
<Divider />
<Typography variant="subtitle2" sx={{ mt: 2 }}>
{subContent}
</Typography>
</SubCard>
)}
</>
);
}
DataCard.propTypes = {
isLoading: PropTypes.bool,
title: PropTypes.string,
content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
tip: PropTypes.node,
subContent: PropTypes.node
};

View File

@@ -0,0 +1,17 @@
// material-ui
import Skeleton from '@mui/material/Skeleton';
import SubCard from 'ui-component/cards/SubCard';
import { Divider, Stack } from '@mui/material';
const DataCard = () => (
<SubCard sx={{ height: '160px' }}>
<Stack spacing={1}>
<Skeleton variant="rectangular" height={20} width={80} />
<Skeleton variant="rectangular" height={41} width={50} />
<Divider />
<Skeleton variant="rectangular" />
</Stack>
</SubCard>
);
export default DataCard;

View File

@@ -0,0 +1,63 @@
import PropTypes from 'prop-types';
// material-ui
import { Grid, Typography } from '@mui/material';
// third-party
import Chart from 'react-apexcharts';
// project imports
import SkeletonTotalGrowthBarChart from 'ui-component/cards/Skeleton/TotalGrowthBarChart';
import MainCard from 'ui-component/cards/MainCard';
import { gridSpacing } from 'store/constant';
import { Box } from '@mui/material';
// ==============================|| DASHBOARD DEFAULT - TOTAL GROWTH BAR CHART ||============================== //
const ApexCharts = ({ isLoading, chartDatas, title = '统计' }) => {
return (
<>
{isLoading ? (
<SkeletonTotalGrowthBarChart />
) : (
<MainCard>
<Grid container spacing={gridSpacing}>
<Grid item xs={12}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h3">{title}</Typography>
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
{chartDatas.series ? (
<Chart {...chartDatas} />
) : (
<Box
sx={{
minHeight: '490px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography variant="h3" color={'#697586'}>
暂无数据
</Typography>
</Box>
)}
</Grid>
</Grid>
</MainCard>
)}
</>
);
};
ApexCharts.propTypes = {
isLoading: PropTypes.bool,
chartDatas: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
title: PropTypes.string
};
export default ApexCharts;