From bc5fa9a3664a57f05162b6f655afc782c8db3799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=E6=98=B5=E7=A7=B0=E9=87=8D=E8=A6=81=E5=90=97O?= Date: Sat, 14 Mar 2026 21:09:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(meeting):=20=E6=B7=BB=E5=8A=A0=E5=AD=A6?= =?UTF-8?q?=E7=94=9F=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD=E5=92=8C=E9=BB=91?= =?UTF-8?q?=E5=90=8D=E5=8D=95=E7=94=A8=E6=88=B7DTO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在MeetingController中添加BlacklistUserDto导入并移除内联定义 - 将BlacklistUserDto移动到meeting.dto.ts文件中并添加数据验证装饰器 - 在SaveStudentOrderDto中添加@IsNotEmpty和@IsArray验证规则 - 创建saveStudentOrderAxios API函数用于保存学生排序 - 更新MeetingRoomTeacher组件以处理新的API响应格式 - 使用vue-slicksort库替换原生拖拽实现学生排序功能 - 修改StudentSortDialog组件使用SlickList和SlickItem组件 - 通过API调用保存排序结果并添加成功提示 - 调整样式以适配新的排序组件结构 --- .../src/modules/meeting/meeting.controller.ts | 15 +-- node_api/src/modules/meeting/meeting.dto.ts | 15 +++ src/api/meeting.ts | 12 ++ .../meeting-room/meeting-room-teacher.vue | 3 +- .../meeting-room/student-sort-dialog.vue | 123 ++++-------------- 5 files changed, 55 insertions(+), 113 deletions(-) diff --git a/node_api/src/modules/meeting/meeting.controller.ts b/node_api/src/modules/meeting/meeting.controller.ts index 47fde9c..ab4c8f6 100644 --- a/node_api/src/modules/meeting/meeting.controller.ts +++ b/node_api/src/modules/meeting/meeting.controller.ts @@ -1,25 +1,14 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; -import { ApiParam, ApiProperty, ApiQuery, ApiTags } from '@nestjs/swagger'; +import { ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; import { FailResult, OkResult } from '../../common/dto/result.dto'; import { MeetingService } from './meeting.service'; import { NonEmptyStringPipe } from '@/common/pipes/non-empty-string.pipe'; import { Uint32Pipe } from '@/common/pipes/uint32.pipe'; import { ApiCustomOkResponse } from '@/common/decorators/swagger.decorator'; -import { SaveStudentOrderDto, StudentDetailResponseDto, TokenResponseDto } from './meeting.dto'; +import { BlacklistUserDto, SaveStudentOrderDto, StudentDetailResponseDto, TokenResponseDto } from './meeting.dto'; import { MeetingRedisService } from '../websocket/meeting-redis.service'; import { MeetingWebSocketGateway } from '../websocket/meeting.websocket'; -/** - * 黑名单用户 DTO - */ -class BlacklistUserDto { - @ApiProperty({ description: '短 UID', example: 1001 }) - shortUid?: number; - - @ApiProperty({ description: '用户名称', example: '张三' }) - userName?: string; -} - /** * 会议控制器 * 处理会议相关的 API 请求 diff --git a/node_api/src/modules/meeting/meeting.dto.ts b/node_api/src/modules/meeting/meeting.dto.ts index 6dbc7d1..0e4a93e 100644 --- a/node_api/src/modules/meeting/meeting.dto.ts +++ b/node_api/src/modules/meeting/meeting.dto.ts @@ -1,5 +1,16 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsString } from 'class-validator'; +/** + * 黑名单用户 DTO + */ +export class BlacklistUserDto { + @ApiProperty({ description: '短 UID', example: 1001 }) + shortUid?: number; + + @ApiProperty({ description: '用户名称', example: '张三' }) + userName?: string; +} /** * Token 响应数据 DTO */ @@ -33,8 +44,12 @@ export class StudentDetailResponseDto { */ export class SaveStudentOrderDto { @ApiProperty({ description: '房间 ID', example: 'room_123' }) + @IsNotEmpty({ message: 'roomId 不能为空' }) + @IsString({ message: 'roomId 必须是字符串' }) roomId!: string; @ApiProperty({ description: '学生长 ID 数组 (按排序顺序)', example: [1234567890, 1234567891, 1234567892] }) + @IsNotEmpty({ message: 'studentLongIds 不能为空' }) + @IsArray({ message: 'studentLongIds 必须是数组' }) studentLongIds!: number[]; } diff --git a/src/api/meeting.ts b/src/api/meeting.ts index 322d874..4c62092 100644 --- a/src/api/meeting.ts +++ b/src/api/meeting.ts @@ -49,6 +49,18 @@ export function getStudentDetailsAxios(homeworkId: number, roomId: string): Prom }); } +/** + * 保存学生排序顺序 + * @param roomId - 房间 ID + * @param studentLongIds - 学生长 ID 数组 (按排序顺序) + */ +export function saveStudentOrderAxios(roomId: string, studentLongIds: number[]): Promise> { + return axios.post>(`${import.meta.env.VITE_MEETING_BASE_URL}/meeting/student-order`, { + roomId, + studentLongIds, + }); +} + /** * 黑名单用户项 (包含短 UID 和名称) */ diff --git a/src/views/meeting/meeting-room/meeting-room-teacher.vue b/src/views/meeting/meeting-room/meeting-room-teacher.vue index edea6fc..b107c51 100644 --- a/src/views/meeting/meeting-room/meeting-room-teacher.vue +++ b/src/views/meeting/meeting-room/meeting-room-teacher.vue @@ -627,7 +627,8 @@ } try { const result = await getStudentDetailsAxios(homeworkId.value, roomId.value); - if (result.data) { + // 现在后端直接返回数组,不再包装在 data.students 中 + if (result.data && Array.isArray(result.data)) { studentListForSort.value = result.data; studentSortDialogVisible.value = true; } diff --git a/src/views/meeting/meeting-room/student-sort-dialog.vue b/src/views/meeting/meeting-room/student-sort-dialog.vue index 83cb3be..802ecb0 100644 --- a/src/views/meeting/meeting-room/student-sort-dialog.vue +++ b/src/views/meeting/meeting-room/student-sort-dialog.vue @@ -1,22 +1,15 @@