增加自然语言状态摘要
This commit is contained in:
Submodule
+1
Submodule Academic-Affairs-System.wiki added at c7be1438bc
@@ -178,20 +178,43 @@ public sealed class DashboardController(
|
|||||||
.Select(x => (Guid?)x.Id)
|
.Select(x => (Guid?)x.Id)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
if (!studentId.HasValue)
|
if (!studentId.HasValue)
|
||||||
return new DashboardGreeting("Student", $"{name}{greeting}", "绑定学籍后,将为你生成课程与成绩学习概览。", "学习节奏", []);
|
return new DashboardGreeting("Student", $"{name}{greeting}", "绑定学籍后,将为你生成课程与成绩学习概览。", "学习节奏", "关联学生档案后,可从课程安排、成绩和考试中生成学习状态摘要。", []);
|
||||||
|
|
||||||
var enrollments = db.CourseEnrollments.AsNoTracking().Where(x =>
|
var student = await db.Students.AsNoTracking()
|
||||||
x.StudentId == studentId.Value &&
|
.Where(x => x.Id == studentId.Value)
|
||||||
x.Status == CourseEnrollmentStatus.Enrolled &&
|
.Select(x => new { x.AdministrativeClassId })
|
||||||
|
.FirstAsync(cancellationToken);
|
||||||
|
var currentTasks = db.TeachingTasks.AsNoTracking().Where(task =>
|
||||||
currentTermId.HasValue &&
|
currentTermId.HasValue &&
|
||||||
x.CourseSelectionOffering!.CourseSelectionRound!.AcademicTermId == currentTermId.Value);
|
task.AcademicTermId == currentTermId.Value &&
|
||||||
var selectedCourses = await enrollments.CountAsync(cancellationToken);
|
task.Status == TeachingTaskStatus.Published &&
|
||||||
var selectedCredits = await enrollments.SumAsync(
|
(task.Classes.Any(item =>
|
||||||
x => (decimal?)x.CourseSelectionOffering!.TeachingTask!.Course!.Credits,
|
item.AdministrativeClassId == student.AdministrativeClassId) ||
|
||||||
cancellationToken) ?? 0;
|
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 =>
|
var publishedGrades = db.GradeRecords.AsNoTracking().Where(x =>
|
||||||
x.StudentId == studentId.Value &&
|
x.StudentId == studentId.Value &&
|
||||||
x.GradeSheet!.Status == GradeSheetStatus.Published);
|
x.GradeSheet!.Status == GradeSheetStatus.Published &&
|
||||||
|
currentTermId.HasValue &&
|
||||||
|
x.GradeSheet.TeachingTask!.AcademicTermId == currentTermId.Value);
|
||||||
var gradeCount = await publishedGrades.CountAsync(cancellationToken);
|
var gradeCount = await publishedGrades.CountAsync(cancellationToken);
|
||||||
var average = await publishedGrades
|
var average = await publishedGrades
|
||||||
.Where(x => x.ExamStatus == GradeExamStatus.Normal && x.TotalScore.HasValue)
|
.Where(x => x.ExamStatus == GradeExamStatus.Normal && x.TotalScore.HasValue)
|
||||||
@@ -202,12 +225,19 @@ public sealed class DashboardController(
|
|||||||
|
|
||||||
var subtitle = failed > 0
|
var subtitle = failed > 0
|
||||||
? $"已发布成绩中有 {failed} 门课程需要重点关注,建议优先查看课程反馈。"
|
? $"已发布成绩中有 {failed} 门课程需要重点关注,建议优先查看课程反馈。"
|
||||||
: selectedCourses > 0
|
: courseCount > 0
|
||||||
? $"本学期已选 {selectedCourses} 门课、{selectedCredits:0.#} 学分,保持现在的学习节奏。"
|
? $"本学期已有 {courseCount} 门课程、{courseCredits:0.#} 学分进入你的学习安排。"
|
||||||
: "本学期暂未发现有效选课记录,可先查看选课安排。";
|
: "本学期暂未发现为你安排或确认选课的课程,可先查看培养方案和选课安排。";
|
||||||
return new DashboardGreeting("Student", $"{name}{greeting}", subtitle, "学习节奏",
|
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("本学期课程", $"{selectedCourses} 门", $"共 {selectedCredits:0.#} 学分", "calm"),
|
new DashboardGreetingInsight("本学期课程", $"{courseCount} 门", $"共 {courseCredits:0.#} 学分", "calm"),
|
||||||
new DashboardGreetingInsight("已发布成绩", $"{gradeCount} 门", average.HasValue ? $"平均分 {average.Value:0.0}" : "等待成绩发布", "calm"),
|
new DashboardGreetingInsight("已发布成绩", $"{gradeCount} 门", average.HasValue ? $"平均分 {average.Value:0.0}" : "等待成绩发布", "calm"),
|
||||||
new DashboardGreetingInsight("重点关注", $"{failed} 门", failed > 0 ? "建议尽早安排复习与答疑" : "当前无不及格记录", failed > 0 ? "attention" : "positive")
|
new DashboardGreetingInsight("重点关注", $"{failed} 门", failed > 0 ? "建议尽早安排复习与答疑" : "当前无不及格记录", failed > 0 ? "attention" : "positive")
|
||||||
]);
|
]);
|
||||||
@@ -220,7 +250,7 @@ public sealed class DashboardController(
|
|||||||
.Select(x => (Guid?)x.Id)
|
.Select(x => (Guid?)x.Id)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
if (!teacherId.HasValue)
|
if (!teacherId.HasValue)
|
||||||
return new DashboardGreeting("Teacher", $"{name}{greeting}", "绑定教师档案后,将为你生成本学期教学负荷概览。", "教学节奏", []);
|
return new DashboardGreeting("Teacher", $"{name}{greeting}", "绑定教师档案后,将为你生成本学期教学负荷概览。", "教学节奏", "关联教师档案后,可从教学班、授课学时和成绩进度生成今日工作摘要。", []);
|
||||||
|
|
||||||
var tasks = db.TeachingTasks.AsNoTracking().Where(x =>
|
var tasks = db.TeachingTasks.AsNoTracking().Where(x =>
|
||||||
currentTermId.HasValue && x.AcademicTermId == currentTermId.Value &&
|
currentTermId.HasValue && x.AcademicTermId == currentTermId.Value &&
|
||||||
@@ -240,7 +270,10 @@ public sealed class DashboardController(
|
|||||||
: teachingClasses > 0
|
: teachingClasses > 0
|
||||||
? $"本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时。"
|
? $"本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时。"
|
||||||
: "本学期暂未分配教学班,请留意教学任务安排。";
|
: "本学期暂未分配教学班,请留意教学任务安排。";
|
||||||
return new DashboardGreeting("Teacher", $"{name}{greeting}", subtitle, "教学节奏",
|
var narrative = pendingGrades > 0
|
||||||
|
? $"你本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时;有 {pendingGrades} 张成绩登记册等待提交。"
|
||||||
|
: $"你本学期承担 {teachingClasses} 个教学班,预计授课 {estimatedHours} 学时,目前没有待提交的成绩登记册。";
|
||||||
|
return new DashboardGreeting("Teacher", $"{name}{greeting}", subtitle, "教学节奏", narrative,
|
||||||
[
|
[
|
||||||
new DashboardGreetingInsight("教学班", $"{teachingClasses} 个", $"预计 {estimatedHours} 学时", "calm"),
|
new DashboardGreetingInsight("教学班", $"{teachingClasses} 个", $"预计 {estimatedHours} 学时", "calm"),
|
||||||
new DashboardGreetingInsight("待提交成绩", $"{pendingGrades} 张", pendingGrades > 0 ? "请在截止日前完成登记" : "当前无需提交", pendingGrades > 0 ? "attention" : "positive"),
|
new DashboardGreetingInsight("待提交成绩", $"{pendingGrades} 张", pendingGrades > 0 ? "请在截止日前完成登记" : "当前无需提交", pendingGrades > 0 ? "attention" : "positive"),
|
||||||
@@ -258,7 +291,10 @@ public sealed class DashboardController(
|
|||||||
: taskCount > 0
|
: taskCount > 0
|
||||||
? $"本学期 {taskCount} 个教学班正在运行,当前没有积压待办。"
|
? $"本学期 {taskCount} 个教学班正在运行,当前没有积压待办。"
|
||||||
: "当前学期运行数据已就绪,可从教学任务开始推进。";
|
: "当前学期运行数据已就绪,可从教学任务开始推进。";
|
||||||
return new DashboardGreeting("Manager", $"{name}{greeting}", subtitleForManager, "运行态势",
|
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("当前待办", $"{actionable} 项", actionable > 0 ? "优先处理可操作事项" : "暂无积压", actionable > 0 ? "attention" : "positive"),
|
||||||
new DashboardGreetingInsight("本学期教学班", $"{taskCount} 个", "教学运行规模", "calm"),
|
new DashboardGreetingInsight("本学期教学班", $"{taskCount} 个", "教学运行规模", "calm"),
|
||||||
@@ -418,6 +454,7 @@ public sealed record DashboardGreeting(
|
|||||||
string Title,
|
string Title,
|
||||||
string Subtitle,
|
string Subtitle,
|
||||||
string Label,
|
string Label,
|
||||||
|
string Narrative,
|
||||||
IReadOnlyList<DashboardGreetingInsight> Insights);
|
IReadOnlyList<DashboardGreetingInsight> Insights);
|
||||||
|
|
||||||
public sealed record DashboardGreetingInsight(
|
public sealed record DashboardGreetingInsight(
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ interface DashboardGreeting {
|
|||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
label: string
|
label: string
|
||||||
|
narrative: string
|
||||||
insights: Array<{
|
insights: Array<{
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
@@ -465,6 +466,8 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<p class="greeting-narrative">{{ data.greeting.narrative }}</p>
|
||||||
|
|
||||||
<section class="overview-metrics" aria-label="关键教学数据">
|
<section class="overview-metrics" aria-label="关键教学数据">
|
||||||
<button
|
<button
|
||||||
v-for="metric in adminMetrics"
|
v-for="metric in adminMetrics"
|
||||||
@@ -652,6 +655,14 @@ onMounted(async () => {
|
|||||||
.greeting-insight strong { color: var(--dashboard-navy); font-size: 20px; }
|
.greeting-insight strong { color: var(--dashboard-navy); font-size: 20px; }
|
||||||
.greeting-insight small { overflow: hidden; color: #84909d; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
.greeting-insight small { overflow: hidden; color: #84909d; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
|
||||||
|
.greeting-narrative {
|
||||||
|
margin: -7px 0 0;
|
||||||
|
padding: 0 3px;
|
||||||
|
color: #526078;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
.overview-hero::after {
|
.overview-hero::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ interface DashboardGreeting {
|
|||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
label: string
|
label: string
|
||||||
|
narrative: string
|
||||||
insights: Array<{
|
insights: Array<{
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
@@ -350,6 +351,7 @@ onMounted(loadOverview)
|
|||||||
<em>{{ insight.hint }}</em>
|
<em>{{ insight.hint }}</em>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
<p v-if="dashboardGreeting?.narrative" class="student-greeting-narrative">{{ dashboardGreeting.narrative }}</p>
|
||||||
|
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="failedSections.length"
|
v-if="failedSections.length"
|
||||||
@@ -595,6 +597,14 @@ onMounted(loadOverview)
|
|||||||
.student-greeting-insights strong { color: var(--ink); font-size: 19px; }
|
.student-greeting-insights strong { color: var(--ink); font-size: 19px; }
|
||||||
.student-greeting-insights em { overflow: hidden; color: var(--muted); font-size: 10px; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
|
.student-greeting-insights em { overflow: hidden; color: var(--muted); font-size: 10px; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
|
||||||
|
.student-greeting-narrative {
|
||||||
|
margin: -5px 0 0;
|
||||||
|
padding: 0 3px;
|
||||||
|
color: #526078;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
.today-status {
|
.today-status {
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user