首页问候
This commit is contained in:
@@ -57,9 +57,23 @@ interface DashboardData {
|
||||
classroomReservations: number
|
||||
generalApprovals: number
|
||||
}
|
||||
greeting: DashboardGreeting
|
||||
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 {
|
||||
key: string
|
||||
label: string
|
||||
@@ -390,8 +404,8 @@ onMounted(async () => {
|
||||
<i>数据范围</i>
|
||||
</div>
|
||||
<p class="overview-eyebrow">ACADEMIC OPERATIONS</p>
|
||||
<h1>{{ data.audience.title }}</h1>
|
||||
<p class="overview-description">{{ data.audience.description }}</p>
|
||||
<h1>{{ data.greeting.title }}</h1>
|
||||
<p class="overview-description">{{ data.greeting.subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<button class="pending-brief" type="button" @click="router.push(primaryActionRoute)">
|
||||
@@ -436,6 +450,21 @@ onMounted(async () => {
|
||||
</div>
|
||||
</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="关键教学数据">
|
||||
<button
|
||||
v-for="metric in adminMetrics"
|
||||
@@ -586,6 +615,43 @@ onMounted(async () => {
|
||||
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 {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -1155,6 +1221,7 @@ button:focus-visible {
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.greeting-insights { grid-template-columns: 1fr; gap: 12px; }
|
||||
.dashboard-work-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -1170,6 +1237,8 @@ button:focus-visible {
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.greeting-insights { padding: 15px; }
|
||||
.greeting-insights > div { grid-template-columns: 1fr; }
|
||||
.admin-dashboard { gap: 10px; }
|
||||
|
||||
.overview-hero {
|
||||
|
||||
@@ -70,6 +70,18 @@ interface GradeItem {
|
||||
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 auth = useAuthStore()
|
||||
const loading = ref(true)
|
||||
@@ -79,6 +91,7 @@ const notifications = ref<NotificationItem[]>([])
|
||||
const unreadCount = ref(0)
|
||||
const exams = ref<ExamItem[]>([])
|
||||
const grades = ref<GradeItem[]>([])
|
||||
const dashboardGreeting = ref<DashboardGreeting | null>(null)
|
||||
const now = ref(new Date())
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
@@ -281,6 +294,7 @@ async function loadOverview() {
|
||||
http.get('/notifications', { params: { page: 1, pageSize: 5 } }),
|
||||
http.get('/exams/my-schedule'),
|
||||
http.get('/grades/student/transcript'),
|
||||
http.get<DashboardGreeting>('/dashboard/greeting'),
|
||||
])
|
||||
|
||||
if (results[0].status === 'fulfilled') {
|
||||
@@ -304,6 +318,9 @@ async function loadOverview() {
|
||||
} else {
|
||||
failedSections.value.push('考试成绩')
|
||||
}
|
||||
if (results[4].status === 'fulfilled') {
|
||||
dashboardGreeting.value = results[4].value.data
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -315,8 +332,8 @@ onMounted(loadOverview)
|
||||
<section class="student-overview-hero">
|
||||
<div class="student-hero-copy">
|
||||
<span class="section-kicker">MY ACADEMIC DAY</span>
|
||||
<h2>{{ greeting }},{{ auth.user?.displayName ?? '同学' }}</h2>
|
||||
<p>{{ todayLabel }}<template v-if="timetable?.term"> · {{ timetable.term.name }}</template></p>
|
||||
<h2>{{ dashboardGreeting?.title ?? `${greeting},${auth.user?.displayName ?? '同学'}` }}</h2>
|
||||
<p>{{ dashboardGreeting?.subtitle ?? todayLabel }}<template v-if="!dashboardGreeting && timetable?.term"> · {{ timetable.term.name }}</template></p>
|
||||
</div>
|
||||
<div class="today-status">
|
||||
<span>今日课程</span>
|
||||
@@ -325,6 +342,15 @@ onMounted(loadOverview)
|
||||
</div>
|
||||
</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
|
||||
v-if="failedSections.length"
|
||||
type="warning"
|
||||
@@ -539,6 +565,36 @@ onMounted(loadOverview)
|
||||
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 {
|
||||
min-width: 180px;
|
||||
margin-left: auto;
|
||||
@@ -871,12 +927,14 @@ onMounted(loadOverview)
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.student-greeting-insights { grid-template-columns: 1fr repeat(3, minmax(0, 1fr)); }
|
||||
.student-overview-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.student-greeting-insights { padding: 15px 18px; grid-template-columns: 1fr; }
|
||||
.student-overview {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user