86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
import React, { type CSSProperties } from 'react';
|
|
import { Card, Col, Row } from 'antd';
|
|
import { useIntersectionObserver } from 'usehooks-ts';
|
|
import ReactECharts from '../../components/ECharts';
|
|
import type { ClassroomOccupancy, GanttRoom } from './Dashboard.types';
|
|
import { buildClassroomHeatmapOption, buildGanttOption } from './DashboardCharts';
|
|
|
|
const useInViewport = (rootMargin = '200px') => {
|
|
const { ref, isIntersecting } = useIntersectionObserver({
|
|
rootMargin,
|
|
freezeOnceVisible: true,
|
|
});
|
|
return { ref, inView: isIntersecting };
|
|
};
|
|
|
|
const LazySection: React.FC<{
|
|
title: string;
|
|
vp: { ref: (node?: Element | null) => void; inView: boolean };
|
|
minHeight: number;
|
|
style?: CSSProperties;
|
|
children: React.ReactNode;
|
|
}> = ({ title, vp, minHeight, style, children }) => {
|
|
return (
|
|
<div ref={vp.ref} style={style}>
|
|
{vp.inView ? (
|
|
<Row gutter={[16, 16]}>
|
|
<Col xs={24}>
|
|
<Card title={title}>{children}</Card>
|
|
</Col>
|
|
</Row>
|
|
) : (
|
|
<Card title={title} style={{ minHeight }}>
|
|
<div style={{ textAlign: 'center', padding: 40, color: '#999' }}>加载中…</div>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const ClassroomHeatmapCard: React.FC<{
|
|
data: ClassroomOccupancy[];
|
|
isMobile: boolean;
|
|
}> = ({ data, isMobile }) => {
|
|
const vp = useInViewport('200px');
|
|
return (
|
|
<LazySection
|
|
title="教室占用热力图"
|
|
vp={vp}
|
|
minHeight={isMobile ? 340 : 440}
|
|
style={{ marginBottom: 24 }}
|
|
>
|
|
{data.length > 0 ? (
|
|
<ReactECharts
|
|
option={buildClassroomHeatmapOption(data)}
|
|
style={{ width: '100%', height: isMobile ? 300 : 400 }}
|
|
/>
|
|
) : (
|
|
<div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无教室数据</div>
|
|
)}
|
|
</LazySection>
|
|
);
|
|
};
|
|
|
|
export const GanttCard: React.FC<{ data: GanttRoom[]; isMobile: boolean }> = ({
|
|
data,
|
|
isMobile,
|
|
}) => {
|
|
const vp = useInViewport('200px');
|
|
return (
|
|
<LazySection
|
|
title="入住时间线(甘特图)"
|
|
vp={vp}
|
|
minHeight={isMobile ? 340 : 490}
|
|
>
|
|
{data.length > 0 ? (
|
|
<ReactECharts
|
|
option={buildGanttOption(data)}
|
|
style={{ width: '100%', height: isMobile ? 300 : 450 }}
|
|
/>
|
|
) : (
|
|
<div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无入住数据</div>
|
|
)}
|
|
</LazySection>
|
|
);
|
|
};
|