首页问候语-智能化进程1 #2
@@ -135,9 +135,143 @@ public sealed class DashboardController(
|
|||||||
currentTerm,
|
currentTerm,
|
||||||
counts,
|
counts,
|
||||||
pending,
|
pending,
|
||||||
|
await BuildGreetingAsync(scope, currentTermId, counts, pending, cancellationToken),
|
||||||
DateTime.UtcNow));
|
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 enrollments = db.CourseEnrollments.AsNoTracking().Where(x =>
|
||||||
|
x.StudentId == studentId.Value &&
|
||||||
|
x.Status == CourseEnrollmentStatus.Enrolled &&
|
||||||
|
currentTermId.HasValue &&
|
||||||
|
x.CourseSelectionOffering!.CourseSelectionRound!.AcademicTermId == currentTermId.Value);
|
||||||
|
var selectedCourses = await enrollments.CountAsync(cancellationToken);
|
||||||
|
var selectedCredits = await enrollments.SumAsync(
|
||||||
|
x => (decimal?)x.CourseSelectionOffering!.TeachingTask!.Course!.Credits,
|
||||||
|
cancellationToken) ?? 0;
|
||||||
|
var publishedGrades = db.GradeRecords.AsNoTracking().Where(x =>
|
||||||
|
x.StudentId == studentId.Value &&
|
||||||
|
x.GradeSheet!.Status == GradeSheetStatus.Published);
|
||||||
|
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} 门课程需要重点关注,建议优先查看课程反馈。"
|
||||||
|
: selectedCourses > 0
|
||||||
|
? $"本学期已选 {selectedCourses} 门课、{selectedCredits:0.#} 学分,保持现在的学习节奏。"
|
||||||
|
: "本学期暂未发现有效选课记录,可先查看选课安排。";
|
||||||
|
return new DashboardGreeting("Student", $"{name}{greeting}", subtitle, "学习节奏",
|
||||||
|
[
|
||||||
|
new DashboardGreetingInsight("本学期课程", $"{selectedCourses} 门", $"共 {selectedCredits: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} 学时。"
|
||||||
|
: "本学期暂未分配教学班,请留意教学任务安排。";
|
||||||
|
return new DashboardGreeting("Teacher", $"{name}{greeting}", subtitle, "教学节奏",
|
||||||
|
[
|
||||||
|
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} 个教学班正在运行,当前没有积压待办。"
|
||||||
|
: "当前学期运行数据已就绪,可从教学任务开始推进。";
|
||||||
|
return new DashboardGreeting("Manager", $"{name}{greeting}", subtitleForManager, "运行态势",
|
||||||
|
[
|
||||||
|
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(
|
private async Task<DashboardPending> LoadPendingAsync(
|
||||||
CurrentUserScope scope,
|
CurrentUserScope scope,
|
||||||
Guid? restrictedCollegeId,
|
Guid? restrictedCollegeId,
|
||||||
@@ -276,8 +410,22 @@ public sealed record DashboardResponse(
|
|||||||
DashboardTerm? CurrentTerm,
|
DashboardTerm? CurrentTerm,
|
||||||
DashboardCounts Counts,
|
DashboardCounts Counts,
|
||||||
DashboardPending Pending,
|
DashboardPending Pending,
|
||||||
|
DashboardGreeting Greeting,
|
||||||
DateTime GeneratedAt);
|
DateTime GeneratedAt);
|
||||||
|
|
||||||
|
public sealed record DashboardGreeting(
|
||||||
|
string Role,
|
||||||
|
string Title,
|
||||||
|
string Subtitle,
|
||||||
|
string Label,
|
||||||
|
IReadOnlyList<DashboardGreetingInsight> Insights);
|
||||||
|
|
||||||
|
public sealed record DashboardGreetingInsight(
|
||||||
|
string Label,
|
||||||
|
string Value,
|
||||||
|
string Hint,
|
||||||
|
string Tone);
|
||||||
|
|
||||||
public sealed record DashboardAudience(
|
public sealed record DashboardAudience(
|
||||||
string Level,
|
string Level,
|
||||||
string Title,
|
string Title,
|
||||||
|
|||||||
@@ -57,9 +57,23 @@ interface DashboardData {
|
|||||||
classroomReservations: number
|
classroomReservations: number
|
||||||
generalApprovals: number
|
generalApprovals: number
|
||||||
}
|
}
|
||||||
|
greeting: DashboardGreeting
|
||||||
generatedAt: string
|
generatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DashboardGreeting {
|
||||||
|
role: string
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
label: string
|
||||||
|
insights: Array<{
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
hint: string
|
||||||
|
tone: 'calm' | 'positive' | 'attention'
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
interface DashboardLink {
|
interface DashboardLink {
|
||||||
key: string
|
key: string
|
||||||
label: string
|
label: string
|
||||||
@@ -390,8 +404,8 @@ onMounted(async () => {
|
|||||||
<i>数据范围</i>
|
<i>数据范围</i>
|
||||||
</div>
|
</div>
|
||||||
<p class="overview-eyebrow">ACADEMIC OPERATIONS</p>
|
<p class="overview-eyebrow">ACADEMIC OPERATIONS</p>
|
||||||
<h1>{{ data.audience.title }}</h1>
|
<h1>{{ data.greeting.title }}</h1>
|
||||||
<p class="overview-description">{{ data.audience.description }}</p>
|
<p class="overview-description">{{ data.greeting.subtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="pending-brief" type="button" @click="router.push(primaryActionRoute)">
|
<button class="pending-brief" type="button" @click="router.push(primaryActionRoute)">
|
||||||
@@ -436,6 +450,21 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="greeting-insights" :aria-label="data.greeting.label">
|
||||||
|
<span class="greeting-insights-label">{{ data.greeting.label }}</span>
|
||||||
|
<div>
|
||||||
|
<article
|
||||||
|
v-for="insight in data.greeting.insights"
|
||||||
|
:key="insight.label"
|
||||||
|
:class="`greeting-insight ${insight.tone}`"
|
||||||
|
>
|
||||||
|
<span>{{ insight.label }}</span>
|
||||||
|
<strong>{{ insight.value }}</strong>
|
||||||
|
<small>{{ insight.hint }}</small>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="overview-metrics" aria-label="关键教学数据">
|
<section class="overview-metrics" aria-label="关键教学数据">
|
||||||
<button
|
<button
|
||||||
v-for="metric in adminMetrics"
|
v-for="metric in adminMetrics"
|
||||||
@@ -586,6 +615,43 @@ onMounted(async () => {
|
|||||||
background-size: 32px 32px, 32px 32px, auto;
|
background-size: 32px 32px, 32px 32px, auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.greeting-insights {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px minmax(0, 1fr);
|
||||||
|
gap: 18px;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 17px 21px;
|
||||||
|
border: 1px solid #dce6eb;
|
||||||
|
background: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-insights-label {
|
||||||
|
align-self: center;
|
||||||
|
color: var(--dashboard-teal);
|
||||||
|
font: 700 10px/1.5 Consolas, monospace;
|
||||||
|
letter-spacing: .12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-insights > div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-insight {
|
||||||
|
min-width: 0;
|
||||||
|
padding-left: 13px;
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
border-left: 2px solid #a9bcc6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-insight.positive { border-color: var(--dashboard-teal); }
|
||||||
|
.greeting-insight.attention { border-color: var(--dashboard-amber); }
|
||||||
|
.greeting-insight span { color: #687788; font-size: 11px; }
|
||||||
|
.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; }
|
||||||
|
|
||||||
.overview-hero::after {
|
.overview-hero::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -1155,6 +1221,7 @@ button:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
|
.greeting-insights { grid-template-columns: 1fr; gap: 12px; }
|
||||||
.dashboard-work-grid {
|
.dashboard-work-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@@ -1170,6 +1237,8 @@ button:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
|
.greeting-insights { padding: 15px; }
|
||||||
|
.greeting-insights > div { grid-template-columns: 1fr; }
|
||||||
.admin-dashboard { gap: 10px; }
|
.admin-dashboard { gap: 10px; }
|
||||||
|
|
||||||
.overview-hero {
|
.overview-hero {
|
||||||
|
|||||||
@@ -70,6 +70,18 @@ interface GradeItem {
|
|||||||
publishedAt?: string
|
publishedAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DashboardGreeting {
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
label: string
|
||||||
|
insights: Array<{
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
hint: string
|
||||||
|
tone: 'calm' | 'positive' | 'attention'
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -79,6 +91,7 @@ const notifications = ref<NotificationItem[]>([])
|
|||||||
const unreadCount = ref(0)
|
const unreadCount = ref(0)
|
||||||
const exams = ref<ExamItem[]>([])
|
const exams = ref<ExamItem[]>([])
|
||||||
const grades = ref<GradeItem[]>([])
|
const grades = ref<GradeItem[]>([])
|
||||||
|
const dashboardGreeting = ref<DashboardGreeting | null>(null)
|
||||||
const now = ref(new Date())
|
const now = ref(new Date())
|
||||||
|
|
||||||
const categoryLabels: Record<string, string> = {
|
const categoryLabels: Record<string, string> = {
|
||||||
@@ -281,6 +294,7 @@ async function loadOverview() {
|
|||||||
http.get('/notifications', { params: { page: 1, pageSize: 5 } }),
|
http.get('/notifications', { params: { page: 1, pageSize: 5 } }),
|
||||||
http.get('/exams/my-schedule'),
|
http.get('/exams/my-schedule'),
|
||||||
http.get('/grades/student/transcript'),
|
http.get('/grades/student/transcript'),
|
||||||
|
http.get<DashboardGreeting>('/dashboard/greeting'),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (results[0].status === 'fulfilled') {
|
if (results[0].status === 'fulfilled') {
|
||||||
@@ -304,6 +318,9 @@ async function loadOverview() {
|
|||||||
} else {
|
} else {
|
||||||
failedSections.value.push('考试成绩')
|
failedSections.value.push('考试成绩')
|
||||||
}
|
}
|
||||||
|
if (results[4].status === 'fulfilled') {
|
||||||
|
dashboardGreeting.value = results[4].value.data
|
||||||
|
}
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,8 +332,8 @@ onMounted(loadOverview)
|
|||||||
<section class="student-overview-hero">
|
<section class="student-overview-hero">
|
||||||
<div class="student-hero-copy">
|
<div class="student-hero-copy">
|
||||||
<span class="section-kicker">MY ACADEMIC DAY</span>
|
<span class="section-kicker">MY ACADEMIC DAY</span>
|
||||||
<h2>{{ greeting }},{{ auth.user?.displayName ?? '同学' }}</h2>
|
<h2>{{ dashboardGreeting?.title ?? `${greeting},${auth.user?.displayName ?? '同学'}` }}</h2>
|
||||||
<p>{{ todayLabel }}<template v-if="timetable?.term"> · {{ timetable.term.name }}</template></p>
|
<p>{{ dashboardGreeting?.subtitle ?? todayLabel }}<template v-if="!dashboardGreeting && timetable?.term"> · {{ timetable.term.name }}</template></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="today-status">
|
<div class="today-status">
|
||||||
<span>今日课程</span>
|
<span>今日课程</span>
|
||||||
@@ -325,6 +342,15 @@ onMounted(loadOverview)
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section v-if="dashboardGreeting?.insights.length" class="student-greeting-insights" :aria-label="dashboardGreeting.label">
|
||||||
|
<span>{{ dashboardGreeting.label }}</span>
|
||||||
|
<article v-for="insight in dashboardGreeting.insights" :key="insight.label" :class="insight.tone">
|
||||||
|
<small>{{ insight.label }}</small>
|
||||||
|
<strong>{{ insight.value }}</strong>
|
||||||
|
<em>{{ insight.hint }}</em>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="failedSections.length"
|
v-if="failedSections.length"
|
||||||
type="warning"
|
type="warning"
|
||||||
@@ -539,6 +565,36 @@ onMounted(loadOverview)
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.student-greeting-insights {
|
||||||
|
padding: 15px 21px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 105px repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
border: 1px solid #dce6eb;
|
||||||
|
background: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-greeting-insights > span {
|
||||||
|
align-self: center;
|
||||||
|
color: var(--teal);
|
||||||
|
font: 700 10px/1.5 Consolas, monospace;
|
||||||
|
letter-spacing: .11em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-greeting-insights article {
|
||||||
|
min-width: 0;
|
||||||
|
padding-left: 12px;
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
border-left: 2px solid #a9bcc6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-greeting-insights article.positive { border-color: var(--teal); }
|
||||||
|
.student-greeting-insights article.attention { border-color: #c4812a; }
|
||||||
|
.student-greeting-insights small { color: #667488; font-size: 10px; }
|
||||||
|
.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; }
|
||||||
|
|
||||||
.today-status {
|
.today-status {
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@@ -871,12 +927,14 @@ onMounted(loadOverview)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
|
.student-greeting-insights { grid-template-columns: 1fr repeat(3, minmax(0, 1fr)); }
|
||||||
.student-overview-grid {
|
.student-overview-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
.student-greeting-insights { padding: 15px 18px; grid-template-columns: 1fr; }
|
||||||
.student-overview {
|
.student-overview {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user