app端优化
This commit is contained in:
1 parent
7994ed1ed3
commit
1e45ede5be
14 files changed
+357
-37
No files matched your search
@@ -13,6 +13,9 @@ interface WidgetPayload {
|
||||
scheduleDate: string
|
||||
schedule: WidgetItem[]
|
||||
exams: WidgetItem[]
|
||||
attendance: WidgetItem[]
|
||||
notifications: WidgetItem[]
|
||||
unreadNotificationCount: number
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
@@ -128,6 +131,26 @@ function examItems(exams: any[], now: Date): WidgetItem[] {
|
||||
}))
|
||||
}
|
||||
|
||||
function attendanceItems(activities: any[]): WidgetItem[] {
|
||||
return activities.slice(0, 3).map((activity: any) => ({
|
||||
title: String(activity.courseName ?? '待签到课程'),
|
||||
subtitle: [activity.courseCode, activity.sheetName].filter(Boolean).join(' · '),
|
||||
time: activity.checkInAt ? '已签到' : activity.checkInEndsAt
|
||||
? `截止 ${new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(activity.checkInEndsAt))}`
|
||||
: '签到进行中',
|
||||
}))
|
||||
}
|
||||
|
||||
function notificationItems(notifications: any[]): WidgetItem[] {
|
||||
return notifications.filter((item: any) => !item.isRead).slice(0, 3).map((item: any) => ({
|
||||
title: String(item.title ?? '教务通知'),
|
||||
subtitle: String(item.senderName ?? '教务系统'),
|
||||
time: item.createdAt ? new Intl.DateTimeFormat('zh-CN', {
|
||||
month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false,
|
||||
}).format(new Date(item.createdAt)) : '未读',
|
||||
}))
|
||||
}
|
||||
|
||||
export async function clearNativeWidgets() {
|
||||
if (!native) return
|
||||
try {
|
||||
@@ -154,9 +177,13 @@ export async function syncNativeWidgets(force = false) {
|
||||
return
|
||||
}
|
||||
|
||||
const [timetableResult, examResult] = await Promise.allSettled([
|
||||
const [timetableResult, examResult, notificationResult, attendanceResult] = await Promise.allSettled([
|
||||
http.get('/timetables/mine'),
|
||||
http.get('/exams/my-schedule'),
|
||||
http.get('/notifications', { params: { page: 1, pageSize: 3, unreadOnly: true } }),
|
||||
roles.includes('Student')
|
||||
? http.get('/attendance/open-check-ins')
|
||||
: Promise.resolve({ data: [] }),
|
||||
])
|
||||
const now = new Date()
|
||||
const payload: WidgetPayload = {
|
||||
@@ -168,6 +195,15 @@ export async function syncNativeWidgets(force = false) {
|
||||
exams: examResult.status === 'fulfilled'
|
||||
? examItems(examResult.value.data, now)
|
||||
: [],
|
||||
attendance: attendanceResult.status === 'fulfilled'
|
||||
? attendanceItems(attendanceResult.value.data)
|
||||
: [],
|
||||
notifications: notificationResult.status === 'fulfilled'
|
||||
? notificationItems(notificationResult.value.data.items ?? [])
|
||||
: [],
|
||||
unreadNotificationCount: notificationResult.status === 'fulfilled'
|
||||
? Number(notificationResult.value.data.unreadCount ?? 0)
|
||||
: 0,
|
||||
updatedAt: now.toISOString(),
|
||||
}
|
||||
await NativeHome.updateWidgets({ payload })
|
||||
|
||||
+3
-2
@@ -16,8 +16,9 @@
|
||||
--el-color-primary: #233876;
|
||||
--el-color-success: #087f73;
|
||||
--el-border-radius-base: 7px;
|
||||
--safe-area-top: env(safe-area-inset-top, 0px);
|
||||
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
||||
/* Capacitor 8 injects these Android values; env() alone stays at zero in WebView. */
|
||||
--safe-area-top: max(env(safe-area-inset-top, 0px), var(--safe-area-inset-top, 0px));
|
||||
--safe-area-bottom: max(env(safe-area-inset-bottom, 0px), var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
@@ -917,7 +917,7 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table v-else :data="group.projects" class="project-table student-project-table" size="small">
|
||||
<el-table v-else :data="group.projects" class="project-table student-project-table desktop-project-table" size="small">
|
||||
<el-table-column type="expand" width="44">
|
||||
<template #default="{ row }">
|
||||
<div class="student-project-detail">
|
||||
@@ -988,6 +988,43 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="isStudent" class="mobile-project-cards">
|
||||
<article v-for="row in group.projects" :key="row.id" class="mobile-project-card">
|
||||
<header>
|
||||
<div>
|
||||
<span>{{ row.code }}</span>
|
||||
<h4>{{ row.name }}</h4>
|
||||
</div>
|
||||
<el-tag :type="row.arrangementMode === 'Centralized' ? 'primary' : 'success'" size="small" effect="plain">
|
||||
{{ modeMeta[row.arrangementMode].label }}
|
||||
</el-tag>
|
||||
</header>
|
||||
<p>{{ row.description || modeMeta[row.arrangementMode].note }}</p>
|
||||
<div class="mobile-project-arrangement">
|
||||
<template v-if="row.arrangementMode === 'Centralized' && row.scheduleEntry">
|
||||
<b>统一安排</b>
|
||||
<span>第 {{ row.scheduleEntry.projectWeek ?? `${row.scheduleEntry.startWeek}–${row.scheduleEntry.endWeek}` }} 周 · 星期 {{ row.scheduleEntry.dayOfWeek }} · 第 {{ row.scheduleEntry.startPeriod }}–{{ row.scheduleEntry.startPeriod + row.scheduleEntry.periodCount - 1 }} 节</span>
|
||||
<small>{{ row.scheduleEntry.campusName }} · {{ row.scheduleEntry.buildingName }} {{ row.scheduleEntry.classroomName }}</small>
|
||||
</template>
|
||||
<template v-else-if="row.arrangementMode === 'SelfScheduled'">
|
||||
<b>{{ row.myBooking ? '已预约场次' : '选择实验场次' }}</b>
|
||||
<div v-if="row.sessions.length" class="mobile-session-list">
|
||||
<article v-for="session in row.sessions" :key="session.id" :class="{ 'is-selected': row.myBooking?.experimentSessionId === session.id }">
|
||||
<div>
|
||||
<strong>{{ formatSessionTime(session) }}</strong>
|
||||
<small>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }}</small>
|
||||
</div>
|
||||
<el-tag :type="sessionTagType(session)" size="small" effect="plain">余 {{ session.remainingCount }} / {{ session.capacity }}</el-tag>
|
||||
<el-button v-if="row.myBooking?.experimentSessionId === session.id" size="small" type="success" disabled>已预约</el-button>
|
||||
<el-button v-else size="small" type="primary" plain :disabled="row.status !== 'Published' || !!row.myBooking || session.remainingCount <= 0" @click="book(row, session)">预约</el-button>
|
||||
</article>
|
||||
</div>
|
||||
<span v-else class="table-muted">暂无有效实验场次</span>
|
||||
<el-button v-if="row.myBooking && row.status === 'Published'" class="mobile-cancel-booking" size="small" text type="danger" @click="cancelBooking(row)">取消预约</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1593,6 +1630,7 @@ onMounted(async () => {
|
||||
.batch-project-cell b, .batch-project-cell small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.batch-project-cell b { color: var(--lab-ink); font-size: 12px; }
|
||||
.batch-project-cell small { color: var(--muted); font-size: 10px; }
|
||||
.mobile-project-cards { display: none; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.session-ticket { transition: none; }
|
||||
}
|
||||
@@ -1611,5 +1649,26 @@ onMounted(async () => {
|
||||
.student-project-detail { grid-template-columns: 1fr; }
|
||||
.mode-choice { grid-template-columns: 1fr; }
|
||||
.intro-actions { flex-wrap: wrap; justify-content: flex-end; }
|
||||
.desktop-project-table { display: none; }
|
||||
.mobile-project-cards { display: grid; gap: 1px; background: #dce5ea; }
|
||||
.mobile-project-card { padding: 15px 14px; background: #fff; }
|
||||
.mobile-project-card > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
||||
.mobile-project-card > header > div { min-width: 0; }
|
||||
.mobile-project-card > header span { color: #68808d; font: 700 10px/1.2 Consolas, monospace; letter-spacing: .06em; }
|
||||
.mobile-project-card h4 { margin: 4px 0 0; color: var(--lab-ink); font-size: 15px; line-height: 1.45; }
|
||||
.mobile-project-card > p { margin: 9px 0 0; color: #667f8d; font-size: 12px; line-height: 1.6; }
|
||||
.mobile-project-arrangement { display: grid; gap: 5px; margin-top: 13px; padding: 11px 12px; border-left: 3px solid #a6bbc6; background: #f6fafb; }
|
||||
.mobile-project-arrangement > b { color: #365364; font-size: 12px; }
|
||||
.mobile-project-arrangement > span { color: #405d6c; font-size: 12px; line-height: 1.5; }
|
||||
.mobile-project-arrangement > small { color: #718792; font-size: 11px; line-height: 1.45; }
|
||||
.mobile-session-list { display: grid; gap: 7px; margin-top: 3px; }
|
||||
.mobile-session-list > article { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; padding: 9px; border: 1px solid #dce6e9; background: #fff; }
|
||||
.mobile-session-list > article.is-selected { border-color: #69a99d; box-shadow: inset 3px 0 0 var(--lab-teal); }
|
||||
.mobile-session-list > article > div { display: grid; gap: 3px; min-width: 0; }
|
||||
.mobile-session-list strong { color: #304d5d; font-size: 12px; }
|
||||
.mobile-session-list small { overflow: hidden; color: #718792; font-size: 10px; line-height: 1.4; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.mobile-session-list .el-button { grid-column: 2; grid-row: 1 / span 2; align-self: center; min-height: 34px; }
|
||||
.mobile-session-list .el-tag { grid-column: 1; width: fit-content; }
|
||||
.mobile-cancel-booking { justify-self: start; margin-top: 2px; }
|
||||
}
|
||||
</style>
|
||||
Loaded 3 of 14 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user