import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { DatePicker, Card, Row, Col, Statistic, Tag, Space, Button, Modal, Spin, Empty, Tooltip, } from 'antd'; import { CalendarOutlined, FileTextOutlined } from '@ant-design/icons'; import dayjs, { Dayjs } from 'dayjs'; import api from '../../api'; import { downloadBlob } from '../../utils/download'; interface ScheduleData { year: number; month: number; days: number; classrooms: any[]; tenants: any[]; matrix: Record>; summary: Record< number, { totalDays: number; rentedDays: number; idleDays: number; occupancyRate: number } >; } const ClassroomSchedulePage: React.FC = () => { const [month, setMonth] = useState(dayjs()); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [detailModal, setDetailModal] = useState(null); const fetchData = useCallback(async () => { setLoading(true); try { const res: any = await api.get('/classroom-rentals/schedule', { params: { year: month.year(), month: month.month() + 1 }, }); setData(res); } catch (e) { console.error(e); } setLoading(false); }, [month]); useEffect(() => { fetchData(); }, [fetchData]); // 按楼栋+楼层分组教室 const groups = useMemo(() => { if (!data) return []; const map = new Map(); for (const c of data.classrooms) { const key = `${c.building || '其他'}${c.floor ? ` · ${c.floor}层` : ''}`; if (!map.has(key)) map.set(key, []); map.get(key)!.push(c); } return Array.from(map.entries()).map(([name, classrooms]) => ({ name, classrooms })); }, [data]); // 整体统计 const overall = useMemo(() => { if (!data) return { total: 0, rented: 0, rate: 0 }; let rented = 0; const total = data.classrooms.length * data.days; for (const cid of Object.keys(data.summary)) { rented += data.summary[+cid].rentedDays; } return { total, rented, rate: total > 0 ? Math.round((rented / total) * 100) : 0, }; }, [data]); const showDetail = async (rentalId: number) => { try { const res: any = await api.get(`/classroom-rentals/${rentalId}`); setDetailModal(res); } catch (e) { console.error(e); } }; const handleDownloadContract = async (id: number, filename?: string) => { try { await downloadBlob( `/classroom-rentals/${id}/contract`, filename || `contract-${id}.pdf`, ); } catch { // downloadBlob already shows an error via throw } }; return (

教室排期总览

v && setMonth(v)} allowClear={false} placeholder="选择月份" format="YYYY年M月" />
{/* 统计卡片 */} 70 ? '#cf1322' : overall.rate > 40 ? '#fa8c16' : '#3f8600', }, }} /> {/* 图例 */} {data && ( 内部排课 {data.tenants.map((t) => ( {t.name} (租赁) ))} 空闲 )} {!data || data.classrooms.length === 0 ? ( ) : (
{groups.map((group) => ( {Array.from({ length: data.days }, (_, i) => i + 1).map((d) => ( ))} {group.classrooms.map((c) => { const sum = data.summary[c.id] || { rentedDays: 0, totalDays: data.days, occupancyRate: 0, }; return ( {Array.from({ length: data.days }, (_, i) => i + 1).map((d) => { const cell = data.matrix[c.id]?.[d]; const isInternal = cell?.scheduleType === 'INTERNAL'; const isRental = cell?.scheduleType === 'RENTAL'; return ( ); })} ); })}
教室 类型 占用率 {d}
{c.name} {c.roomType} 0.7 ? '#cf1322' : sum.occupancyRate > 0.4 ? '#fa8c16' : '#3f8600', }} > {Math.round(sum.occupancyRate * 100)}% { if (isRental) showDetail(cell.rentalId); }} style={{ padding: 0, border: '1px solid #f0f0f0', background: cell?.color || '#fff', height: 26, cursor: isRental ? 'pointer' : 'default', textAlign: 'center', }} > {cell && ( {isInternal ? '📖' : cell.hasContract ? '📄' : ''} )}
))}
)}
setDetailModal(null)} footer={null} width={500} > {detailModal && (
教室: {detailModal.classroom?.building} · {detailModal.classroom?.name}( {detailModal.classroom?.roomType})
租赁方: {detailModal.tenant?.name}
联系人: {detailModal.tenant?.contact || '-'} {detailModal.tenant?.phone || ''}
起止日期: {detailModal.startDate} ~ {detailModal.endDate}( {dayjs(detailModal.endDate).diff(dayjs(detailModal.startDate), 'day') + 1}天)
{detailModal.dailyRate && (
日租金:¥{detailModal.dailyRate}
)} {detailModal.totalAmount && (
合同总额:¥{detailModal.totalAmount}
)} {detailModal.notes && (
备注: {detailModal.notes}
)}
合同文件: {detailModal.contractPath ? ( ) : ( '未上传' )}
)}
); }; export default ClassroomSchedulePage;