mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-17 13:43:42 +08:00
✨ feat: add admin statistics (#59)
This commit is contained in:
104
web/src/ui-component/DateRangePicker.js
Normal file
104
web/src/ui-component/DateRangePicker.js
Normal 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
|
||||
};
|
||||
41
web/src/ui-component/cards/DataCard.js
Normal file
41
web/src/ui-component/cards/DataCard.js
Normal 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
|
||||
};
|
||||
17
web/src/ui-component/cards/Skeleton/DataCard.js
Normal file
17
web/src/ui-component/cards/Skeleton/DataCard.js
Normal 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;
|
||||
63
web/src/ui-component/chart/ApexCharts.js
Normal file
63
web/src/ui-component/chart/ApexCharts.js
Normal 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;
|
||||
Reference in New Issue
Block a user