From 7800951aee6a812fa38ae937f8ee890368672ba7 Mon Sep 17 00:00:00 2001 From: biss Date: Sat, 25 Jul 2026 13:21:06 +0800 Subject: [PATCH] =?UTF-8?q?deepseek=20=E5=89=8D=E5=8F=B0=20=E2=80=94=20?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=20(router/index.ts)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - my-timetable 路由的角色从 ['Student'] 扩展为 ['Student', 'Teacher'] - 新增 teacher-timetable/:teacherId 路由,用于查看指定教师的课表 前台 — 课表视图 (TimetableView.vue) - 新增 isTeacherView 和 isTeacher 计算属性,区分教师个人课表和查看他人课表 - 教师个人课表:标题显示"我的授课课表",描述改为"展示本人本学期所有授课安排" - 查看他人课表:标题显示"教师课表" - 两种模式均隐藏资源筛选面板,只保留学期选择 - 加载逻辑适配教师模式 API 前台 — 教师档案页 (PersonnelView.vue) - 教师行操作列新增 "课表" 按钮(带日历图标),仅在职教师可点击 - 点击后跳转到该教师的授课课表页面 前台 — 侧边导航 (AdminLayout.vue) - 教师角色可见"我的授课课表"菜单项,与学生的"我的课表"并列 --- .../Controllers/TimetablesController.cs | 138 ++++++++++++++---- web/src/layouts/AdminLayout.vue | 2 +- web/src/router/index.ts | 8 +- web/src/views/PersonnelView.vue | 21 ++- web/src/views/TimetableView.vue | 39 ++++- 5 files changed, 171 insertions(+), 37 deletions(-) diff --git a/src/Jiaowu.Api/Controllers/TimetablesController.cs b/src/Jiaowu.Api/Controllers/TimetablesController.cs index 37aeefd..5127fda 100644 --- a/src/Jiaowu.Api/Controllers/TimetablesController.cs +++ b/src/Jiaowu.Api/Controllers/TimetablesController.cs @@ -91,6 +91,45 @@ public sealed class TimetablesController( CancellationToken cancellationToken) => BuildTimetableAsync(classId, academicTermId, null, cancellationToken); + [HttpGet("teachers/{teacherId:guid}")] + [AllowAnonymous] + public async Task GetTeacherTimetable( + Guid teacherId, + Guid? academicTermId, + CancellationToken cancellationToken) + { + var result = await timetableDataService.BuildAsync( + TimetableResourceType.Teacher, + teacherId, + academicTermId, + null, + false, + null, + null, + cancellationToken); + return result is null ? NotFound() : Ok(result); + } + + [HttpGet("teachers/{teacherId:guid}/export.xlsx")] + [AllowAnonymous] + public async Task ExportTeacherTimetable( + Guid teacherId, + Guid? academicTermId, + CancellationToken cancellationToken) + { + var result = await timetableDataService.BuildAsync( + TimetableResourceType.Teacher, + teacherId, + academicTermId, + null, + false, + null, + null, + cancellationToken); + if (result is null) return NotFound(); + return ExcelFile(result); + } + [HttpGet("classes/{classId:guid}/export.xlsx")] [AllowAnonymous] public async Task ExportClassTimetable( @@ -112,13 +151,14 @@ public sealed class TimetablesController( } [HttpGet("mine")] - [Authorize(Roles = SystemRoles.Student)] + [Authorize(Roles = SystemRoles.Student + "," + SystemRoles.Teacher)] public async Task GetMyTimetable( Guid? academicTermId, CancellationToken cancellationToken) { if (!Guid.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out var userId)) return Unauthorized(); + var student = await db.Students.AsNoTracking() .Where(x => x.UserId == userId) .Select(x => new @@ -129,30 +169,51 @@ public sealed class TimetablesController( x.Name }) .FirstOrDefaultAsync(cancellationToken); - if (student is null) - return Conflict(new ProblemDetails - { - Title = "学生档案未关联", - Detail = "当前登录账号没有关联学生档案,请联系教务管理员。", - Status = StatusCodes.Status409Conflict - }); + if (student is not null) + { + return await BuildTimetableAsync( + student.AdministrativeClassId, + academicTermId, + student.Id, + cancellationToken, + new TimetableStudentDto(student.StudentNumber, student.Name)); + } - return await BuildTimetableAsync( - student.AdministrativeClassId, - academicTermId, - student.Id, - cancellationToken, - new TimetableStudentDto(student.StudentNumber, student.Name)); + var teacher = await db.Teachers.AsNoTracking() + .Where(x => x.UserId == userId && x.Status == TeacherStatus.Active) + .Select(x => new { x.Id }) + .FirstOrDefaultAsync(cancellationToken); + if (teacher is not null) + { + var result = await timetableDataService.BuildAsync( + TimetableResourceType.Teacher, + teacher.Id, + academicTermId, + null, + false, + null, + null, + cancellationToken); + return result is null ? NotFound() : Ok(result); + } + + return Conflict(new ProblemDetails + { + Title = "档案未关联", + Detail = "当前登录账号没有关联教师或学生档案,请联系教务管理员。", + Status = StatusCodes.Status409Conflict + }); } [HttpGet("mine/export.xlsx")] - [Authorize(Roles = SystemRoles.Student)] + [Authorize(Roles = SystemRoles.Student + "," + SystemRoles.Teacher)] public async Task ExportMyTimetable( Guid? academicTermId, CancellationToken cancellationToken) { if (!Guid.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out var userId)) return Unauthorized(); + var student = await db.Students.AsNoTracking() .Where(x => x.UserId == userId) .Select(x => new @@ -163,18 +224,41 @@ public sealed class TimetablesController( x.Name }) .FirstOrDefaultAsync(cancellationToken); - if (student is null) return NotFound(); - var result = await timetableDataService.BuildAsync( - TimetableResourceType.Class, - student.AdministrativeClassId, - academicTermId, - null, - false, - student.Id, - new TimetableStudentDto(student.StudentNumber, student.Name), - cancellationToken); - if (result is null) return NotFound(); - return ExcelFile(result); + if (student is not null) + { + var result = await timetableDataService.BuildAsync( + TimetableResourceType.Class, + student.AdministrativeClassId, + academicTermId, + null, + false, + student.Id, + new TimetableStudentDto(student.StudentNumber, student.Name), + cancellationToken); + if (result is null) return NotFound(); + return ExcelFile(result); + } + + var teacher = await db.Teachers.AsNoTracking() + .Where(x => x.UserId == userId && x.Status == TeacherStatus.Active) + .Select(x => new { x.Id }) + .FirstOrDefaultAsync(cancellationToken); + if (teacher is not null) + { + var result = await timetableDataService.BuildAsync( + TimetableResourceType.Teacher, + teacher.Id, + academicTermId, + null, + false, + null, + null, + cancellationToken); + if (result is null) return NotFound(); + return ExcelFile(result); + } + + return NotFound(); } private async Task BuildTimetableAsync( diff --git a/web/src/layouts/AdminLayout.vue b/web/src/layouts/AdminLayout.vue index 668800f..7f531e9 100644 --- a/web/src/layouts/AdminLayout.vue +++ b/web/src/layouts/AdminLayout.vue @@ -104,7 +104,7 @@ const navigationGroups = computed(() => [ hasAnyRole(['SuperAdmin', 'AcademicAdmin']), { path: '/schedules', label: '排课与课表' }, ), - ...whenVisible(isStudent.value, { path: '/my-timetable', label: '我的课表' }), + ...whenVisible(isStudent.value || isTeacher.value, { path: '/my-timetable', label: isTeacher.value ? '我的授课课表' : '我的课表' }), ...whenVisible(isStudent.value, { path: '/free-classrooms', label: '空闲教室' }), ...whenVisible(!isStudent.value, { path: '/class-timetable', diff --git a/web/src/router/index.ts b/web/src/router/index.ts index 7c5a574..9e33534 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -141,7 +141,13 @@ const router = createRouter({ path: 'my-timetable', name: 'my-timetable', component: () => import('../views/TimetableView.vue'), - meta: { roles: ['Student'], mine: true }, + meta: { roles: ['Student', 'Teacher'], mine: true }, + }, + { + path: 'teacher-timetable/:teacherId', + name: 'teacher-timetable', + component: () => import('../views/TimetableView.vue'), + meta: { teacherView: true }, }, { path: 'free-classrooms', diff --git a/web/src/views/PersonnelView.vue b/web/src/views/PersonnelView.vue index 66149a4..00aca18 100644 --- a/web/src/views/PersonnelView.vue +++ b/web/src/views/PersonnelView.vue @@ -1,7 +1,7 @@