forked from wangziqi/gongxue-base
160 lines
7.7 KiB
TypeScript
160 lines
7.7 KiB
TypeScript
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 { RoomsService } from './rooms.service';
|
|
import { CreateRoomDto, UpdateRoomDto } from './dto/room.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('rooms')
|
|
export class RoomsController {
|
|
constructor(private service: RoomsService, private logService: OperationLogsService) {}
|
|
|
|
@Get()
|
|
@RequirePermission('room:view')
|
|
findAll(@Query('building') building?: string, @Query('includeArchived') includeArchived?: string) {
|
|
return this.service.findAll({ building, includeArchived: includeArchived === 'true' });
|
|
}
|
|
|
|
@Get('overview')
|
|
@RequirePermission('room:view')
|
|
getOverview(@Query('includeArchived') includeArchived?: string) {
|
|
return this.service.getRoomOverview({ includeArchived: includeArchived === 'true' });
|
|
}
|
|
|
|
@Get('visual')
|
|
@RequirePermission('room:view')
|
|
getVisual() {
|
|
return this.service.getRoomVisual();
|
|
}
|
|
|
|
@Get('template')
|
|
@RequirePermission('room:view')
|
|
async downloadTemplate(@Res() res: Response) {
|
|
const workbook = new ExcelJS.Workbook();
|
|
const ws = workbook.addWorksheet('宿舍导入模板');
|
|
ws.columns = [
|
|
{ header: '房间号', key: 'roomNumber', width: 12 },
|
|
{ header: '楼栋', key: 'building', width: 12 },
|
|
{ header: '楼层', key: 'floor', width: 8 },
|
|
{ header: '额定人数', key: 'capacity', width: 10 },
|
|
{ header: '宿舍类型', key: 'roomType', width: 12 },
|
|
];
|
|
ws.getRow(1).font = { bold: true };
|
|
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
|
ws.addRow({ roomNumber: '4-102', building: '4号楼', floor: 1, capacity: 4, roomType: '四人间' });
|
|
ws.addRow({ roomNumber: '2-201', building: '2号楼', floor: 2, capacity: 1, roomType: '单人间' });
|
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
res.setHeader('Content-Disposition', 'attachment; filename=room_template.xlsx');
|
|
await workbook.xlsx.write(res);
|
|
res.end();
|
|
}
|
|
|
|
@Get('export')
|
|
@RequirePermission('room:view')
|
|
async exportExcel(@Query('includeArchived') includeArchived?: string, @Res() res?: Response) {
|
|
const rooms = await this.service.getRoomOverview({ includeArchived: includeArchived === 'true' });
|
|
const workbook = new ExcelJS.Workbook();
|
|
const ws = workbook.addWorksheet('宿舍列表');
|
|
ws.columns = [
|
|
{ header: '房间号', key: 'roomNumber', width: 12 },
|
|
{ header: '楼栋', key: 'building', width: 12 },
|
|
{ header: '楼层', key: 'floor', width: 8 },
|
|
{ header: '宿舍类型', key: 'roomType', width: 12 },
|
|
{ header: '额定人数', key: 'capacity', width: 10 },
|
|
{ header: '当前入住', key: 'currentCount', width: 10 },
|
|
{ header: '性别', key: 'gender', width: 8 },
|
|
{ header: '状态', key: 'status', width: 10 },
|
|
];
|
|
ws.getRow(1).font = { bold: true };
|
|
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
|
const statusMap: Record<string, string> = { available: '可入住', full: '已满', maintenance: '维修中', archived: '已归档' };
|
|
for (const r of rooms) {
|
|
ws.addRow({ roomNumber: r.roomNumber, building: r.building || '', floor: r.floor || '', roomType: r.roomType || '', capacity: r.capacity, currentCount: r.currentCount, gender: r.gender || '', status: statusMap[r.status] || r.status });
|
|
}
|
|
res!.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
res!.setHeader('Content-Disposition', 'attachment; filename=rooms.xlsx');
|
|
await workbook.xlsx.write(res!);
|
|
res!.end();
|
|
}
|
|
|
|
@Get(':id')
|
|
@RequirePermission('room:view')
|
|
findOne(@Param('id') id: string) {
|
|
return this.service.findOneWithOccupants(+id);
|
|
}
|
|
|
|
@Post()
|
|
@RequirePermission('room:create')
|
|
async create(@Body() dto: CreateRoomDto, @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: '添加宿舍', detail: `房间号: ${dto.roomNumber}, 楼栋: ${dto.building || '无'}, 额定: ${dto.capacity}人`, ipAddress, userAgent });
|
|
return result;
|
|
}
|
|
|
|
@Put(':id')
|
|
@RequirePermission('room:edit')
|
|
async update(@Param('id') id: string, @Body() dto: UpdateRoomDto, @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: 'room', detail: JSON.stringify(dto), ipAddress, userAgent });
|
|
return result;
|
|
}
|
|
|
|
@Delete(':id')
|
|
@RequirePermission('room: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: 'room', ipAddress, userAgent });
|
|
return result;
|
|
}
|
|
|
|
@Post('batch-delete')
|
|
@RequirePermission('room:delete')
|
|
async batchRemove(@Body() body: { ids: number[] }, @Request() req: any) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.service.batchRemove(body.ids || []);
|
|
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '宿舍', action: '批量归档宿舍', detail: `IDs: ${(body.ids || []).join(',')}`, ipAddress, userAgent });
|
|
return result;
|
|
}
|
|
|
|
@Put(':id/restore')
|
|
@RequirePermission('room: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: 'room', ipAddress, userAgent });
|
|
return result;
|
|
}
|
|
|
|
@Post('import')
|
|
@RequirePermission('room: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: { roomNumber: string; building?: string; floor?: number; capacity?: number; roomType?: string }[] = [];
|
|
ws.eachRow((row, idx) => {
|
|
if (idx === 1) return;
|
|
rows.push({
|
|
roomNumber: String(row.getCell(1).value || ''),
|
|
building: String(row.getCell(2).value || '') || undefined,
|
|
floor: Number(row.getCell(3).value) || undefined,
|
|
capacity: Number(row.getCell(4).value) || 4,
|
|
roomType: String(row.getCell(5).value || '').trim() || 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;
|
|
}
|
|
}
|