forked from wangziqi/gongxue-base
chore: commit oxfmt formatting changes and verify artifacts
This commit is contained in:
@@ -7,7 +7,16 @@ import api from '../../api';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const COLORS = ['#007AFF', '#34C759', '#FF9500', '#FF3B30', '#5AC8FA', '#AF52DE', '#FF2D55', '#FFCC00'];
|
||||
const COLORS = [
|
||||
'#007AFF',
|
||||
'#34C759',
|
||||
'#FF9500',
|
||||
'#FF3B30',
|
||||
'#5AC8FA',
|
||||
'#AF52DE',
|
||||
'#FF2D55',
|
||||
'#FFCC00',
|
||||
];
|
||||
|
||||
const DashboardPage: React.FC = () => {
|
||||
const [stats, setStats] = useState<any>(null);
|
||||
@@ -26,21 +35,37 @@ const DashboardPage: React.FC = () => {
|
||||
const [s, g, e, r] = await Promise.all([
|
||||
api.get('/dashboard/stats'),
|
||||
api.get('/dashboard/gantt', { params: { periodStart: period[0], periodEnd: period[1] } }),
|
||||
api.get('/dashboard/expense-stats', { params: { periodStart: period[0], periodEnd: period[1] } }),
|
||||
api.get('/dashboard/room-ranking', { params: { periodStart: period[0], periodEnd: period[1] } }),
|
||||
api.get('/dashboard/expense-stats', {
|
||||
params: { periodStart: period[0], periodEnd: period[1] },
|
||||
}),
|
||||
api.get('/dashboard/room-ranking', {
|
||||
params: { periodStart: period[0], periodEnd: period[1] },
|
||||
}),
|
||||
]);
|
||||
setStats(s);
|
||||
setGanttData(g as any);
|
||||
setExpenseStats(e as any);
|
||||
setRoomRanking(r as any);
|
||||
} catch (e) { console.error(e); }
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => { fetchData(); }, [period]);
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [period]);
|
||||
|
||||
const expenseTypeMap: Record<string, string> = {
|
||||
water: '水费', electricity: '电费', cleaning: '保洁费', damage: '损坏赔偿', penalty: '罚款', key: '钥匙费', remote: '空调遥控器', deposit_deduction: '押金扣除', other: '其他',
|
||||
water: '水费',
|
||||
electricity: '电费',
|
||||
cleaning: '保洁费',
|
||||
damage: '损坏赔偿',
|
||||
penalty: '罚款',
|
||||
key: '钥匙费',
|
||||
remote: '空调遥控器',
|
||||
deposit_deduction: '押金扣除',
|
||||
other: '其他',
|
||||
};
|
||||
|
||||
// 甘特图配置
|
||||
@@ -73,22 +98,24 @@ const DashboardPage: React.FC = () => {
|
||||
grid: { left: 80, right: 30, top: 20, bottom: 30 },
|
||||
xAxis: { type: 'time', min: pStart, max: pEnd },
|
||||
yAxis: { type: 'category', data: rooms, inverse: true },
|
||||
series: [{
|
||||
type: 'custom',
|
||||
renderItem: (_params: any, api: any) => {
|
||||
const catIdx = api.value(0);
|
||||
const start = api.coord([api.value(1), catIdx]);
|
||||
const end = api.coord([api.value(2), catIdx]);
|
||||
const height = api.size([0, 1])[1] * 0.6;
|
||||
return {
|
||||
type: 'rect',
|
||||
shape: { x: start[0], y: start[1] - height / 2, width: end[0] - start[0], height },
|
||||
style: { ...api.style(), fill: api.visual('color'), stroke: '#fff', lineWidth: 1 },
|
||||
};
|
||||
series: [
|
||||
{
|
||||
type: 'custom',
|
||||
renderItem: (_params: any, api: any) => {
|
||||
const catIdx = api.value(0);
|
||||
const start = api.coord([api.value(1), catIdx]);
|
||||
const end = api.coord([api.value(2), catIdx]);
|
||||
const height = api.size([0, 1])[1] * 0.6;
|
||||
return {
|
||||
type: 'rect',
|
||||
shape: { x: start[0], y: start[1] - height / 2, width: end[0] - start[0], height },
|
||||
style: { ...api.style(), fill: api.visual('color'), stroke: '#fff', lineWidth: 1 },
|
||||
};
|
||||
},
|
||||
encode: { x: [1, 2], y: 0 },
|
||||
data,
|
||||
},
|
||||
encode: { x: [1, 2], y: 0 },
|
||||
data,
|
||||
}],
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -96,14 +123,16 @@ const DashboardPage: React.FC = () => {
|
||||
const pieOption = {
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 0 },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: expenseStats.map((e) => ({
|
||||
name: expenseTypeMap[e.type] || e.type,
|
||||
value: Number(e.total),
|
||||
})),
|
||||
}],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: expenseStats.map((e) => ({
|
||||
name: expenseTypeMap[e.type] || e.type,
|
||||
value: Number(e.total),
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 宿舍费用排行
|
||||
@@ -111,15 +140,33 @@ const DashboardPage: React.FC = () => {
|
||||
tooltip: {},
|
||||
grid: { left: 80, right: 20, bottom: 30, top: 10 },
|
||||
xAxis: { type: 'value' },
|
||||
yAxis: { type: 'category', data: roomRanking.map((r) => r.roomNumber).reverse(), inverse: false },
|
||||
series: [{ type: 'bar', data: roomRanking.map((r) => Number(r.total)).reverse(), itemStyle: { color: '#007AFF', borderRadius: [0, 4, 4, 0] } }],
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: roomRanking.map((r) => r.roomNumber).reverse(),
|
||||
inverse: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: roomRanking.map((r) => Number(r.total)).reverse(),
|
||||
itemStyle: { color: '#007AFF', borderRadius: [0, 4, 4, 0] },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (loading && !stats) return <Spin size="large" style={{ display: 'block', margin: '100px auto' }} />;
|
||||
if (loading && !stats)
|
||||
return <Spin size="large" style={{ display: 'block', margin: '100px auto' }} />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>数据面板</h2>
|
||||
<RangePicker
|
||||
value={[dayjs(period[0]), dayjs(period[1])]}
|
||||
@@ -131,22 +178,47 @@ const DashboardPage: React.FC = () => {
|
||||
|
||||
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="宿舍总数" value={stats?.totalRooms || 0} prefix={<HomeOutlined />} /></Card>
|
||||
<Card>
|
||||
<Statistic title="宿舍总数" value={stats?.totalRooms || 0} prefix={<HomeOutlined />} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="在读学生" value={stats?.totalStudents || 0} prefix={<TeamOutlined />} /></Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="在读学生"
|
||||
value={stats?.totalStudents || 0}
|
||||
prefix={<TeamOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="当前在住" value={stats?.occupiedBeds || 0} suffix={`/ ${stats?.totalCapacity || 0}`} prefix={<CheckCircleOutlined />} /></Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="当前在住"
|
||||
value={stats?.occupiedBeds || 0}
|
||||
suffix={`/ ${stats?.totalCapacity || 0}`}
|
||||
prefix={<CheckCircleOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="入住率" value={stats?.occupancyRate || 0} suffix="%" prefix={<DollarOutlined />} /></Card>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="入住率"
|
||||
value={stats?.occupancyRate || 0}
|
||||
suffix="%"
|
||||
prefix={<DollarOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card title="入住时间线(甘特图)" style={{ marginBottom: 24 }}>
|
||||
{ganttData.length > 0 ? (
|
||||
<ReactECharts option={ganttOption()} style={{ height: Math.max(300, ganttData.length * 40) }} />
|
||||
<ReactECharts
|
||||
option={ganttOption()}
|
||||
style={{ height: Math.max(300, ganttData.length * 40) }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无入住数据</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user