diff --git a/apps/admin/src/pages/Classrooms/index.tsx b/apps/admin/src/pages/Classrooms/index.tsx index bd8485c..731a6b9 100644 --- a/apps/admin/src/pages/Classrooms/index.tsx +++ b/apps/admin/src/pages/Classrooms/index.tsx @@ -12,6 +12,7 @@ import { Tag, Popconfirm, Upload, + Tooltip, } from 'antd'; import { PlusOutlined, @@ -25,9 +26,18 @@ import PermissionButton from '../../components/PermissionButton'; const statusMap: Record = { available: { text: '可用', color: 'green' }, + in_use: { text: '使用中', color: 'blue' }, + maintenance: { text: '维护中', color: 'orange' }, archived: { text: '已归档', color: '#999' }, }; +interface CurrentUsage { + type: 'schedule' | 'rental'; + title: string; + startTime: string; + endTime: string; +} + const typeColor: Record = { 大: 'volcano', 次大: 'geekblue', @@ -142,7 +152,14 @@ const ClassroomsPage: React.FC = () => { { title: '状态', dataIndex: 'status', - render: (s: string) => {statusMap[s]?.text || s}, + render: (s: string, record: { currentUsage?: CurrentUsage | null }) => { + const effectiveStatus = record.currentUsage ? 'in_use' : s; + return ( + + {statusMap[effectiveStatus]?.text || s} + + ); + }, }, { title: '操作', diff --git a/apps/server/src/classrooms/classrooms.service.ts b/apps/server/src/classrooms/classrooms.service.ts index 2789acb..992604b 100644 --- a/apps/server/src/classrooms/classrooms.service.ts +++ b/apps/server/src/classrooms/classrooms.service.ts @@ -22,13 +22,16 @@ export class ClassroomsService { if (query?.building) where.building = query.building; if (query?.roomType) where.roomType = query.roomType; if (!query?.includeArchived) where.status = Not('archived'); - return this.repo.find({ where: await this.scope.filter(where), order: { building: 'ASC', name: 'ASC' } }); + const list = await this.repo.find({ where: await this.scope.filter(where), order: { building: 'ASC', name: 'ASC' } }); + const usageMap = await this.getCurrentUsageForClassrooms(list.map((c) => c.id)); + return list.map((c) => ({ ...c, currentUsage: usageMap.get(c.id) ?? null })); } async findOne(id: number) { const cls = await this.repo.findOne({ where: { id } }); if (!cls) throw new NotFoundException('教室不存在'); - return cls; + const usageMap = await this.getCurrentUsageForClassrooms([id]); + return { ...cls, currentUsage: usageMap.get(id) ?? null }; } async create(dto: CreateClassroomDto) { @@ -47,16 +50,83 @@ export class ClassroomsService { async remove(id: number) { await this.findOne(id); - await this.repo.update(id, { status: 'archived' } as any); + await this.repo.update(id, { status: 'archived' }); return { message: '已归档' }; } async restore(id: number) { await this.findOne(id); - await this.repo.update(id, { status: 'available' } as any); + await this.repo.update(id, { status: 'available' }); return this.repo.findOne({ where: { id } }); } + private async getCurrentUsageForClassrooms(classroomIds: number[]): Promise> { + const result = new Map(); + if (classroomIds.length === 0) return result; + + const now = new Date(); + const todayStr = now.toISOString().slice(0, 10); + const currentTime = now.toTimeString().slice(0, 5); + const weekDay = now.getDay() || 7; + + const schedules = await this.scheduleRepo + .createQueryBuilder('s') + .leftJoin('Class', 'c', 'c.id = s.classId') + .select('s.classroomId', 'classroomId') + .addSelect('s.startTime', 'startTime') + .addSelect('s.endTime', 'endTime') + .addSelect('s.subject', 'subject') + .addSelect('c.name', 'className') + .where('s.classroomId IN (:...ids)', { ids: classroomIds }) + .andWhere('s.status = :active', { active: 'active' }) + .andWhere('s.scheduleType = :type', { type: 'INTERNAL' }) + .andWhere('s.startDate <= :today', { today: todayStr }) + .andWhere('s.endDate >= :today', { today: todayStr }) + .andWhere('s.weekDay = :weekDay', { weekDay }) + .andWhere('s.startTime <= :currentTime', { currentTime }) + .andWhere('s.endTime >= :currentTime', { currentTime }) + .getRawMany(); + + for (const s of schedules) { + const classroomId = Number(s.classroomId); + if (!result.has(classroomId)) { + result.set(classroomId, { + type: 'schedule', + title: `${s.className || ''} ${s.subject || ''}`.trim() || '内部课程', + startTime: String(s.startTime), + endTime: String(s.endTime), + }); + } + } + + const rentals = await this.rentalRepo + .createQueryBuilder('r') + .leftJoin('Tenant', 't', 't.id = r.tenantId') + .select('r.classroomId', 'classroomId') + .addSelect('r.startDate', 'startDate') + .addSelect('r.endDate', 'endDate') + .addSelect('t.name', 'tenantName') + .where('r.classroomId IN (:...ids)', { ids: classroomIds }) + .andWhere('r.status != :cancelled', { cancelled: 'cancelled' }) + .andWhere('r.startDate <= :today', { today: todayStr }) + .andWhere('r.endDate >= :today', { today: todayStr }) + .getRawMany(); + + for (const r of rentals) { + const classroomId = Number(r.classroomId); + if (!result.has(classroomId)) { + result.set(classroomId, { + type: 'rental', + title: r.tenantName ? `${r.tenantName} 租赁` : '外部租赁', + startTime: '00:00', + endTime: '23:59', + }); + } + } + + return result; + } + async batchImport( rows: { name: string;