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 @@