499 lines
24 KiB
C#
499 lines
24 KiB
C#
using Jiaowu.Api.Domain.Academic;
|
||
using Jiaowu.Api.Domain.Identity;
|
||
using Jiaowu.Api.Infrastructure.Auth;
|
||
using Jiaowu.Api.Infrastructure.Persistence;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.EntityFrameworkCore;
|
||
|
||
namespace Jiaowu.Api.Controllers;
|
||
|
||
[ApiController]
|
||
[Authorize]
|
||
[Route("api/dashboard")]
|
||
public sealed class DashboardController(
|
||
AppDbContext db,
|
||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||
{
|
||
[HttpGet]
|
||
public async Task<ActionResult<DashboardResponse>> Get(
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var scope = currentUserDataScope.Current;
|
||
Guid? restrictedCollegeId = scope.Scope == DataScope.All
|
||
? null
|
||
: scope.CollegeId ?? Guid.Empty;
|
||
var collegeName = restrictedCollegeId.HasValue &&
|
||
restrictedCollegeId.Value != Guid.Empty
|
||
? await db.Colleges.AsNoTracking()
|
||
.Where(x => x.Id == restrictedCollegeId.Value)
|
||
.Select(x => x.Name)
|
||
.FirstOrDefaultAsync(cancellationToken)
|
||
: null;
|
||
|
||
var currentTerm = await db.AcademicTerms
|
||
.AsNoTracking()
|
||
.Where(x => x.IsCurrent)
|
||
.Select(x => new DashboardTerm(
|
||
x.Id,
|
||
x.Name,
|
||
x.StartDate,
|
||
x.EndDate))
|
||
.FirstOrDefaultAsync(cancellationToken);
|
||
var currentTermId = currentTerm?.Id;
|
||
|
||
var students = db.Students.AsNoTracking()
|
||
.Where(x =>
|
||
!restrictedCollegeId.HasValue ||
|
||
x.AdministrativeClass!.Major!.CollegeId ==
|
||
restrictedCollegeId.Value);
|
||
var teachers = db.Teachers.AsNoTracking()
|
||
.Where(x =>
|
||
!restrictedCollegeId.HasValue ||
|
||
x.CollegeId == restrictedCollegeId.Value);
|
||
var courses = db.Courses.AsNoTracking()
|
||
.Where(x =>
|
||
!restrictedCollegeId.HasValue ||
|
||
x.CollegeId == restrictedCollegeId.Value);
|
||
var teachingTasks = db.TeachingTasks.AsNoTracking()
|
||
.Where(x =>
|
||
currentTermId.HasValue &&
|
||
x.AcademicTermId == currentTermId.Value &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.Course!.CollegeId == restrictedCollegeId.Value));
|
||
var gradeSheets = db.GradeSheets.AsNoTracking()
|
||
.Where(x =>
|
||
currentTermId.HasValue &&
|
||
x.TeachingTask!.AcademicTermId == currentTermId.Value &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.TeachingTask.Course!.CollegeId ==
|
||
restrictedCollegeId.Value));
|
||
var enrollments = db.CourseEnrollments.AsNoTracking()
|
||
.Where(x =>
|
||
currentTermId.HasValue &&
|
||
x.Status == CourseEnrollmentStatus.Enrolled &&
|
||
x.CourseSelectionOffering!.CourseSelectionRound!
|
||
.AcademicTermId == currentTermId.Value &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.CourseSelectionOffering.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value));
|
||
|
||
var taskCount = await teachingTasks.CountAsync(cancellationToken);
|
||
var publishedTaskCount = await teachingTasks.CountAsync(
|
||
x => x.Status == TeachingTaskStatus.Published,
|
||
cancellationToken);
|
||
var scheduledTaskCount = currentTermId.HasValue
|
||
? await db.ScheduleEntries.AsNoTracking()
|
||
.Where(x =>
|
||
x.SchedulePlan!.AcademicTermId == currentTermId.Value &&
|
||
x.SchedulePlan.Status == SchedulePlanStatus.Published &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value))
|
||
.Select(x => x.TeachingTaskId)
|
||
.Distinct()
|
||
.CountAsync(cancellationToken)
|
||
: 0;
|
||
var gradeSheetCount = await gradeSheets.CountAsync(cancellationToken);
|
||
var publishedGradeSheetCount = await gradeSheets.CountAsync(
|
||
x => x.Status == GradeSheetStatus.Published,
|
||
cancellationToken);
|
||
|
||
var counts = new DashboardCounts(
|
||
await students.CountAsync(
|
||
x => x.Status == StudentStatus.Active,
|
||
cancellationToken),
|
||
await teachers.CountAsync(
|
||
x => x.Status == TeacherStatus.Active,
|
||
cancellationToken),
|
||
await courses.CountAsync(
|
||
x => x.IsEnabled,
|
||
cancellationToken),
|
||
taskCount,
|
||
publishedTaskCount,
|
||
scheduledTaskCount,
|
||
await enrollments.CountAsync(cancellationToken),
|
||
gradeSheetCount,
|
||
publishedGradeSheetCount,
|
||
await gradeSheets.CountAsync(
|
||
x => x.Status == GradeSheetStatus.Submitted,
|
||
cancellationToken),
|
||
currentTermId.HasValue
|
||
? await db.CourseSelectionRounds.AsNoTracking().CountAsync(
|
||
x => x.AcademicTermId == currentTermId.Value &&
|
||
x.Status == CourseSelectionRoundStatus.Open,
|
||
cancellationToken)
|
||
: 0);
|
||
|
||
var pending = await LoadPendingAsync(
|
||
scope,
|
||
restrictedCollegeId,
|
||
cancellationToken);
|
||
|
||
return Ok(new DashboardResponse(
|
||
BuildAudience(scope, collegeName),
|
||
currentTerm,
|
||
counts,
|
||
pending,
|
||
await BuildGreetingAsync(scope, currentTermId, counts, pending, cancellationToken),
|
||
DateTime.UtcNow));
|
||
}
|
||
|
||
[HttpGet("greeting")]
|
||
public async Task<ActionResult<DashboardGreeting>> GetGreeting(
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var currentTermId = await db.AcademicTerms.AsNoTracking()
|
||
.Where(x => x.IsCurrent)
|
||
.Select(x => (Guid?)x.Id)
|
||
.FirstOrDefaultAsync(cancellationToken);
|
||
|
||
return Ok(await BuildGreetingAsync(
|
||
currentUserDataScope.Current,
|
||
currentTermId,
|
||
null,
|
||
null,
|
||
cancellationToken));
|
||
}
|
||
|
||
private async Task<DashboardGreeting> BuildGreetingAsync(
|
||
CurrentUserScope scope,
|
||
Guid? currentTermId,
|
||
DashboardCounts? counts,
|
||
DashboardPending? pending,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var name = string.IsNullOrWhiteSpace(scope.DisplayName) ? "" : $"{scope.DisplayName},";
|
||
var greeting = GetTimeGreeting();
|
||
var isManager = scope.IsInRole(SystemRoles.SuperAdmin) ||
|
||
scope.IsInRole(SystemRoles.AcademicAdmin) ||
|
||
scope.IsInRole(SystemRoles.CollegeAdmin) ||
|
||
scope.IsInRole(SystemRoles.Leader) ||
|
||
scope.IsInRole(SystemRoles.Counselor);
|
||
|
||
if (!isManager && scope.IsInRole(SystemRoles.Student))
|
||
{
|
||
var studentId = await db.Students.AsNoTracking()
|
||
.Where(x => x.UserId == scope.UserId)
|
||
.Select(x => (Guid?)x.Id)
|
||
.FirstOrDefaultAsync(cancellationToken);
|
||
if (!studentId.HasValue)
|
||
return new DashboardGreeting("Student", $"{name}{greeting}", "绑定学籍后,将为你生成课程与成绩学习概览。", "学习节奏", "关联学生档案后,可从课程安排、成绩和考试中生成学习状态摘要。", []);
|
||
|
||
var student = await db.Students.AsNoTracking()
|
||
.Where(x => x.Id == studentId.Value)
|
||
.Select(x => new { x.AdministrativeClassId })
|
||
.FirstAsync(cancellationToken);
|
||
var currentTasks = db.TeachingTasks.AsNoTracking().Where(task =>
|
||
currentTermId.HasValue &&
|
||
task.AcademicTermId == currentTermId.Value &&
|
||
task.Status == TeachingTaskStatus.Published &&
|
||
(task.Classes.Any(item =>
|
||
item.AdministrativeClassId == student.AdministrativeClassId) ||
|
||
db.CourseEnrollments.Any(enrollment =>
|
||
enrollment.StudentId == studentId.Value &&
|
||
enrollment.Status == CourseEnrollmentStatus.Enrolled &&
|
||
enrollment.CourseSelectionOffering!.TeachingTaskId == task.Id)));
|
||
var taskWorkload = await currentTasks.Select(task => new
|
||
{
|
||
task.Id,
|
||
task.CourseId,
|
||
Credits = task.Course!.Credits,
|
||
IsClassAssigned = task.Classes.Any(item =>
|
||
item.AdministrativeClassId == student.AdministrativeClassId)
|
||
}).ToListAsync(cancellationToken);
|
||
var currentCourses = taskWorkload
|
||
.GroupBy(x => x.CourseId)
|
||
.Select(x => x.First())
|
||
.ToList();
|
||
var courseCount = currentCourses.Count;
|
||
var courseCredits = currentCourses.Sum(x => x.Credits);
|
||
var classAssignedCount = taskWorkload.Count(x => x.IsClassAssigned);
|
||
var selfSelectedCount = taskWorkload.Count(x => !x.IsClassAssigned);
|
||
var publishedGrades = db.GradeRecords.AsNoTracking().Where(x =>
|
||
x.StudentId == studentId.Value &&
|
||
x.GradeSheet!.Status == GradeSheetStatus.Published &&
|
||
currentTermId.HasValue &&
|
||
x.GradeSheet.TeachingTask!.AcademicTermId == currentTermId.Value);
|
||
var gradeCount = await publishedGrades.CountAsync(cancellationToken);
|
||
var average = await publishedGrades
|
||
.Where(x => x.ExamStatus == GradeExamStatus.Normal && x.TotalScore.HasValue)
|
||
.AverageAsync(x => (decimal?)x.TotalScore, cancellationToken);
|
||
var failed = await publishedGrades.CountAsync(x =>
|
||
x.ExamStatus == GradeExamStatus.Normal && x.TotalScore.HasValue && x.TotalScore < 60,
|
||
cancellationToken);
|
||
|
||
var subtitle = failed > 0
|
||
? $"已发布成绩中有 {failed} 门课程需要重点关注,建议优先查看课程反馈。"
|
||
: courseCount > 0
|
||
? $"本学期已有 {courseCount} 门课程、{courseCredits:0.#} 学分进入你的学习安排。"
|
||
: "本学期暂未发现为你安排或确认选课的课程,可先查看培养方案和选课安排。";
|
||
var narrative = courseCount == 0
|
||
? "你的当前学习安排尚未形成:系统还没有找到行政班已安排课程或已确认选课。"
|
||
: failed > 0
|
||
? $"本学期已形成 {courseCount} 门课程安排,其中 {classAssignedCount} 个教学班来自行政班安排;已发布成绩中有 {failed} 门需要重点关注。"
|
||
: gradeCount > 0
|
||
? $"本学期有 {courseCount} 门课程进入学习安排,已发布 {gradeCount} 门成绩,当前没有不及格记录。"
|
||
: $"本学期有 {courseCount} 门课程进入学习安排,包含 {classAssignedCount} 个行政班教学班和 {selfSelectedCount} 个自主选课教学班,成绩发布后会在这里更新。";
|
||
return new DashboardGreeting("Student", $"{name}{greeting}", subtitle, "学习节奏", narrative,
|
||
[
|
||
new DashboardGreetingInsight("本学期课程", $"{courseCount} 门", $"共 {courseCredits:0.#} 学分", "calm"),
|
||
new DashboardGreetingInsight("已发布成绩", $"{gradeCount} 门", average.HasValue ? $"平均分 {average.Value:0.0}" : "等待成绩发布", "calm"),
|
||
new DashboardGreetingInsight("重点关注", $"{failed} 门", failed > 0 ? "建议尽早安排复习与答疑" : "当前无不及格记录", failed > 0 ? "attention" : "positive")
|
||
]);
|
||
}
|
||
|
||
if (!isManager && scope.IsInRole(SystemRoles.Teacher))
|
||
{
|
||
var teacherId = await db.Teachers.AsNoTracking()
|
||
.Where(x => x.UserId == scope.UserId && x.Status == TeacherStatus.Active)
|
||
.Select(x => (Guid?)x.Id)
|
||
.FirstOrDefaultAsync(cancellationToken);
|
||
if (!teacherId.HasValue)
|
||
return new DashboardGreeting("Teacher", $"{name}{greeting}", "绑定教师档案后,将为你生成本学期教学负荷概览。", "教学节奏", "关联教师档案后,可从教学班、授课学时和成绩进度生成今日工作摘要。", []);
|
||
|
||
var tasks = db.TeachingTasks.AsNoTracking().Where(x =>
|
||
currentTermId.HasValue && x.AcademicTermId == currentTermId.Value &&
|
||
x.Teachers.Any(t => t.TeacherId == teacherId.Value));
|
||
var teachingClasses = await tasks.CountAsync(cancellationToken);
|
||
var estimatedHours = await tasks.SumAsync(
|
||
x => (int?)(x.WeeklyHours * (x.EndWeek - x.StartWeek + 1)), cancellationToken) ?? 0;
|
||
var gradeSheets = db.GradeSheets.AsNoTracking().Where(x =>
|
||
x.TeachingTask!.Teachers.Any(t => t.TeacherId == teacherId.Value) &&
|
||
currentTermId.HasValue && x.TeachingTask.AcademicTermId == currentTermId.Value);
|
||
var pendingGrades = await gradeSheets.CountAsync(x =>
|
||
x.Status == GradeSheetStatus.Draft ||
|
||
x.Status == GradeSheetStatus.Returned, cancellationToken);
|
||
var submittedGrades = await gradeSheets.CountAsync(x => x.Status == GradeSheetStatus.Submitted, cancellationToken);
|
||
var subtitle = pendingGrades > 0
|
||
? $"有 {pendingGrades} 张成绩登记册尚待提交,完成后可进入审核流程。"
|
||
: teachingClasses > 0
|
||
? $"本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时。"
|
||
: "本学期暂未分配教学班,请留意教学任务安排。";
|
||
var narrative = pendingGrades > 0
|
||
? $"你本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时;有 {pendingGrades} 张成绩登记册等待提交。"
|
||
: $"你本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时,目前没有待提交的成绩登记册。";
|
||
return new DashboardGreeting("Teacher", $"{name}{greeting}", subtitle, "教学节奏", narrative,
|
||
[
|
||
new DashboardGreetingInsight("教学班", $"{teachingClasses} 个", $"预计 {estimatedHours} 学时", "calm"),
|
||
new DashboardGreetingInsight("待提交成绩", $"{pendingGrades} 张", pendingGrades > 0 ? "请在截止日前完成登记" : "当前无需提交", pendingGrades > 0 ? "attention" : "positive"),
|
||
new DashboardGreetingInsight("审核中成绩", $"{submittedGrades} 张", submittedGrades > 0 ? "等待审核结果" : "暂无审核中登记册", "calm")
|
||
]);
|
||
}
|
||
|
||
var actionable = pending is null ? 0 : pending.TeacherApplications + pending.GradeSheets +
|
||
pending.CourseAdjustments + pending.StudentStatusChanges + pending.GradeModifications +
|
||
pending.ClassroomReservations + pending.GeneralApprovals;
|
||
var taskCount = counts?.TeachingTasks ?? 0;
|
||
var scheduledCount = counts?.ScheduledTeachingTasks ?? 0;
|
||
var subtitleForManager = actionable > 0
|
||
? $"当前有 {actionable} 项待办需要跟进,优先处理时效性审核事项。"
|
||
: taskCount > 0
|
||
? $"本学期 {taskCount} 个教学班正在运行,当前没有积压待办。"
|
||
: "当前学期运行数据已就绪,可从教学任务开始推进。";
|
||
var managerNarrative = actionable > 0
|
||
? $"当前教学运行覆盖 {taskCount} 个教学班,其中 {scheduledCount} 个已进入课表;{actionable} 项待办正等待处理。"
|
||
: $"当前教学运行覆盖 {taskCount} 个教学班,其中 {scheduledCount} 个已进入课表,暂未发现需要你处理的积压事项。";
|
||
return new DashboardGreeting("Manager", $"{name}{greeting}", subtitleForManager, "运行态势", managerNarrative,
|
||
[
|
||
new DashboardGreetingInsight("当前待办", $"{actionable} 项", actionable > 0 ? "优先处理可操作事项" : "暂无积压", actionable > 0 ? "attention" : "positive"),
|
||
new DashboardGreetingInsight("本学期教学班", $"{taskCount} 个", "教学运行规模", "calm"),
|
||
new DashboardGreetingInsight("已进入课表", $"{scheduledCount} 个", taskCount > 0 ? $"覆盖 {Math.Round(scheduledCount * 100d / taskCount)}% 教学班" : "等待教学任务发布", "calm")
|
||
]);
|
||
}
|
||
|
||
private static string GetTimeGreeting()
|
||
{
|
||
var hour = DateTime.UtcNow.AddHours(8).Hour;
|
||
return hour < 11 ? "早上好" : hour < 14 ? "中午好" : hour < 18 ? "下午好" : "晚上好";
|
||
}
|
||
|
||
private async Task<DashboardPending> LoadPendingAsync(
|
||
CurrentUserScope scope,
|
||
Guid? restrictedCollegeId,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var isSchoolManager =
|
||
scope.IsInRole(SystemRoles.SuperAdmin) ||
|
||
scope.IsInRole(SystemRoles.AcademicAdmin);
|
||
var isCollegeManager =
|
||
!isSchoolManager && scope.IsInRole(SystemRoles.CollegeAdmin);
|
||
if (!isSchoolManager && !isCollegeManager)
|
||
return new DashboardPending(0, 0, 0, 0, 0, 0, 0);
|
||
|
||
var teacherApplications = db.TeacherCourseApplications.AsNoTracking()
|
||
.Where(x =>
|
||
x.Status == TeacherCourseApplicationStatus.Pending &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.Teacher!.CollegeId == restrictedCollegeId.Value));
|
||
var gradeSheets = db.GradeSheets.AsNoTracking()
|
||
.Where(x =>
|
||
x.Status == GradeSheetStatus.Submitted &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value));
|
||
var courseAdjustments = db.CourseAdjustments.AsNoTracking()
|
||
.Where(x =>
|
||
x.Status == CourseAdjustmentStatus.Submitted &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value));
|
||
var studentStatusChanges = db.StudentStatusChanges.AsNoTracking()
|
||
.Where(x =>
|
||
x.State == (isCollegeManager
|
||
? StudentStatusChangeState.CounselorApproved
|
||
: StudentStatusChangeState.CollegeApproved) &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.Student!.AdministrativeClass!.Major!.CollegeId ==
|
||
restrictedCollegeId.Value));
|
||
var gradeModifications = db.GradeModifications.AsNoTracking()
|
||
.Where(x =>
|
||
x.Status == (isCollegeManager
|
||
? GradeModificationStatus.TeacherSubmitted
|
||
: GradeModificationStatus.CollegeApproved) &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.GradeRecord!.GradeSheet!.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value));
|
||
|
||
var generalApprovals =
|
||
await db.CourseExemptions.AsNoTracking().CountAsync(
|
||
x => x.Status == ApprovalStatus.Submitted &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value),
|
||
cancellationToken) +
|
||
await db.DeferredExams.AsNoTracking().CountAsync(
|
||
x => x.Status == ApprovalStatus.Submitted &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.TeachingTask!.Course!.CollegeId ==
|
||
restrictedCollegeId.Value),
|
||
cancellationToken) +
|
||
await db.CourseSubstitutions.AsNoTracking().CountAsync(
|
||
x => x.Status == ApprovalStatus.Submitted &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.Student!.AdministrativeClass!.Major!.CollegeId ==
|
||
restrictedCollegeId.Value),
|
||
cancellationToken) +
|
||
await db.AttendanceRecords.AsNoTracking().CountAsync(
|
||
x => x.AppealStatus == AttendanceAppealStatus.Pending &&
|
||
(!restrictedCollegeId.HasValue ||
|
||
x.Student!.AdministrativeClass!.Major!.CollegeId ==
|
||
restrictedCollegeId.Value),
|
||
cancellationToken);
|
||
|
||
var classroomReservations = isCollegeManager &&
|
||
restrictedCollegeId.HasValue
|
||
? await db.ClassroomReservations.AsNoTracking().CountAsync(
|
||
x => x.Status == ClassroomReservationStatus.Submitted &&
|
||
x.ApplicantCollegeId == restrictedCollegeId.Value,
|
||
cancellationToken)
|
||
: 0;
|
||
|
||
return new DashboardPending(
|
||
await teacherApplications.CountAsync(cancellationToken),
|
||
await gradeSheets.CountAsync(cancellationToken),
|
||
await courseAdjustments.CountAsync(cancellationToken),
|
||
await studentStatusChanges.CountAsync(cancellationToken),
|
||
await gradeModifications.CountAsync(cancellationToken),
|
||
classroomReservations,
|
||
generalApprovals);
|
||
}
|
||
|
||
private static DashboardAudience BuildAudience(
|
||
CurrentUserScope scope,
|
||
string? collegeName)
|
||
{
|
||
if (scope.IsInRole(SystemRoles.SuperAdmin))
|
||
return new DashboardAudience(
|
||
"System",
|
||
"全域教务工作台",
|
||
"全校",
|
||
"统筹基础数据、教学运行与系统治理");
|
||
if (scope.IsInRole(SystemRoles.AcademicAdmin))
|
||
return new DashboardAudience(
|
||
"School",
|
||
"校级教务工作台",
|
||
"全校",
|
||
"聚焦跨学院教学运行与校级审核");
|
||
if (scope.IsInRole(SystemRoles.CollegeAdmin))
|
||
return new DashboardAudience(
|
||
"College",
|
||
"学院教务工作台",
|
||
collegeName ?? "本学院",
|
||
"聚焦本学院教学准备、过程审核与成绩归档");
|
||
if (scope.IsInRole(SystemRoles.Leader))
|
||
return new DashboardAudience(
|
||
"Leadership",
|
||
"教学运行观察台",
|
||
"全校",
|
||
"查看全校教学运行与质量数据");
|
||
if (scope.IsInRole(SystemRoles.Counselor))
|
||
return new DashboardAudience(
|
||
"Counselor",
|
||
"班级工作台",
|
||
collegeName ?? "所辖班级",
|
||
"处理学生过程管理与学业支持");
|
||
return new DashboardAudience(
|
||
"Teaching",
|
||
"教学工作台",
|
||
collegeName ?? "个人教学",
|
||
"查看课程运行并进入日常教学工作");
|
||
}
|
||
}
|
||
|
||
public sealed record DashboardResponse(
|
||
DashboardAudience Audience,
|
||
DashboardTerm? CurrentTerm,
|
||
DashboardCounts Counts,
|
||
DashboardPending Pending,
|
||
DashboardGreeting Greeting,
|
||
DateTime GeneratedAt);
|
||
|
||
public sealed record DashboardGreeting(
|
||
string Role,
|
||
string Title,
|
||
string Subtitle,
|
||
string Label,
|
||
string Narrative,
|
||
IReadOnlyList<DashboardGreetingInsight> Insights);
|
||
|
||
public sealed record DashboardGreetingInsight(
|
||
string Label,
|
||
string Value,
|
||
string Hint,
|
||
string Tone);
|
||
|
||
public sealed record DashboardAudience(
|
||
string Level,
|
||
string Title,
|
||
string ScopeName,
|
||
string Description);
|
||
|
||
public sealed record DashboardTerm(
|
||
Guid Id,
|
||
string Name,
|
||
DateOnly StartDate,
|
||
DateOnly EndDate);
|
||
|
||
public sealed record DashboardCounts(
|
||
int Students,
|
||
int Teachers,
|
||
int Courses,
|
||
int TeachingTasks,
|
||
int PublishedTeachingTasks,
|
||
int ScheduledTeachingTasks,
|
||
int CourseEnrollments,
|
||
int GradeSheets,
|
||
int PublishedGradeSheets,
|
||
int SubmittedGradeSheets,
|
||
int OpenCourseSelectionRounds);
|
||
|
||
public sealed record DashboardPending(
|
||
int TeacherApplications,
|
||
int GradeSheets,
|
||
int CourseAdjustments,
|
||
int StudentStatusChanges,
|
||
int GradeModifications,
|
||
int ClassroomReservations,
|
||
int GeneralApprovals);
|