diff --git a/apps/server/src/classrooms/classrooms.controller.ts b/apps/server/src/classrooms/classrooms.controller.ts index 3dbfa05..0c4a932 100644 --- a/apps/server/src/classrooms/classrooms.controller.ts +++ b/apps/server/src/classrooms/classrooms.controller.ts @@ -12,6 +12,7 @@ import { Res, UseInterceptors, UploadedFile, + BadRequestException, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import type { Response } from 'express'; @@ -112,6 +113,38 @@ export class ClassroomsController { res.end(); } + @Get('report') + @RequirePermission('classroom:view') + async exportReport( + @Query('dateFrom') dateFrom: string, + @Query('dateTo') dateTo: string, + @Res() res: Response, + ) { + const data = await this.service.getUsageReport(dateFrom, dateTo); + const workbook = new ExcelJS.Workbook(); + const ws = workbook.addWorksheet('教室使用统计'); + ws.columns = [ + { header: '教室名称', key: 'name', width: 20 }, + { header: '楼栋', key: 'building', width: 12 }, + { header: '类型', key: 'roomType', width: 12 }, + { header: '容量', key: 'capacity', width: 8 }, + { header: '统计天数', key: 'totalDays', width: 10 }, + { header: '租赁占用天数', key: 'rentalDays', width: 14 }, + { header: '排课占用天数', key: 'scheduleDays', width: 14 }, + { header: '空闲天数', key: 'idleDays', width: 10 }, + { header: '占用率', key: 'occupancyRate', width: 10 }, + ]; + ws.getRow(1).font = { bold: true }; + ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } }; + for (const row of data) { + ws.addRow({ ...row, occupancyRate: `${row.occupancyRate}%` }); + } + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', 'attachment; filename=classroom-report.xlsx'); + await workbook.xlsx.write(res); + res.end(); + } + @Get(':id') @RequirePermission('classroom:view') findOne(@Param('id') id: string) { @@ -170,8 +203,9 @@ export class ClassroomsController { @Post('import') @RequirePermission('classroom:create') - @UseInterceptors(FileInterceptor('file')) + @UseInterceptors(FileInterceptor('file', { limits: { fileSize: 10 * 1024 * 1024 } })) async importExcel(@UploadedFile() file: Express.Multer.File, @Request() req: AuthenticatedRequest) { + if (!file) throw new BadRequestException('缺少上传文件'); const { ipAddress, userAgent } = extractRequestInfo(req); const workbook = new ExcelJS.Workbook(); await workbook.xlsx.load(bufferToArrayBuffer(file.buffer)); @@ -205,36 +239,4 @@ export class ClassroomsController { }); return result; } - - @Get('report') - @RequirePermission('classroom:view') - async exportReport( - @Query('dateFrom') dateFrom: string, - @Query('dateTo') dateTo: string, - @Res() res: Response, - ) { - const data = await this.service.getUsageReport(dateFrom, dateTo); - const workbook = new ExcelJS.Workbook(); - const ws = workbook.addWorksheet('教室使用统计'); - ws.columns = [ - { header: '教室名称', key: 'name', width: 20 }, - { header: '楼栋', key: 'building', width: 12 }, - { header: '类型', key: 'roomType', width: 12 }, - { header: '容量', key: 'capacity', width: 8 }, - { header: '统计天数', key: 'totalDays', width: 10 }, - { header: '租赁占用天数', key: 'rentalDays', width: 14 }, - { header: '排课占用天数', key: 'scheduleDays', width: 14 }, - { header: '空闲天数', key: 'idleDays', width: 10 }, - { header: '占用率', key: 'occupancyRate', width: 10 }, - ]; - ws.getRow(1).font = { bold: true }; - ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } }; - for (const row of data) { - ws.addRow({ ...row, occupancyRate: `${row.occupancyRate}%` }); - } - res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - res.setHeader('Content-Disposition', 'attachment; filename=classroom-report.xlsx'); - await workbook.xlsx.write(res); - res.end(); - } } diff --git a/apps/server/src/classrooms/classrooms.service.ts b/apps/server/src/classrooms/classrooms.service.ts index 215062a..dd39e56 100644 --- a/apps/server/src/classrooms/classrooms.service.ts +++ b/apps/server/src/classrooms/classrooms.service.ts @@ -7,6 +7,7 @@ import { ClassSchedule } from '../entities/class-schedule.entity'; import { AttendanceDevice } from '../entities/attendance-device.entity'; import { CreateClassroomDto, UpdateClassroomDto } from './dto/classroom.dto'; import dayjs from '../common/dayjs'; +import { escapeLike } from '../common/like-escape'; /** getRawMany 原始行:驱动可能返回 string/number,date 列可能是 string 或 Date */ interface ScheduleUsageRawRow { @@ -72,7 +73,7 @@ export class ClassroomsService { > { const where: Record = { status: Not('archived') }; if (query?.building) where.building = query.building; - if (query?.keyword) where.name = Like(`%${query.keyword}%`); + if (query?.keyword) where.name = Like(`%${escapeLike(query.keyword)}%`); const list = await this.repo.find({ where, order: { building: 'ASC', name: 'ASC' },