前台 — 路由 (router/index.ts)

  - my-timetable 路由的角色从 ['Student'] 扩展为 ['Student', 'Teacher']
  - 新增 teacher-timetable/:teacherId 路由,用于查看指定教师的课表

  前台 — 课表视图 (TimetableView.vue)

  - 新增 isTeacherView 和 isTeacher 计算属性,区分教师个人课表和查看他人课表
  - 教师个人课表:标题显示"我的授课课表",描述改为"展示本人本学期所有授课安排"
  - 查看他人课表:标题显示"教师课表"
  - 两种模式均隐藏资源筛选面板,只保留学期选择
  - 加载逻辑适配教师模式 API

  前台 — 教师档案页 (PersonnelView.vue)

  - 教师行操作列新增 "课表" 按钮(带日历图标),仅在职教师可点击
  - 点击后跳转到该教师的授课课表页面

  前台 — 侧边导航 (AdminLayout.vue)

  - 教师角色可见"我的授课课表"菜单项,与学生的"我的课表"并列
This commit is contained in:
2026-07-25 13:21:06 +08:00 Unverified
parent a081273ac6
commit 7800951aee
5 changed files with 171 additions and 37 deletions
@@ -91,6 +91,45 @@ public sealed class TimetablesController(
CancellationToken cancellationToken) =>
BuildTimetableAsync(classId, academicTermId, null, cancellationToken);
[HttpGet("teachers/{teacherId:guid}")]
[AllowAnonymous]
public async Task<ActionResult> 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<ActionResult> 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<ActionResult> ExportClassTimetable(
@@ -112,13 +151,14 @@ public sealed class TimetablesController(
}
[HttpGet("mine")]
[Authorize(Roles = SystemRoles.Student)]
[Authorize(Roles = SystemRoles.Student + "," + SystemRoles.Teacher)]
public async Task<ActionResult> 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<ActionResult> 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<ActionResult> BuildTimetableAsync(