feat(task1): restructure directories for turborepo monorepo
- Move backend/ to apps/server/ via git mv - Move frontend/ to apps/admin/ via git mv - Create packages/typescript-config/ with base, nestjs, and react-vite presets
This commit is contained in:
132
apps/server/src/classrooms/classrooms.controller.ts
Normal file
132
apps/server/src/classrooms/classrooms.controller.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards, Request, Res, UseInterceptors, UploadedFile } from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import type { Response } from 'express';
|
||||
import { ClassroomsService } from './classrooms.service';
|
||||
import { CreateClassroomDto, UpdateClassroomDto } from './dto/classroom.dto';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
||||
import { extractRequestInfo } from '../common/request-utils';
|
||||
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
||||
import * as ExcelJS from 'exceljs';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('classrooms')
|
||||
export class ClassroomsController {
|
||||
constructor(private service: ClassroomsService, private logService: OperationLogsService) {}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('classroom:view')
|
||||
findAll(@Query('building') building?: string, @Query('roomType') roomType?: string, @Query('includeArchived') includeArchived?: string) {
|
||||
return this.service.findAll({
|
||||
building,
|
||||
roomType,
|
||||
includeArchived: includeArchived === 'true',
|
||||
});
|
||||
}
|
||||
|
||||
@Get('template')
|
||||
@RequirePermission('classroom:view')
|
||||
async downloadTemplate(@Res() res: Response) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const ws = workbook.addWorksheet('教室导入模板');
|
||||
ws.columns = [
|
||||
{ header: '教室名', key: 'name', width: 15 },
|
||||
{ header: '楼栋', key: 'building', width: 12 },
|
||||
{ header: '楼层', key: 'floor', width: 8 },
|
||||
{ header: '类型', key: 'roomType', width: 10 },
|
||||
{ header: '容量', key: 'capacity', width: 10 },
|
||||
{ header: '课程类型', key: 'courseType', width: 16 },
|
||||
{ header: '负责人', key: 'supervisor', width: 12 },
|
||||
];
|
||||
ws.getRow(1).font = { bold: true };
|
||||
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
||||
ws.addRow({ name: 'A201', building: 'A座', floor: 2, roomType: '大', capacity: 60, courseType: '尊享培优班', supervisor: '张老师' });
|
||||
ws.addRow({ name: 'B301', building: 'B座', floor: 3, roomType: '次大', capacity: 40, courseType: '专业课集训班', supervisor: '李老师' });
|
||||
ws.addRow({ name: 'B405', building: 'B座', floor: 4, roomType: '小', capacity: 20, courseType: '', supervisor: '' });
|
||||
|
||||
// 说明sheet
|
||||
const ws2 = workbook.addWorksheet('使用说明');
|
||||
ws2.columns = [{ header: '说明', key: 'note', width: 80 }];
|
||||
ws2.getRow(1).font = { bold: true };
|
||||
[
|
||||
'1. 教室名必填,建议采用「楼栋+房号」如 A201、B301',
|
||||
'2. 类型可填 大 / 次大 / 小,为空默认「大」',
|
||||
'3. 同名教室会自动跳过(不覆盖)',
|
||||
'4. 课程类型可填尊享培优班、专业课集训班等产品班级',
|
||||
'5. 负责人为班主任/对接人',
|
||||
].forEach((note) => ws2.addRow({ note }));
|
||||
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=classroom_template.xlsx');
|
||||
await workbook.xlsx.write(res);
|
||||
res.end();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RequirePermission('classroom:view')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.service.findOne(+id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('classroom:create')
|
||||
async create(@Body() dto: CreateClassroomDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.create(dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '教室', action: '新增教室', targetId: result.id, targetType: 'classroom', detail: dto.name, ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@RequirePermission('classroom:edit')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateClassroomDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.update(+id, dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '教室', action: '编辑教室', targetId: +id, targetType: 'classroom', detail: JSON.stringify(dto), ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('classroom:delete')
|
||||
async remove(@Param('id') id: string, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.remove(+id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '教室', action: '归档教室', targetId: +id, targetType: 'classroom', ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put(':id/restore')
|
||||
@RequirePermission('classroom:edit')
|
||||
async restore(@Param('id') id: string, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.restore(+id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '教室', action: '恢复教室', targetId: +id, targetType: 'classroom', ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('import')
|
||||
@RequirePermission('classroom:create')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async importExcel(@UploadedFile() file: Express.Multer.File, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.load(file.buffer as any);
|
||||
const ws = workbook.worksheets[0];
|
||||
const rows: any[] = [];
|
||||
ws.eachRow((row, idx) => {
|
||||
if (idx === 1) return;
|
||||
rows.push({
|
||||
name: String(row.getCell(1).value || ''),
|
||||
building: String(row.getCell(2).value || '') || undefined,
|
||||
floor: Number(row.getCell(3).value) || undefined,
|
||||
roomType: String(row.getCell(4).value || '') || undefined,
|
||||
capacity: Number(row.getCell(5).value) || undefined,
|
||||
courseType: String(row.getCell(6).value || '') || undefined,
|
||||
supervisor: String(row.getCell(7).value || '') || undefined,
|
||||
});
|
||||
});
|
||||
const result = await this.service.batchImport(rows);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '教室', action: '批量导入', detail: result.message, ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user