1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-12-08 13:49:37 +00:00

Fix notification deletion bug: save {} instead of [] when list is empty

When all notifications are deleted, the persistentNotifications array
becomes empty ([]). The backend doesn't accept an empty array as a
valid value, requiring an empty object ({}) instead. This fix modifies
both index.vue and UrgentTestDialog.vue to save {} when the
notification list is empty.

Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-01 10:16:47 +00:00
parent 65b08653c8
commit 056225b6b3
64 changed files with 15281 additions and 1001 deletions

149
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,149 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp
const customRef: typeof import('vue').customRef
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const effectScope: typeof import('vue').effectScope
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const inject: typeof import('vue').inject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const provide: typeof import('vue').provide
const reactive: typeof import('vue').reactive
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue').resolveComponent
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const toRaw: typeof import('vue').toRaw
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const unref: typeof import('vue').unref
const useAttrs: typeof import('vue').useAttrs
const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue').useCssVars
const useId: typeof import('vue').useId
const useLink: typeof import('vue-router').useLink
const useModel: typeof import('vue').useModel
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSlots: typeof import('vue').useSlots
const useTemplateRef: typeof import('vue').useTemplateRef
const watch: typeof import('vue').watch
const watchEffect: typeof import('vue').watchEffect
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useId: UnwrapRef<typeof import('vue')['useId']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}

67
components.d.ts vendored Normal file
View File

@ -0,0 +1,67 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
404: typeof import('./src/components/error/404.vue')['default']
AboutCard: typeof import('./src/components/settings/AboutCard.vue')['default']
AlternativeCodeDialog: typeof import('./src/components/auth/AlternativeCodeDialog.vue')['default']
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
AttendanceManagementDialog: typeof import('./src/components/attendance/AttendanceManagementDialog.vue')['default']
AttendanceSidebar: typeof import('./src/components/attendance/AttendanceSidebar.vue')['default']
CacheManager: typeof import('./src/components/CacheManager.vue')['default']
ChatWidget: typeof import('./src/components/ChatWidget.vue')['default']
CloudNamespaceInfoCard: typeof import('./src/components/settings/cards/CloudNamespaceInfoCard.vue')['default']
DataProviderSettingsCard: typeof import('./src/components/settings/cards/DataProviderSettingsCard.vue')['default']
DeviceAuthDialog: typeof import('./src/components/auth/DeviceAuthDialog.vue')['default']
DisplaySettingsCard: typeof import('./src/components/settings/cards/DisplaySettingsCard.vue')['default']
EchoChamberCard: typeof import('./src/components/settings/cards/EchoChamberCard.vue')['default']
EditSettingsCard: typeof import('./src/components/settings/cards/EditSettingsCard.vue')['default']
EventSender: typeof import('./src/components/EventSender.vue')['default']
ExamConfigEditor: typeof import('./src/components/ExamConfigEditor.vue')['default']
FirstTimeGuide: typeof import('./src/components/auth/FirstTimeGuide.vue')['default']
FloatingICP: typeof import('./src/components/FloatingICP.vue')['default']
FloatingToolbar: typeof import('./src/components/FloatingToolbar.vue')['default']
GlobalMessage: typeof import('./src/components/GlobalMessage.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
HomeActions: typeof import('./src/components/home/HomeActions.vue')['default']
HomeworkEditDialog: typeof import('./src/components/HomeworkEditDialog.vue')['default']
HomeworkGrid: typeof import('./src/components/home/HomeworkGrid.vue')['default']
HomeworkTemplateCard: typeof import('./src/components/settings/cards/HomeworkTemplateCard.vue')['default']
InitServiceChooser: typeof import('./src/components/InitServiceChooser.vue')['default']
KvDatabaseCard: typeof import('./src/components/settings/cards/KvDatabaseCard.vue')['default']
KvInitialize: typeof import('./src/components/KvInitialize.vue')['default']
MessageLog: typeof import('./src/components/MessageLog.vue')['default']
MigrationTool: typeof import('./src/components/MigrationTool.vue')['default']
ProgressiveRegisterPage: typeof import('./src/components/auth/ProgressiveRegisterPage.vue')['default']
RandomPicker: typeof import('./src/components/RandomPicker.vue')['default']
RandomPickerCard: typeof import('./src/components/settings/cards/RandomPickerCard.vue')['default']
RateLimitModal: typeof import('./src/components/RateLimitModal.vue')['default']
ReadOnlyTokenWarning: typeof import('./src/components/ReadOnlyTokenWarning.vue')['default']
RefreshSettingsCard: typeof import('./src/components/settings/cards/RefreshSettingsCard.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ServerSettingsCard: typeof import('./src/components/settings/cards/ServerSettingsCard.vue')['default']
SettingGroup: typeof import('./src/components/settings/SettingGroup.vue')['default']
SettingItem: typeof import('./src/components/settings/SettingItem.vue')['default']
SettingsCard: typeof import('./src/components/SettingsCard.vue')['default']
SettingsExplorer: typeof import('./src/components/settings/SettingsExplorer.vue')['default']
SettingsLinkGenerator: typeof import('./src/components/SettingsLinkGenerator.vue')['default']
StudentListCard: typeof import('./src/components/settings/StudentListCard.vue')['default']
StudentNameManager: typeof import('./src/components/StudentNameManager.vue')['default']
SubjectManagementCard: typeof import('./src/components/settings/cards/SubjectManagementCard.vue')['default']
ThemeSettingsCard: typeof import('./src/components/settings/cards/ThemeSettingsCard.vue')['default']
TokenInputDialog: typeof import('./src/components/auth/TokenInputDialog.vue')['default']
UnsavedWarning: typeof import('./src/components/common/UnsavedWarning.vue')['default']
UrgentNotification: typeof import('./src/components/UrgentNotification.vue')['default']
UrgentTestDialog: typeof import('./src/components/UrgentTestDialog.vue')['default']
}
}

11995
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,14 @@
<v-app> <v-app>
<!-- 正常路由 --> <!-- 正常路由 -->
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition mode="out-in" name="md3"> <transition
<component :is="Component" :key="route.path" /> mode="out-in"
name="md3"
>
<component
:is="Component"
:key="route.path"
/>
</transition> </transition>
</router-view> </router-view>
<global-message /> <global-message />

View File

@ -1,7 +1,7 @@
<template> <template>
<v-app-bar :elevation="2"> <v-app-bar :elevation="2">
<template v-slot:prepend> <template #prepend>
<v-app-bar-nav-icon></v-app-bar-nav-icon> <v-app-bar-nav-icon />
</template> </template>
<v-app-bar-title>作业</v-app-bar-title> <v-app-bar-title>作业</v-app-bar-title>

View File

@ -2,48 +2,85 @@
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<span>缓存管理</span> <span>缓存管理</span>
<v-spacer></v-spacer> <v-spacer />
<v-btn :loading="loading" color="error" @click="clearAllCaches"> <v-btn
:loading="loading"
color="error"
@click="clearAllCaches"
>
清除所有缓存 清除所有缓存
</v-btn> </v-btn>
<v-btn class="ml-2" icon @click="refreshCaches"> <v-btn
class="ml-2"
icon
@click="refreshCaches"
>
<v-icon>mdi-refresh</v-icon> <v-icon>mdi-refresh</v-icon>
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-alert v-if="!serviceWorkerActive" class="mb-4" type="warning"> <v-alert
v-if="!serviceWorkerActive"
class="mb-4"
type="warning"
>
Service Worker 未激活缓存管理功能不可用 Service Worker 未激活缓存管理功能不可用
</v-alert> </v-alert>
<v-alert v-if="message" :type="messageType" class="mb-4"> <v-alert
v-if="message"
:type="messageType"
class="mb-4"
>
{{ message }} {{ message }}
</v-alert> </v-alert>
<v-expansion-panels v-if="caches.length > 0"> <v-expansion-panels v-if="caches.length > 0">
<v-expansion-panel v-for="cache in caches" :key="cache.name"> <v-expansion-panel
v-for="cache in caches"
:key="cache.name"
>
<v-expansion-panel-title> <v-expansion-panel-title>
<div class="d-flex align-center"> <div class="d-flex align-center">
<span>{{ formatCacheName(cache.name) }}</span> <span>{{ formatCacheName(cache.name) }}</span>
<v-chip class="ml-2" size="small">{{ cache.urls.length }} 个文件</v-chip> <v-chip
class="ml-2"
size="small"
>
{{ cache.urls.length }} 个文件
</v-chip>
</div> </div>
</v-expansion-panel-title> </v-expansion-panel-title>
<v-expansion-panel-text> <v-expansion-panel-text>
<div class="d-flex justify-end mb-2"> <div class="d-flex justify-end mb-2">
<v-btn :loading="loading" color="error" size="small" @click="clearCache(cache.name)"> <v-btn
:loading="loading"
color="error"
size="small"
@click="clearCache(cache.name)"
>
清除此缓存 清除此缓存
</v-btn> </v-btn>
</div> </div>
<v-list lines="two"> <v-list lines="two">
<v-list-item v-for="(url, index) in cache.urls" :key="index"> <v-list-item
v-for="(url, index) in cache.urls"
:key="index"
>
<v-list-item-title class="text-truncate"> <v-list-item-title class="text-truncate">
{{ getFileName(url) }} {{ getFileName(url) }}
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle class="text-truncate"> <v-list-item-subtitle class="text-truncate">
{{ url }} {{ url }}
</v-list-item-subtitle> </v-list-item-subtitle>
<template v-slot:append> <template #append>
<v-btn color="error" icon size="small" @click="clearUrl(cache.name, url)"> <v-btn
color="error"
icon
size="small"
@click="clearUrl(cache.name, url)"
>
<v-icon>mdi-delete</v-icon> <v-icon>mdi-delete</v-icon>
</v-btn> </v-btn>
</template> </template>
@ -53,9 +90,15 @@
</v-expansion-panel> </v-expansion-panel>
</v-expansion-panels> </v-expansion-panels>
<v-skeleton-loader v-else-if="loading" type="article"/> <v-skeleton-loader
v-else-if="loading"
type="article"
/>
<v-alert v-else type="info"> <v-alert
v-else
type="info"
>
没有找到缓存数据 没有找到缓存数据
</v-alert> </v-alert>
</v-card-text> </v-card-text>

View File

@ -11,7 +11,9 @@
@click:close="error = ''" @click:close="error = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-alert-circle</v-icon> <v-icon class="mr-2">
mdi-alert-circle
</v-icon>
{{ error }} {{ error }}
</div> </div>
</v-alert> </v-alert>
@ -27,7 +29,9 @@
@click:close="success = ''" @click:close="success = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-check-circle</v-icon> <v-icon class="mr-2">
mdi-check-circle
</v-icon>
{{ success }} {{ success }}
</div> </div>
</v-alert> </v-alert>
@ -43,29 +47,49 @@
<div class="d-flex align-center"> <div class="d-flex align-center">
<span class="font-weight-bold">配置验证失败请检查以下问题</span> <span class="font-weight-bold">配置验证失败请检查以下问题</span>
</div> </div>
<v-list class="bg-transparent" density="compact"> <v-list
class="bg-transparent"
density="compact"
>
<v-list-item <v-list-item
v-for="(error, index) in validationErrors" v-for="(error, index) in validationErrors"
:key="index" :key="index"
class="px-0 py-0" class="px-0 py-0"
> >
<template v-slot:prepend> <template #prepend>
<v-icon color="warning" size="small">mdi-circle-small</v-icon> <v-icon
color="warning"
size="small"
>
mdi-circle-small
</v-icon>
</template> </template>
<v-list-item-title class="text-body-2">{{ error }}</v-list-item-title> <v-list-item-title class="text-body-2">
{{ error }}
</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-alert> </v-alert>
<!-- 加载状态 --> <!-- 加载状态 -->
<v-card v-if="loading" class="my-4" outlined> <v-card
v-if="loading"
class="my-4"
outlined
>
<v-card-text> <v-card-text>
<v-skeleton-loader class="mx-auto" type="article"></v-skeleton-loader> <v-skeleton-loader
class="mx-auto"
type="article"
/>
</v-card-text> </v-card-text>
</v-card> </v-card>
<!-- 模式切换按钮和操作按钮 --> <!-- 模式切换按钮和操作按钮 -->
<div v-if="!loading" class="d-flex justify-space-between align-center mb-4"> <div
v-if="!loading"
class="d-flex justify-space-between align-center mb-4"
>
<div class="d-flex align-center gap-2"> <div class="d-flex align-center gap-2">
<v-btn <v-btn
:disabled="!isValidConfig" :disabled="!isValidConfig"
@ -96,19 +120,31 @@
class="text-error" class="text-error"
prepend-icon="mdi-delete" prepend-icon="mdi-delete"
@click="confirmDelete" @click="confirmDelete"
> >
删除配置 删除配置
</v-btn> </v-btn>
<v-btn :value="false" prepend-icon="mdi-eye"> 预览</v-btn> <v-btn
<v-btn :value="true" prepend-icon="mdi-pencil"> 编辑</v-btn> :value="false"
prepend-icon="mdi-eye"
>
预览
</v-btn>
<v-btn
:value="true"
prepend-icon="mdi-pencil"
>
编辑
</v-btn>
</v-btn-toggle> </v-btn-toggle>
</div> </div>
<!-- 预览模式 --> <!-- 预览模式 -->
<div v-if="!loading && !isEditMode"> <div v-if="!loading && !isEditMode">
<div class="mb-8"> <div class="mb-8">
<div class="text-h3 font-weight-bold" style="line-height: 1.2"> <div
class="text-h3 font-weight-bold"
style="line-height: 1.2"
>
{{ localConfig.examName || "未设置考试名称" }} {{ localConfig.examName || "未设置考试名称" }}
</div> </div>
<div <div
@ -117,8 +153,14 @@
> >
{{ localConfig.message || "未设置考试提示" }} {{ localConfig.message || "未设置考试提示" }}
</div> </div>
<v-chip v-if="localConfig.room" class="px-4 py-2" size="large"> <v-chip
<v-icon start>mdi-home</v-icon> v-if="localConfig.room"
class="px-4 py-2"
size="large"
>
<v-icon start>
mdi-home
</v-icon>
考场{{ localConfig.room }} 考场{{ localConfig.room }}
</v-chip> </v-chip>
</div> </div>
@ -134,20 +176,29 @@
lg="4" lg="4"
md="6" md="6"
> >
<v-card class="h-100" hover variant="tonal"> <v-card
class="h-100"
hover
variant="tonal"
>
<v-card-title class="bg-primary-lighten-5 pa-4"> <v-card-title class="bg-primary-lighten-5 pa-4">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-book-open-page-variant</v-icon> <v-icon class="mr-2">
mdi-book-open-page-variant
</v-icon>
<span class="">{{ examInfo.name || "未设置科目" }}</span> <span class="">{{ examInfo.name || "未设置科目" }}</span>
</div> </div>
</v-card-title> </v-card-title>
<v-card-text class="pa-4"> <v-card-text class="pa-4">
<div class="mb-3"> <div class="mb-3">
<div class="d-flex align-center mb-1"> <div class="d-flex align-center mb-1">
<v-icon class="mr-2" color="success" size="small" <v-icon
>mdi-clock-start class="mr-2"
</v-icon color="success"
size="small"
> >
mdi-clock-start
</v-icon>
<span class="text-body-2 text-grey-darken-1">开始时间</span> <span class="text-body-2 text-grey-darken-1">开始时间</span>
</div> </div>
<div class="text-h6 font-weight-medium text-success"> <div class="text-h6 font-weight-medium text-success">
@ -156,10 +207,13 @@
</div> </div>
<div> <div>
<div class="d-flex align-center mb-1"> <div class="d-flex align-center mb-1">
<v-icon class="mr-2" color="error" size="small" <v-icon
>mdi-clock-end class="mr-2"
</v-icon color="error"
size="small"
> >
mdi-clock-end
</v-icon>
<span class="text-body-2 text-grey-darken-1">结束时间</span> <span class="text-body-2 text-grey-darken-1">结束时间</span>
</div> </div>
<div class="text-h6 font-weight-medium text-error"> <div class="text-h6 font-weight-medium text-error">
@ -171,29 +225,50 @@
</v-col> </v-col>
</v-row> </v-row>
</div> </div>
<div v-else class="text-center py-12"> <div
<v-icon class="mb-4" color="grey-lighten-2" size="80"> v-else
class="text-center py-12"
>
<v-icon
class="mb-4"
color="grey-lighten-2"
size="80"
>
mdi-calendar-blank mdi-calendar-blank
</v-icon> </v-icon>
<div class="text-h5 text-grey-darken-1 mb-2">暂无考试科目安排</div> <div class="text-h5 text-grey-darken-1 mb-2">
暂无考试科目安排
</div>
<div class="text-body-1 text-grey mb-4"> <div class="text-body-1 text-grey mb-4">
点击上方"添加科目"按钮开始配置考试时间表 点击上方"添加科目"按钮开始配置考试时间表
</div> </div>
<v-btn color="primary" variant="outlined" @click="quickEdit"> <v-btn
<v-icon start>mdi-plus</v-icon> color="primary"
variant="outlined"
@click="quickEdit"
>
<v-icon start>
mdi-plus
</v-icon>
立即添加 立即添加
</v-btn> </v-btn>
</div> </div>
<!-- JSON预览 --> <!-- JSON预览 -->
<v-card border class="mb-4" elevation="2"> <v-card
border
class="mb-4"
elevation="2"
>
<v-card-title <v-card-title
class="d-flex align-center text-white cursor-pointer" class="d-flex align-center text-white cursor-pointer"
@click="showJsonPreview = !showJsonPreview" @click="showJsonPreview = !showJsonPreview"
> >
<v-icon class="mr-2">mdi-code-json</v-icon> <v-icon class="mr-2">
mdi-code-json
</v-icon>
JSON配置预览 JSON配置预览
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="white" color="white"
prepend-icon="mdi-content-copy" prepend-icon="mdi-content-copy"
@ -209,12 +284,17 @@
color="white" color="white"
size="small" size="small"
variant="text" variant="text"
> />
</v-btn>
</v-card-title> </v-card-title>
<v-expand-transition> <v-expand-transition>
<v-card-text v-show="showJsonPreview" class="pa-4"> <v-card-text
<v-card class="pa-4" variant="tonal"> v-show="showJsonPreview"
class="pa-4"
>
<v-card
class="pa-4"
variant="tonal"
>
<pre class="json-preview"><code>{{ formattedJson }}</code></pre> <pre class="json-preview"><code>{{ formattedJson }}</code></pre>
</v-card> </v-card>
</v-card-text> </v-card-text>
@ -225,14 +305,23 @@
<!-- 编辑模式 --> <!-- 编辑模式 -->
<div v-if="!loading && isEditMode"> <div v-if="!loading && isEditMode">
<!-- 基本信息 --> <!-- 基本信息 -->
<v-card border class="mb-4" elevation="1"> <v-card
border
class="mb-4"
elevation="1"
>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2">mdi-information</v-icon> <v-icon class="mr-2">
mdi-information
</v-icon>
基本信息 基本信息
</v-card-title> </v-card-title>
<v-card-text class="pa-4"> <v-card-text class="pa-4">
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="localConfig.examName" v-model="localConfig.examName"
:rules="[(v) => !!v || '考试名称不能为空']" :rules="[(v) => !!v || '考试名称不能为空']"
@ -240,15 +329,18 @@
prepend-inner-icon="mdi-calendar-text" prepend-inner-icon="mdi-calendar-text"
required required
variant="outlined" variant="outlined"
></v-text-field> />
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="localConfig.room" v-model="localConfig.room"
label="考场号标准ES尚不支持此配置" label="考场号标准ES尚不支持此配置"
prepend-inner-icon="mdi-home" prepend-inner-icon="mdi-home"
variant="outlined" variant="outlined"
></v-text-field> />
</v-col> </v-col>
</v-row> </v-row>
<v-textarea <v-textarea
@ -258,7 +350,7 @@
prepend-inner-icon="mdi-message-text" prepend-inner-icon="mdi-message-text"
rows="3" rows="3"
variant="outlined" variant="outlined"
></v-textarea> />
<!-- 默认提示选项 --> <!-- 默认提示选项 -->
<v-chip-group <v-chip-group
@ -275,23 +367,39 @@
variant="outlined" variant="outlined"
@click="selectDefaultTip(tip)" @click="selectDefaultTip(tip)"
> >
<v-icon size="small" start>mdi-plus</v-icon> <v-icon
size="small"
start
>
mdi-plus
</v-icon>
{{ tip }} {{ tip }}
</v-chip> </v-chip>
</v-chip-group> </v-chip-group>
<div class="text-caption text-medium-emphasis ml-2"> <div class="text-caption text-medium-emphasis ml-2">
<v-icon class="mr-1" size="small">mdi-lightbulb-outline</v-icon> <v-icon
class="mr-1"
size="small"
>
mdi-lightbulb-outline
</v-icon>
点击上方选项快速添加常用考试提示 点击上方选项快速添加常用考试提示
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
<!-- 考试科目安排 --> <!-- 考试科目安排 -->
<v-card border class="mb-4" elevation="1"> <v-card
border
class="mb-4"
elevation="1"
>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2">mdi-format-list-bulleted</v-icon> <v-icon class="mr-2">
mdi-format-list-bulleted
</v-icon>
考试科目安排 考试科目安排
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="primary" color="primary"
prepend-icon="mdi-plus" prepend-icon="mdi-plus"
@ -312,7 +420,10 @@
> >
<div class="w-100"> <div class="w-100">
<v-row> <v-row>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="examInfo.name" v-model="examInfo.name"
:rules="[(v) => !!v || '科目名称不能为空']" :rules="[(v) => !!v || '科目名称不能为空']"
@ -320,9 +431,12 @@
label="科目名称" label="科目名称"
prepend-inner-icon="mdi-book" prepend-inner-icon="mdi-book"
variant="outlined" variant="outlined"
></v-text-field> />
</v-col> </v-col>
<v-col cols="12" md="3"> <v-col
cols="12"
md="3"
>
<v-menu <v-menu
v-model="examInfo.startDateMenu" v-model="examInfo.startDateMenu"
:close-on-content-click="false" :close-on-content-click="false"
@ -330,7 +444,7 @@
offset-y offset-y
transition="scale-transition" transition="scale-transition"
> >
<template v-slot:activator="{ props }"> <template #activator="{ props }">
<v-text-field <v-text-field
v-model="examInfo.startFormatted" v-model="examInfo.startFormatted"
:rules="[(v) => !!v || '开始时间不能为空']" :rules="[(v) => !!v || '开始时间不能为空']"
@ -340,7 +454,7 @@
readonly readonly
v-bind="props" v-bind="props"
variant="outlined" variant="outlined"
></v-text-field> />
</template> </template>
<v-card min-width="600"> <v-card min-width="600">
<v-card-title class="text-center py-2"> <v-card-title class="text-center py-2">
@ -348,7 +462,10 @@
</v-card-title> </v-card-title>
<v-card-text class="pa-0"> <v-card-text class="pa-0">
<v-row no-gutters> <v-row no-gutters>
<v-col class="border-e" cols="6"> <v-col
class="border-e"
cols="6"
>
<v-date-picker <v-date-picker
v-model="examInfo.startDate" v-model="examInfo.startDate"
color="primary" color="primary"
@ -356,7 +473,7 @@
locale="zh-cn" locale="zh-cn"
show-adjacent-months show-adjacent-months
@update:model-value="updateStartDateTime(index)" @update:model-value="updateStartDateTime(index)"
></v-date-picker> />
</v-col> </v-col>
<v-col cols="6"> <v-col cols="6">
<v-time-picker <v-time-picker
@ -366,12 +483,12 @@
format="24hr" format="24hr"
scrollable scrollable
@update:model-value="updateStartDateTime(index)" @update:model-value="updateStartDateTime(index)"
></v-time-picker> />
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="grey" color="grey"
variant="text" variant="text"
@ -390,7 +507,10 @@
</v-card> </v-card>
</v-menu> </v-menu>
</v-col> </v-col>
<v-col cols="12" md="3"> <v-col
cols="12"
md="3"
>
<v-menu <v-menu
v-model="examInfo.endDateMenu" v-model="examInfo.endDateMenu"
:close-on-content-click="false" :close-on-content-click="false"
@ -398,7 +518,7 @@
offset-y offset-y
transition="scale-transition" transition="scale-transition"
> >
<template v-slot:activator="{ props }"> <template #activator="{ props }">
<v-text-field <v-text-field
v-model="examInfo.endFormatted" v-model="examInfo.endFormatted"
:rules="[(v) => !!v || '结束时间不能为空']" :rules="[(v) => !!v || '结束时间不能为空']"
@ -408,7 +528,7 @@
readonly readonly
v-bind="props" v-bind="props"
variant="outlined" variant="outlined"
></v-text-field> />
</template> </template>
<v-card min-width="600"> <v-card min-width="600">
<v-card-title class="text-center py-2"> <v-card-title class="text-center py-2">
@ -416,7 +536,10 @@
</v-card-title> </v-card-title>
<v-card-text class="pa-0"> <v-card-text class="pa-0">
<v-row no-gutters> <v-row no-gutters>
<v-col class="border-e" cols="6"> <v-col
class="border-e"
cols="6"
>
<v-date-picker <v-date-picker
v-model="examInfo.endDate" v-model="examInfo.endDate"
color="primary" color="primary"
@ -424,7 +547,7 @@
locale="zh-cn" locale="zh-cn"
show-adjacent-months show-adjacent-months
@update:model-value="updateEndDateTime(index)" @update:model-value="updateEndDateTime(index)"
></v-date-picker> />
</v-col> </v-col>
<v-col cols="6"> <v-col cols="6">
<v-time-picker <v-time-picker
@ -434,12 +557,12 @@
format="24hr" format="24hr"
scrollable scrollable
@update:model-value="updateEndDateTime(index)" @update:model-value="updateEndDateTime(index)"
></v-time-picker> />
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="grey" color="grey"
variant="text" variant="text"
@ -458,7 +581,11 @@
</v-card> </v-card>
</v-menu> </v-menu>
</v-col> </v-col>
<v-col class="d-flex align-center" cols="12" md="2"> <v-col
class="d-flex align-center"
cols="12"
md="2"
>
<v-btn <v-btn
color="error" color="error"
icon="mdi-delete" icon="mdi-delete"
@ -493,14 +620,25 @@
</div> </div>
</v-list-item> </v-list-item>
</v-list> </v-list>
<div v-else class="text-center py-8"> <div
<v-icon class="mb-2" color="grey-lighten-1" size="48"> v-else
class="text-center py-8"
>
<v-icon
class="mb-2"
color="grey-lighten-1"
size="48"
>
mdi-book-plus mdi-book-plus
</v-icon> </v-icon>
<p class="text-body-2 text-grey-darken-1 mb-4"> <p class="text-body-2 text-grey-darken-1 mb-4">
暂无考试科目点击"添加科目"按钮开始添加 暂无考试科目点击"添加科目"按钮开始添加
</p> </p>
<v-btn color="primary" prepend-icon="mdi-plus" @click="addExamInfo"> <v-btn
color="primary"
prepend-icon="mdi-plus"
@click="addExamInfo"
>
添加科目 添加科目
</v-btn> </v-btn>
</div> </div>
@ -509,10 +647,18 @@
</div> </div>
<!-- 删除确认对话框 --> <!-- 删除确认对话框 -->
<v-dialog v-model="deleteDialog" max-width="400"> <v-dialog
v-model="deleteDialog"
max-width="400"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" color="error">mdi-delete-alert</v-icon> <v-icon
class="mr-2"
color="error"
>
mdi-delete-alert
</v-icon>
确认删除配置 确认删除配置
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
@ -520,7 +666,7 @@
<br><small class="text-grey">此操作不可撤销将会删除所有相关数据</small> <br><small class="text-grey">此操作不可撤销将会删除所有相关数据</small>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="grey" color="grey"
variant="text" variant="text"

View File

@ -7,7 +7,10 @@
elevation="4" elevation="4"
rounded="xl" rounded="xl"
> >
<v-btn-group class="toolbar-buttons" variant="text"> <v-btn-group
class="toolbar-buttons"
variant="text"
>
<v-btn <v-btn
v-ripple v-ripple
:title="'查看昨天'" :title="'查看昨天'"
@ -32,7 +35,10 @@
variant="text" variant="text"
@click="$emit('zoom', 'up')" @click="$emit('zoom', 'up')"
/> />
<v-menu :close-on-content-click="false" location="top"> <v-menu
:close-on-content-click="false"
location="top"
>
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
v-ripple v-ripple
@ -43,7 +49,10 @@
variant="text" variant="text"
/> />
</template> </template>
<v-card border class="date-picker-card"> <v-card
border
class="date-picker-card"
>
<v-date-picker <v-date-picker
:model-value="selectedDate" :model-value="selectedDate"
color="primary" color="primary"
@ -88,7 +97,9 @@
size="large" size="large"
text="复制作业内容到今天" text="复制作业内容到今天"
@click="$emit('copy-to-today')" @click="$emit('copy-to-today')"
>复制到今天</v-btn> >
复制到今天
</v-btn>
</v-slide-x-reverse-transition> </v-slide-x-reverse-transition>
</div> </div>
</template> </template>

View File

@ -8,14 +8,28 @@
variant="tonal" variant="tonal"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon :icon="icons[message?.type] || icons.info" class="mr-2"/> <v-icon
:icon="icons[message?.type] || icons.info"
class="mr-2"
/>
<div> <div>
<div class="text-subtitle-2 font-weight-medium">{{ message?.title }}</div> <div class="text-subtitle-2 font-weight-medium">
<div v-if="message?.content" class="text-body-2">{{ message?.content }}</div> {{ message?.title }}
</div>
<div
v-if="message?.content"
class="text-body-2"
>
{{ message?.content }}
</div>
</div> </div>
</div> </div>
<template #actions> <template #actions>
<v-btn icon="mdi-close" variant="text" @click="snackbar = false"/> <v-btn
icon="mdi-close"
variant="text"
@click="snackbar = false"
/>
</template> </template>
</v-snackbar> </v-snackbar>
</template> </template>

View File

@ -11,12 +11,16 @@
/> />
<div class="text-center"> <div class="text-center">
<div class="text-body-2 font-weight-light mb-n1">Welcome to</div> <div class="text-body-2 font-weight-light mb-n1">
Welcome to
</div>
<h1 class="text-h2 font-weight-bold">Vuetify</h1> <h1 class="text-h2 font-weight-bold">
Vuetify
</h1>
</div> </div>
<div class="py-4"/> <div class="py-4" />
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
@ -29,17 +33,20 @@
variant="outlined" variant="outlined"
> >
<template #image> <template #image>
<v-img position="top right"/> <v-img position="top right" />
</template> </template>
<template #title> <template #title>
<h2 class="text-h5 font-weight-bold">Get started</h2> <h2 class="text-h5 font-weight-bold">
Get started
</h2>
</template> </template>
<template #subtitle> <template #subtitle>
<div class="text-subtitle-1"> <div class="text-subtitle-1">
Replace this page by removing Replace this page by removing
<v-kbd>{{ ` <v-kbd>
{{ `
<HelloWorld/> <HelloWorld/>
` }} ` }}
</v-kbd> </v-kbd>

View File

@ -11,7 +11,11 @@
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
{{ title }} {{ title }}
<v-spacer /> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="handleClose" /> <v-btn
icon="mdi-close"
variant="text"
@click="handleClose"
/>
</v-card-title> </v-card-title>
<v-card-subtitle> <v-card-subtitle>
{{ autoSave ? autoSavePromptText : manualSavePromptText }} {{ autoSave ? autoSavePromptText : manualSavePromptText }}
@ -31,13 +35,21 @@
/> />
<!-- Template Buttons Section --> <!-- Template Buttons Section -->
<div v-if="templateData" class="mt-4"> <div
<div v-if="hasTemplates" class="template-buttons"> v-if="templateData"
class="mt-4"
>
<div
v-if="hasTemplates"
class="template-buttons"
>
<!-- Subject specific books --> <!-- Subject specific books -->
<template v-if="subjectBooks"> <template v-if="subjectBooks">
<div v-for="(pages, book) in subjectBooks" :key="book" class="button-group"> <div
v-for="(pages, book) in subjectBooks"
:key="book"
class="button-group"
>
<v-chip <v-chip
:color="isBookSelected(book) ? 'success' : 'default'" :color="isBookSelected(book) ? 'success' : 'default'"
:variant="isBookSelected(book) ? 'elevated' : 'flat'" :variant="isBookSelected(book) ? 'elevated' : 'flat'"
@ -48,7 +60,10 @@
</v-chip> </v-chip>
<!-- Show pages only if book is selected --> <!-- Show pages only if book is selected -->
<div v-if="isBookSelected(book)" class="pages-container mt-2"> <div
v-if="isBookSelected(book)"
class="pages-container mt-2"
>
<v-chip <v-chip
v-for="page in pages" v-for="page in pages"
:key="page" :key="page"
@ -65,7 +80,11 @@
<!-- Common books --> <!-- Common books -->
<template v-if="commonBooks"> <template v-if="commonBooks">
<div v-for="(pages, book) in commonBooks" :key="book" class="button-group"> <div
v-for="(pages, book) in commonBooks"
:key="book"
class="button-group"
>
<v-chip <v-chip
:color="isBookSelected(book) ? 'success' : 'default'" :color="isBookSelected(book) ? 'success' : 'default'"
:variant="isBookSelected(book) ? 'elevated' : 'flat'" :variant="isBookSelected(book) ? 'elevated' : 'flat'"
@ -76,7 +95,10 @@
</v-chip> </v-chip>
<!-- Show pages only if book is selected --> <!-- Show pages only if book is selected -->
<div v-if="isBookSelected(book)" class="pages-container mt-2"> <div
v-if="isBookSelected(book)"
class="pages-container mt-2"
>
<v-chip <v-chip
v-for="page in pages" v-for="page in pages"
:key="page" :key="page"
@ -92,7 +114,10 @@
</template> </template>
<!-- Actions --> <!-- Actions -->
<div v-if="templateData.actions?.length" class="button-group"> <div
v-if="templateData.actions?.length"
class="button-group"
>
<v-chip <v-chip
v-for="action in templateData.actions" v-for="action in templateData.actions"
:key="action" :key="action"
@ -105,14 +130,21 @@
</v-chip> </v-chip>
</div> </div>
</div> </div>
<div v-else class="text-center text-body-2 text-disabled mt-2"> <div
v-else
class="text-center text-body-2 text-disabled mt-2"
>
暂无可用的模板 暂无可用的模板
</div> </div>
</div> </div>
</div> </div>
<!-- Quick Tools Section --> <!-- Quick Tools Section -->
<div v-if="showQuickTools && !isMobile" class="quick-tools ml-4" style="min-width: 180px;"> <div
v-if="showQuickTools && !isMobile"
class="quick-tools ml-4"
style="min-width: 180px;"
>
<!-- Numeric Keypad --> <!-- Numeric Keypad -->
<div class="numeric-keypad mb-4"> <div class="numeric-keypad mb-4">
<div class="keypad-row"> <div class="keypad-row">
@ -223,10 +255,11 @@
border-color="warning" border-color="warning"
prominent prominent
> >
<template #prepend> <template #prepend />
</template>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="text-h6 mb-1">你打算修改历史</div> <div class="text-h6 mb-1">
你打算修改历史
</div>
<div class="text-body-2"> <div class="text-body-2">
这是 {{ new Date(currentDateString.slice(0,4), currentDateString.slice(4,6)-1, currentDateString.slice(6,8)).toLocaleDateString() }} 的作业 请谨慎操作确保不会覆盖重要数据 这是 {{ new Date(currentDateString.slice(0,4), currentDateString.slice(4,6)-1, currentDateString.slice(6,8)).toLocaleDateString() }} 的作业 请谨慎操作确保不会覆盖重要数据
</div> </div>
@ -236,7 +269,6 @@
<div class="text-center text-body-2 text-disabled mb-5"> <div class="text-center text-body-2 text-disabled mb-5">
点击空白处完成编辑 点击空白处完成编辑
</div> </div>
</v-card> </v-card>
</v-dialog> </v-dialog>
</template> </template>

View File

@ -1,33 +1,50 @@
<template> <template>
<v-navigation-drawer v-if="drawer" v-model="drawer" location="right" temporary width="400"> <v-navigation-drawer
v-if="drawer"
v-model="drawer"
location="right"
temporary
width="400"
>
<v-toolbar color="primary"> <v-toolbar color="primary">
<v-toolbar-title>消息记录</v-toolbar-title> <v-toolbar-title>消息记录</v-toolbar-title>
</v-toolbar> </v-toolbar>
<v-list> <v-list>
<v-list-item v-for="msg in messages" :key="msg.id" rounded> <v-list-item
v-for="msg in messages"
:key="msg.id"
rounded
>
<template #prepend> <template #prepend>
<v-icon :color="colors[msg.type]" :icon="icons[msg.type]" size="20"/> <v-icon
:color="colors[msg.type]"
:icon="icons[msg.type]"
size="20"
/>
</template> </template>
<v-list-item-title>{{ msg.title }}</v-list-item-title> <v-list-item-title>{{ msg.title }}</v-list-item-title>
<v-list-item-subtitle v-if="msg.content">{{ <v-list-item-subtitle v-if="msg.content">
msg.content {{
msg.content
}} }}
</v-list-item-subtitle> </v-list-item-subtitle>
<span class="text-caption text-grey"> <span class="text-caption text-grey">
{{ new Date(msg.timestamp).toLocaleTimeString() }} {{ new Date(msg.timestamp).toLocaleTimeString() }}
</span> </span>
</v-list-item> </v-list-item>
<v-list-item v-if="!messages.length"> <v-list-item v-if="!messages.length">
<template #prepend> <template #prepend>
<v-icon color="grey" icon="mdi-inbox"/> <v-icon
color="grey"
icon="mdi-inbox"
/>
</template> </template>
<v-list-item-title class="text-grey">暂无消息</v-list-item-title> <v-list-item-title class="text-grey">
暂无消息
</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>

View File

@ -4,17 +4,23 @@
<v-card-title>迁移设置</v-card-title> <v-card-title>迁移设置</v-card-title>
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="classNumber" v-model="classNumber"
hint="请输入需要迁移的班级编号" hint="请输入需要迁移的班级编号"
label="班级编号" label="班级编号"
persistent-hint persistent-hint
prepend-icon="mdi-account-group" prepend-icon="mdi-account-group"
></v-text-field> />
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="machineId" v-model="machineId"
hint="系统已自动填充设备标识,通常无需修改" hint="系统已自动填充设备标识,通常无需修改"
@ -22,23 +28,35 @@
persistent-hint persistent-hint
prepend-icon="mdi-identifier" prepend-icon="mdi-identifier"
readonly readonly
></v-text-field> />
</v-col> </v-col>
</v-row> </v-row>
<v-radio-group v-model="migrationType" class="mt-2"> <v-radio-group
<v-radio label="本地数据迁移" value="local"></v-radio> v-model="migrationType"
<v-radio label="服务器数据迁移" value="server"></v-radio> class="mt-2"
>
<v-radio
label="本地数据迁移"
value="local"
/>
<v-radio
label="服务器数据迁移"
value="server"
/>
</v-radio-group> </v-radio-group>
<div v-if="migrationType === 'server'" class="mt-4"> <div
v-if="migrationType === 'server'"
class="mt-4"
>
<v-text-field <v-text-field
v-model="serverUrl" v-model="serverUrl"
hint="输入服务器域名例如https://example.com" hint="输入服务器域名例如https://example.com"
label="服务器地址" label="服务器地址"
persistent-hint persistent-hint
prepend-icon="mdi-server" prepend-icon="mdi-server"
></v-text-field> />
<v-alert <v-alert
class="mt-2" class="mt-2"
@ -46,33 +64,44 @@
type="info" type="info"
variant="outlined" variant="outlined"
> >
服务器接口格式<br/> 服务器接口格式<br>
- 配置接口域名/班号/config<br/> - 配置接口域名/班号/config<br>
- 作业数据接口域名/班号/homework?date=YYYY-MM-DD - 作业数据接口域名/班号/homework?date=YYYY-MM-DD
</v-alert> </v-alert>
<div class="d-flex align-center mt-4"> <div class="d-flex align-center mt-4">
<v-icon class="mr-2" color="warning">mdi-calendar-range</v-icon> <v-icon
class="mr-2"
color="warning"
>
mdi-calendar-range
</v-icon>
<span class="text-subtitle-1">选择迁移时间范围</span> <span class="text-subtitle-1">选择迁移时间范围</span>
</div> </div>
<v-row class="mt-1"> <v-row class="mt-1">
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="startDate" v-model="startDate"
label="开始日期" label="开始日期"
prepend-icon="mdi-calendar-start" prepend-icon="mdi-calendar-start"
type="date" type="date"
></v-text-field> />
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="endDate" v-model="endDate"
label="结束日期" label="结束日期"
prepend-icon="mdi-calendar-end" prepend-icon="mdi-calendar-end"
type="date" type="date"
></v-text-field> />
</v-col> </v-col>
</v-row> </v-row>
</div> </div>
@ -85,7 +114,7 @@
<span>{{ <span>{{
migrationType === "local" ? "本地数据库内容" : "服务器数据内容" migrationType === "local" ? "本地数据库内容" : "服务器数据内容"
}}</span> }}</span>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
:loading="loading || scanning" :loading="loading || scanning"
color="primary" color="primary"
@ -104,9 +133,9 @@
type="info" type="info"
> >
{{ {{
migrationType === "local" migrationType === "local"
? '尚未扫描本地数据或未找到可迁移的数据。点击"扫描数据"按钮开始扫描。' ? '尚未扫描本地数据或未找到可迁移的数据。点击"扫描数据"按钮开始扫描。'
: '尚未预览服务器数据或未找到可迁移的数据。点击"加载数据"按钮开始查询。' : '尚未预览服务器数据或未找到可迁移的数据。点击"加载数据"按钮开始查询。'
}} }}
</v-alert> </v-alert>
@ -144,7 +173,7 @@
<v-skeleton-loader <v-skeleton-loader
v-if="loading || scanning" v-if="loading || scanning"
type="table" type="table"
></v-skeleton-loader> />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -152,18 +181,27 @@
<v-card-title>迁移目标</v-card-title> <v-card-title>迁移目标</v-card-title>
<v-card-text> <v-card-text>
<v-radio-group v-model="targetStorage"> <v-radio-group v-model="targetStorage">
<v-radio label="本地 KV 存储" value="kv-local"></v-radio> <v-radio
<v-radio label="服务器 KV 存储" value="kv-server"></v-radio> label="本地 KV 存储"
value="kv-local"
/>
<v-radio
label="服务器 KV 存储"
value="kv-server"
/>
</v-radio-group> </v-radio-group>
<div v-if="targetStorage === 'kv-server'" class="mt-4"> <div
v-if="targetStorage === 'kv-server'"
class="mt-4"
>
<v-text-field <v-text-field
v-model="targetServerUrl" v-model="targetServerUrl"
hint="输入KV服务器地址例如https://example.com/kv-api" hint="输入KV服务器地址例如https://example.com/kv-api"
label="目标服务器地址" label="目标服务器地址"
persistent-hint persistent-hint
prepend-icon="mdi-server-network" prepend-icon="mdi-server-network"
></v-text-field> />
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -179,16 +217,26 @@
</v-btn> </v-btn>
</div> </div>
<v-dialog v-model="showResult" max-width="600"> <v-dialog
v-model="showResult"
max-width="600"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon :color="migrationSuccess ? 'success' : 'error'" class="mr-2"> <v-icon
:color="migrationSuccess ? 'success' : 'error'"
class="mr-2"
>
{{ migrationSuccess ? "mdi-check-circle" : "mdi-alert-circle" }} {{ migrationSuccess ? "mdi-check-circle" : "mdi-alert-circle" }}
</v-icon> </v-icon>
<span>{{ migrationSuccess ? "迁移成功" : "迁移失败" }}</span> <span>{{ migrationSuccess ? "迁移成功" : "迁移失败" }}</span>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-alert v-if="migrationError" class="mb-4" type="error"> <v-alert
v-if="migrationError"
class="mb-4"
type="error"
>
{{ migrationError }} {{ migrationError }}
</v-alert> </v-alert>
@ -197,7 +245,7 @@
成功迁移 {{ migrationStats.success }} 项数据到 成功迁移 {{ migrationStats.success }} 项数据到
{{ targetStorage === "kv-local" ? "本地" : "服务器" }} KV 存储 {{ targetStorage === "kv-local" ? "本地" : "服务器" }} KV 存储
</p> </p>
<v-divider class="my-4"></v-divider> <v-divider class="my-4" />
<v-list> <v-list>
<v-list-subheader>迁移详情</v-list-subheader> <v-list-subheader>迁移详情</v-list-subheader>
<v-list-item <v-list-item
@ -215,8 +263,13 @@
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn color="primary" @click="showResult = false"> 关闭</v-btn> <v-btn
color="primary"
@click="showResult = false"
>
关闭
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@ -5,16 +5,32 @@
max-width="600" max-width="600"
persistent persistent
> >
<v-card border class="random-picker-card" rounded="xl"> <v-card
border
class="random-picker-card"
rounded="xl"
>
<v-card-title class="text-h5 d-flex align-center"> <v-card-title class="text-h5 d-flex align-center">
<v-icon class="mr-2" icon="mdi-account-question"/> <v-icon
class="mr-2"
icon="mdi-account-question"
/>
随机点名 随机点名
<v-spacer/> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="dialog = false"/> <v-btn
icon="mdi-close"
variant="text"
@click="dialog = false"
/>
</v-card-title> </v-card-title>
<v-card-text v-if="!isPickingStarted" class="text-center py-6"> <v-card-text
<div class="text-h6 mb-4">请选择抽取人数</div> v-if="!isPickingStarted"
class="text-center py-6"
>
<div class="text-h6 mb-4">
请选择抽取人数
</div>
<div class="d-flex justify-center align-center counter-container"> <div class="d-flex justify-center align-center counter-container">
<v-btn <v-btn
@ -52,14 +68,29 @@
mandatory mandatory
rounded="pill" rounded="pill"
> >
<v-btn prepend-icon="mdi-account" value="name">姓名模式</v-btn> <v-btn
<v-btn prepend-icon="mdi-numeric" value="number">学号模式</v-btn> prepend-icon="mdi-account"
value="name"
>
姓名模式
</v-btn>
<v-btn
prepend-icon="mdi-numeric"
value="number"
>
学号模式
</v-btn>
</v-btn-toggle> </v-btn-toggle>
</div> </div>
<!-- 学号范围设置 --> <!-- 学号范围设置 -->
<div v-if="pickerMode === 'number'" class="number-range-container mt-4"> <div
<div class="text-subtitle-1 mb-2">学号范围设置</div> v-if="pickerMode === 'number'"
class="number-range-container mt-4"
>
<div class="text-subtitle-1 mb-2">
学号范围设置
</div>
<div class="d-flex justify-center align-center gap-4"> <div class="d-flex justify-center align-center gap-4">
<v-text-field <v-text-field
v-model.number="minNumber" v-model.number="minNumber"
@ -98,7 +129,10 @@
</v-btn> </v-btn>
</div> </div>
<div v-if="filteredStudents.length === 0" class="mt-4 text-error"> <div
v-if="filteredStudents.length === 0"
class="mt-4 text-error"
>
<template v-if="pickerMode === 'name'"> <template v-if="pickerMode === 'name'">
没有可抽取的学生请调整过滤选项 没有可抽取的学生请调整过滤选项
</template> </template>
@ -109,8 +143,11 @@
<div class="mt-4 text-caption"> <div class="mt-4 text-caption">
当前可抽取学生: {{ filteredStudents.length }} 当前可抽取学生: {{ filteredStudents.length }}
<v-tooltip v-if="pickerMode === 'name'" location="bottom"> <v-tooltip
<template v-slot:activator="{ props }"> v-if="pickerMode === 'name'"
location="bottom"
>
<template #activator="{ props }">
<v-icon <v-icon
class="ml-1" class="ml-1"
icon="mdi-information-outline" icon="mdi-information-outline"
@ -132,7 +169,10 @@
</v-tooltip> </v-tooltip>
<!-- 添加临时过滤选项 --> <!-- 添加临时过滤选项 -->
<div v-if="pickerMode === 'name'" class="d-flex flex-wrap justify-center gap-2 mt-4"> <div
v-if="pickerMode === 'name'"
class="d-flex flex-wrap justify-center gap-2 mt-4"
>
<v-chip <v-chip
:color="tempFilters.excludeLate ? 'warning' : 'default'" :color="tempFilters.excludeLate ? 'warning' : 'default'"
:variant="tempFilters.excludeLate ? 'elevated' : 'text'" :variant="tempFilters.excludeLate ? 'elevated' : 'text'"
@ -165,8 +205,14 @@
</div> </div>
</v-card-text> </v-card-text>
<v-card-text v-else class="text-center py-6"> <v-card-text
<div v-if="isAnimating" class="animation-container"> v-else
class="text-center py-6"
>
<div
v-if="isAnimating"
class="animation-container"
>
<div class="animation-wrapper"> <div class="animation-wrapper">
<transition-group <transition-group
class="shuffle-container" class="shuffle-container"
@ -185,8 +231,13 @@
</div> </div>
</div> </div>
<div v-else class="result-container"> <div
<div class="text-h6 mb-4">抽取结果</div> v-else
class="result-container"
>
<div class="text-h6 mb-4">
抽取结果
</div>
<v-card <v-card
v-for="(student, index) in pickedStudents" v-for="(student, index) in pickedStudents"
:key="index" :key="index"

View File

@ -1,22 +1,39 @@
<template> <template>
<v-dialog v-model="isVisible" max-width="500" persistent> <v-dialog
v-model="isVisible"
max-width="500"
persistent
>
<v-card class="rate-limit-modal"> <v-card class="rate-limit-modal">
<v-card-title class="text-center pa-4 bg-error text-white"> <v-card-title class="text-center pa-4 bg-error text-white">
<v-icon class="mr-2" icon="mdi-clock-alert-outline" size="large"/> <v-icon
class="mr-2"
icon="mdi-clock-alert-outline"
size="large"
/>
请求频率超限 请求频率超限
</v-card-title> </v-card-title>
<v-card-text class="pa-6"> <v-card-text class="pa-6">
<div class="text-body-1 mb-4">您的请求过于频繁请稍后再试</div> <div class="text-body-1 mb-4">
您的请求过于频繁请稍后再试
</div>
<v-card v-if="activeRequests.length > 0" class="mb-4" flat> <v-card
v-if="activeRequests.length > 0"
class="mb-4"
flat
>
<v-card-text> <v-card-text>
<v-list <v-list
v-for="(request, index) in activeRequests" v-for="(request, index) in activeRequests"
:key="index" :key="index"
class="mb-4" class="mb-4"
> >
<v-list-item color="primary" prepend-icon="mdi-web"> <v-list-item
color="primary"
prepend-icon="mdi-web"
>
<v-list-item-title> <v-list-item-title>
等待时间: 等待时间:
<span class="text-primary font-weight-bold">{{ <span class="text-primary font-weight-bold">{{
@ -27,12 +44,11 @@
{{ request.method }} {{ request.path }} {{ request.method }} {{ request.path }}
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item> </v-list-item>
</v-list </v-list>
>
<v-divider <v-divider
v-if="index < activeRequests.length - 1" v-if="index < activeRequests.length - 1"
class="my-3" class="my-3"
></v-divider> />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -42,8 +58,14 @@
</v-card-text> </v-card-text>
<v-card-actions class="pa-4 pt-0"> <v-card-actions class="pa-4 pt-0">
<v-spacer></v-spacer> <v-spacer />
<v-btn color="primary" variant="tonal" @click="close"> 我知道了</v-btn> <v-btn
color="primary"
variant="tonal"
@click="close"
>
我知道了
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@ -9,7 +9,7 @@
@click:close="dismissed = true" @click:close="dismissed = true"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-lock-alert"/> <v-icon icon="mdi-lock-alert" />
</template> </template>
<v-alert-title>当前使用只读 Token</v-alert-title> <v-alert-title>当前使用只读 Token</v-alert-title>
<div class="text-body-2"> <div class="text-body-2">

View File

@ -1,5 +1,8 @@
<template> <template>
<v-card class="settings-card rounded-lg" elevation="2"> <v-card
class="settings-card rounded-lg"
elevation="2"
>
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon <v-icon
@ -8,7 +11,9 @@
size="large" size="large"
/> />
</template> </template>
<v-card-title class="text-h6">{{ title }}</v-card-title> <v-card-title class="text-h6">
{{ title }}
</v-card-title>
</v-card-item> </v-card-item>
<v-card-text> <v-card-text>
@ -18,11 +23,14 @@
color="primary" color="primary"
indeterminate indeterminate
/> />
<slot/> <slot />
</v-card-text> </v-card-text>
<v-card-actions v-if="$slots.actions" class="pa-4"> <v-card-actions
<slot name="actions"/> v-if="$slots.actions"
class="pa-4"
>
<slot name="actions" />
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</template> </template>

View File

@ -1,9 +1,16 @@
<template> <template>
<div> <div>
<!-- 统一链接生成器卡片 --> <!-- 统一链接生成器卡片 -->
<v-card border class="unified-link-generator"> <v-card
border
class="unified-link-generator"
>
<v-card-title class="text-h6"> <v-card-title class="text-h6">
<v-icon class="mr-2" icon="mdi-link-variant" start/> <v-icon
class="mr-2"
icon="mdi-link-variant"
start
/>
统一链接生成器 统一链接生成器
</v-card-title> </v-card-title>
@ -13,15 +20,23 @@
</div> </div>
<!-- 预配置认证信息部分 --> <!-- 预配置认证信息部分 -->
<v-card class="mb-4" variant="tonal"> <v-card
class="mb-4"
variant="tonal"
>
<v-card-title class="text-subtitle-1"> <v-card-title class="text-subtitle-1">
<v-icon start>mdi-account-key</v-icon> <v-icon start>
mdi-account-key
</v-icon>
预配置认证信息 预配置认证信息
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="preconfigForm.namespace" v-model="preconfigForm.namespace"
hint="设备的命名空间标识符" hint="设备的命名空间标识符"
@ -32,7 +47,10 @@
variant="outlined" variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col
cols="12"
md="6"
>
<v-text-field <v-text-field
v-model="preconfigForm.authCode" v-model="preconfigForm.authCode"
hint="留空则需要用户手动输入" hint="留空则需要用户手动输入"
@ -64,9 +82,19 @@
type="info" type="info"
variant="tonal" variant="tonal"
> >
<div class="text-subtitle-2 mb-2">预配置信息</div> <div class="text-subtitle-2 mb-2">
<v-chip class="mr-2 mb-1" size="small"> 预配置信息
<v-icon size="small" start>mdi-identifier</v-icon> </div>
<v-chip
class="mr-2 mb-1"
size="small"
>
<v-icon
size="small"
start
>
mdi-identifier
</v-icon>
命名空间: {{ preconfigForm.namespace }} 命名空间: {{ preconfigForm.namespace }}
</v-chip> </v-chip>
<v-chip <v-chip
@ -75,12 +103,27 @@
color="warning" color="warning"
size="small" size="small"
> >
<v-icon size="small" start>mdi-lock</v-icon> <v-icon
size="small"
start
>
mdi-lock
</v-icon>
认证码: {{ preconfigForm.authCode.length > 8 ? preconfigForm.authCode.substring(0, 8) + "..." : 认证码: {{ preconfigForm.authCode.length > 8 ? preconfigForm.authCode.substring(0, 8) + "..." :
preconfigForm.authCode }} preconfigForm.authCode }}
</v-chip> </v-chip>
<v-chip v-else class="mr-2 mb-1" color="grey" size="small"> <v-chip
<v-icon size="small" start>mdi-lock-open</v-icon> v-else
class="mr-2 mb-1"
color="grey"
size="small"
>
<v-icon
size="small"
start
>
mdi-lock-open
</v-icon>
无认证码 无认证码
</v-chip> </v-chip>
<v-chip <v-chip
@ -88,8 +131,12 @@
class="mr-2 mb-1" class="mr-2 mb-1"
size="small" size="small"
> >
<v-icon size="small" start>{{ <v-icon
preconfigForm.autoExecute ? "mdi-play-circle" : "mdi-hand-back-left" size="small"
start
>
{{
preconfigForm.autoExecute ? "mdi-play-circle" : "mdi-hand-back-left"
}} }}
</v-icon> </v-icon>
{{ preconfigForm.autoExecute ? "自动认证" : "手动认证" }} {{ preconfigForm.autoExecute ? "自动认证" : "手动认证" }}
@ -99,9 +146,14 @@
</v-card> </v-card>
<!-- 设置分享部分 --> <!-- 设置分享部分 -->
<v-card class="mb-4" variant="tonal"> <v-card
class="mb-4"
variant="tonal"
>
<v-card-title class="text-subtitle-1"> <v-card-title class="text-subtitle-1">
<v-icon start>mdi-cog-transfer</v-icon> <v-icon start>
mdi-cog-transfer
</v-icon>
设置分享可选 设置分享可选
</v-card-title> </v-card-title>
@ -152,7 +204,10 @@
<!-- 选择摘要 --> <!-- 选择摘要 -->
<div class="d-flex align-center mb-3 flex-wrap gap-2"> <div class="d-flex align-center mb-3 flex-wrap gap-2">
<v-chip class="mr-2" color="primary"> <v-chip
class="mr-2"
color="primary"
>
已选 {{ selectedItems.length }} 项设置 已选 {{ selectedItems.length }} 项设置
</v-chip> </v-chip>
@ -183,7 +238,9 @@
<v-expansion-panel-title> <v-expansion-panel-title>
<template #default="{ expanded }"> <template #default="{ expanded }">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">{{ expanded ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon> <v-icon class="mr-2">
{{ expanded ? 'mdi-chevron-up' : 'mdi-chevron-down' }}
</v-icon>
显示设置列表详情 显示设置列表详情
</div> </div>
</template> </template>
@ -261,9 +318,14 @@
</v-card> </v-card>
<!-- 链接生成和操作部分 --> <!-- 链接生成和操作部分 -->
<v-card class="mb-4" variant="outlined"> <v-card
class="mb-4"
variant="outlined"
>
<v-card-title class="text-subtitle-1"> <v-card-title class="text-subtitle-1">
<v-icon start>mdi-link</v-icon> <v-icon start>
mdi-link
</v-icon>
生成的统一链接 生成的统一链接
</v-card-title> </v-card-title>
@ -317,10 +379,20 @@
type="success" type="success"
variant="tonal" variant="tonal"
> >
<div class="text-subtitle-2 mb-2">链接包含内容</div> <div class="text-subtitle-2 mb-2">
链接包含内容
</div>
<div class="d-flex flex-wrap gap-1"> <div class="d-flex flex-wrap gap-1">
<v-chip color="primary" size="small"> <v-chip
<v-icon size="small" start>mdi-account-key</v-icon> color="primary"
size="small"
>
<v-icon
size="small"
start
>
mdi-account-key
</v-icon>
预配置认证 预配置认证
</v-chip> </v-chip>
<v-chip <v-chip
@ -328,11 +400,25 @@
color="secondary" color="secondary"
size="small" size="small"
> >
<v-icon size="small" start>mdi-cog</v-icon> <v-icon
size="small"
start
>
mdi-cog
</v-icon>
{{ selectedItems.length }} 项设置 {{ selectedItems.length }} 项设置
</v-chip> </v-chip>
<v-chip v-else color="grey" size="small"> <v-chip
<v-icon size="small" start>mdi-cog-off</v-icon> v-else
color="grey"
size="small"
>
<v-icon
size="small"
start
>
mdi-cog-off
</v-icon>
无额外设置 无额外设置
</v-chip> </v-chip>
</div> </div>
@ -341,8 +427,13 @@
</v-card> </v-card>
<!-- 安全提醒 --> <!-- 安全提醒 -->
<v-alert type="warning" variant="tonal"> <v-alert
<div class="text-subtitle-2 mb-2"> 安全提醒</div> type="warning"
variant="tonal"
>
<div class="text-subtitle-2 mb-2">
安全提醒
</div>
<ul class="text-body-2 pl-4"> <ul class="text-body-2 pl-4">
<li>认证码和设置信息会在URL中传输请谨慎分发</li> <li>认证码和设置信息会在URL中传输请谨慎分发</li>
<li>建议仅在受信任的网络环境中使用</li> <li>建议仅在受信任的网络环境中使用</li>
@ -509,6 +600,45 @@ export default {
}, },
}, },
watch: {
//
selectedItems: {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
}
},
deep: true,
},
//
"preconfigForm.namespace": {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
} else {
this.unifiedLink = "";
}
},
},
"preconfigForm.authCode": {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
}
},
},
"preconfigForm.autoExecute": {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
}
},
},
},
methods: { methods: {
/** /**
* 处理表格选择变化 * 处理表格选择变化
@ -782,44 +912,5 @@ export default {
this.linkCopied = false; this.linkCopied = false;
}, },
}, },
watch: {
//
selectedItems: {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
}
},
deep: true,
},
//
"preconfigForm.namespace": {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
} else {
this.unifiedLink = "";
}
},
},
"preconfigForm.authCode": {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
}
},
},
"preconfigForm.autoExecute": {
handler() {
if (this.preconfigForm.namespace.trim()) {
this.generateUnifiedLink();
}
},
},
},
}; };
</script> </script>

View File

@ -43,7 +43,7 @@
> >
稍后设置 稍后设置
</v-btn> </v-btn>
<v-spacer/> <v-spacer />
<v-btn <v-btn
:disabled="!selectedName || saving" :disabled="!selectedName || saving"
:loading="saving" :loading="saving"

View File

@ -17,7 +17,10 @@
</div> </div>
<!-- 发送者信息使用 Vuetify Card --> <!-- 发送者信息使用 Vuetify Card -->
<v-card variant="flat" color="white"> <v-card
variant="flat"
color="white"
>
<v-card-title>发送者信息</v-card-title> <v-card-title>发送者信息</v-card-title>
<v-card-text> <v-card-text>
<v-chip <v-chip
@ -26,7 +29,12 @@
variant="outlined" variant="outlined"
size="small" size="small"
> >
<v-icon left size="16"> mdi-account </v-icon> <v-icon
left
size="16"
>
mdi-account
</v-icon>
{{ senderName }} {{ senderName }}
</v-chip> </v-chip>
<v-chip <v-chip
@ -35,7 +43,12 @@
variant="outlined" variant="outlined"
size="small" size="small"
> >
<v-icon left size="16"> mdi-devices </v-icon> <v-icon
left
size="16"
>
mdi-devices
</v-icon>
{{ deviceType }} {{ deviceType }}
</v-chip> </v-chip>
<v-chip <v-chip
@ -44,18 +57,33 @@
variant="outlined" variant="outlined"
size="small" size="small"
> >
<v-icon left size="16"> mdi-clock </v-icon> <v-icon
left
size="16"
>
mdi-clock
</v-icon>
{{ formatTime(currentNotification?.timestamp) }} {{ formatTime(currentNotification?.timestamp) }}
</v-chip> </v-chip>
</v-card-text> </v-card-text>
</v-card> </v-card>
<!-- 多通知导航 --> <!-- 多通知导航 -->
<div v-if="hasMultipleNotifications" class="navigation-controls mt-6"> <div
<v-card variant="flat" color="rgba(255,255,255,0.1)"> v-if="hasMultipleNotifications"
class="navigation-controls mt-6"
>
<v-card
variant="flat"
color="rgba(255,255,255,0.1)"
>
<v-card-text class="text-center"> <v-card-text class="text-center">
<div class="notification-counter mb-3"> <div class="notification-counter mb-3">
<v-chip color="white" variant="flat" size="small"> <v-chip
color="white"
variant="flat"
size="small"
>
{{ notificationCountText }} {{ notificationCountText }}
</v-chip> </v-chip>
</div> </div>
@ -88,8 +116,15 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="mt-8"> <div class="mt-8">
<v-btn color="white" size="large" variant="flat" @click="close"> <v-btn
<v-icon left> mdi-check </v-icon> color="white"
size="large"
variant="flat"
@click="close"
>
<v-icon left>
mdi-check
</v-icon>
我知道了 我知道了
</v-btn> </v-btn>
</div> </div>

View File

@ -28,8 +28,6 @@
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-card> <v-card>
<v-card-text> <v-card-text>
<v-form> <v-form>
<v-row> <v-row>
@ -42,15 +40,14 @@
label="强调通知" label="强调通知"
color="red" color="red"
inset inset
> />
</v-switch>
<v-checkbox <v-checkbox
v-model="notificationForm.isPersistent" v-model="notificationForm.isPersistent"
label="常驻展示" label="常驻展示"
color="primary" color="primary"
hide-details hide-details
class="mt-0" class="mt-0"
></v-checkbox> />
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-textarea <v-textarea
@ -81,8 +78,6 @@
</v-btn> </v-btn>
<v-spacer /> <v-spacer />
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-col> </v-col>
@ -93,11 +88,16 @@
<v-col cols="12"> <v-col cols="12">
<v-card> <v-card>
<v-card-title> <v-card-title>
<v-icon class="mr-2">mdi-pin</v-icon> <v-icon class="mr-2">
mdi-pin
</v-icon>
常驻通知管理 常驻通知管理
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<div v-if="persistentNotifications.length === 0" class="text-center text-grey py-4"> <div
v-if="persistentNotifications.length === 0"
class="text-center text-grey py-4"
>
暂无常驻通知 暂无常驻通知
</div> </div>
<v-list v-else> <v-list v-else>
@ -108,14 +108,25 @@
:subtitle="formatTime(item.timestamp)" :subtitle="formatTime(item.timestamp)"
lines="two" lines="two"
> >
<template v-slot:prepend> <template #prepend>
<v-icon :color="item.isUrgent ? 'error' : 'primary'"> <v-icon :color="item.isUrgent ? 'error' : 'primary'">
{{ item.isUrgent ? 'mdi-alert-circle' : 'mdi-information' }} {{ item.isUrgent ? 'mdi-alert-circle' : 'mdi-information' }}
</v-icon> </v-icon>
</template> </template>
<template v-slot:append> <template #append>
<v-btn icon="mdi-pencil" variant="text" size="small" @click="openEditDialog(item)"></v-btn> <v-btn
<v-btn icon="mdi-delete" variant="text" color="error" size="small" @click="deletePersistentNotification(item.id)"></v-btn> icon="mdi-pencil"
variant="text"
size="small"
@click="openEditDialog(item)"
/>
<v-btn
icon="mdi-delete"
variant="text"
color="error"
size="small"
@click="deletePersistentNotification(item.id)"
/>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>
@ -201,17 +212,14 @@
> >
<v-card-text class="pa-2"> <v-card-text class="pa-2">
<div class="align-center"> <div class="align-center">
<span class="text-body-2 font-weight-medium">{{ device.deviceName }} </span> <span class="text-body-2 font-weight-medium">{{ device.deviceName }} </span>
<br/> <br>
{{ device.deviceType }} {{ device.deviceType }}
</div> </div>
<div class="text-caption mt-1"> <div class="text-caption mt-1">
已读于 {{ formatDeviceTime(device.timestamp) }} 已读于 {{ formatDeviceTime(device.timestamp) }}
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -226,7 +234,6 @@
> >
<v-card-text class="pa-2"> <v-card-text class="pa-2">
<div class="align-center"> <div class="align-center">
<span class="text-body-2 font-weight-medium">{{ device.deviceName }}</span> <span class="text-body-2 font-weight-medium">{{ device.deviceName }}</span>
<v-spacer /> <v-spacer />
<span class="text-caption text-grey"> <span class="text-caption text-grey">
@ -238,9 +245,9 @@
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
</div> </div>
<div v-else> <v-card <div v-else>
<v-card
color="info-lighten-4" color="info-lighten-4"
variant="outlined" variant="outlined"
@ -249,9 +256,10 @@
title="无设备在线" title="无设备在线"
> >
<v-card-text> <v-card-text>
如果数秒后任然显示这个提示则可能没有任何设备在线接收通知 如果数秒后任然显示这个提示则可能没有任何设备在线接收通知
</v-card-text> </v-card-text>
</v-card></div> </v-card>
</div>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -266,12 +274,22 @@
<EventSender ref="eventSender" /> <EventSender ref="eventSender" />
<!-- 编辑常驻通知对话框 --> <!-- 编辑常驻通知对话框 -->
<v-dialog v-model="editDialog" max-width="500" :fullscreen="$vuetify.display.xs"> <v-dialog
v-model="editDialog"
max-width="500"
:fullscreen="$vuetify.display.xs"
>
<v-card> <v-card>
<v-toolbar flat density="compact"> <v-toolbar
flat
density="compact"
>
<v-toolbar-title>编辑常驻通知</v-toolbar-title> <v-toolbar-title>编辑常驻通知</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer />
<v-btn icon="mdi-close" @click="editDialog = false"></v-btn> <v-btn
icon="mdi-close"
@click="editDialog = false"
/>
</v-toolbar> </v-toolbar>
<v-card-text> <v-card-text>
<v-form> <v-form>
@ -280,38 +298,66 @@
label="通知内容" label="通知内容"
rows="3" rows="3"
auto-grow auto-grow
></v-textarea> />
<v-switch <v-switch
v-model="editForm.isUrgent" v-model="editForm.isUrgent"
label="强调通知" label="强调通知"
color="error" color="error"
hide-details hide-details
></v-switch> />
<v-checkbox <v-checkbox
v-model="editForm.resend" v-model="editForm.resend"
label="保存并重新发送通知" label="保存并重新发送通知"
hint="勾选后将作为新通知发送给所有在线设备" hint="勾选后将作为新通知发送给所有在线设备"
persistent-hint persistent-hint
></v-checkbox> />
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn variant="text" @click="editDialog = false">取消</v-btn> <v-btn
<v-btn color="primary" :loading="savingEdit" @click="saveEdit">保存</v-btn> variant="text"
@click="editDialog = false"
>
取消
</v-btn>
<v-btn
color="primary"
:loading="savingEdit"
@click="saveEdit"
>
保存
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
<!-- 删除确认对话框 --> <!-- 删除确认对话框 -->
<v-dialog v-model="deleteConfirmDialog" max-width="400"> <v-dialog
v-model="deleteConfirmDialog"
max-width="400"
>
<v-card> <v-card>
<v-card-title class="text-h5">确认删除</v-card-title> <v-card-title class="text-h5">
确认删除
</v-card-title>
<v-card-text>确定要删除这条常驻通知吗此操作无法撤销</v-card-text> <v-card-text>确定要删除这条常驻通知吗此操作无法撤销</v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn color="grey-darken-1" variant="text" @click="deleteConfirmDialog = false">取消</v-btn> <v-btn
<v-btn color="error" variant="text" @click="executeDelete">删除</v-btn> color="grey-darken-1"
variant="text"
@click="deleteConfirmDialog = false"
>
取消
</v-btn>
<v-btn
color="error"
variant="text"
@click="executeDelete"
>
删除
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
@ -681,7 +727,9 @@ export default {
try { try {
this.persistentNotifications = this.persistentNotifications.filter(n => n.id !== id) this.persistentNotifications = this.persistentNotifications.filter(n => n.id !== id)
await dataProvider.saveData('notification-list', this.persistentNotifications) // {} []
const dataToSave = this.persistentNotifications.length > 0 ? this.persistentNotifications : {}
await dataProvider.saveData('notification-list', dataToSave)
this.$message?.success('已删除') this.$message?.success('已删除')
} catch (e) { } catch (e) {
console.error('删除失败', e) console.error('删除失败', e)

View File

@ -8,19 +8,35 @@
> >
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-account-group" /> <v-icon
class="mr-2"
icon="mdi-account-group"
/>
出勤状态管理 出勤状态管理
<v-spacer /> <v-spacer />
<v-chip v-if="!isMobile" class="ml-2" color="primary" size="small"> <v-chip
v-if="!isMobile"
class="ml-2"
color="primary"
size="small"
>
{{ dateString }} {{ dateString }}
</v-chip> </v-chip>
<v-btn v-if="isMobile" icon="mdi-close" variant="text" @click="$emit('update:modelValue', false)" /> <v-btn
v-if="isMobile"
icon="mdi-close"
variant="text"
@click="$emit('update:modelValue', false)"
/>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<!-- 批量操作和搜索 --> <!-- 批量操作和搜索 -->
<v-row class="mb-4"> <v-row class="mb-4">
<v-col cols="12" md="12"> <v-col
cols="12"
md="12"
>
<v-text-field <v-text-field
v-model="attendanceSearch" v-model="attendanceSearch"
clearable clearable
@ -128,7 +144,10 @@
md="6" md="6"
sm="6" sm="6"
> >
<v-card border class="student-card"> <v-card
border
class="student-card"
>
<v-card-text class="d-flex align-center pa-2"> <v-card-text class="d-flex align-center pa-2">
<div class="flex-grow-1"> <div class="flex-grow-1">
<div class="d-flex align-center"> <div class="d-flex align-center">
@ -137,11 +156,13 @@
class="mr-2" class="mr-2"
size="24" size="24"
> >
<v-icon size="small" <v-icon size="small">
>{{ getStudentStatusIcon(student) }} {{ getStudentStatusIcon(student) }}
</v-icon> </v-icon>
</v-avatar> </v-avatar>
<div class="text-subtitle-1">{{ student }}</div> <div class="text-subtitle-1">
{{ student }}
</div>
</div> </div>
</div> </div>
<div class="attendance-actions"> <div class="attendance-actions">
@ -183,10 +204,19 @@
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" md="12"> <v-col
<v-card class="mb-4" color="primary" variant="tonal"> cols="12"
md="12"
>
<v-card
class="mb-4"
color="primary"
variant="tonal"
>
<v-card-text> <v-card-text>
<div class="text-subtitle-2 mb-2">批量操作</div> <div class="text-subtitle-2 mb-2">
批量操作
</div>
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
<v-btn <v-btn
class="flex-grow-1" class="flex-grow-1"
@ -232,8 +262,13 @@
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer />
<v-btn color="primary" @click="$emit('save')"> <v-btn
<v-icon start>mdi-content-save</v-icon> color="primary"
@click="$emit('save')"
>
<v-icon start>
mdi-content-save
</v-icon>
保存 保存
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>

View File

@ -27,9 +27,9 @@
<span style="white-space: nowrap"> <span style="white-space: nowrap">
{{ {{
studentList.length - studentList.length -
attendance.absent.length - attendance.absent.length -
attendance.late.length - attendance.late.length -
attendance.exclude.length attendance.exclude.length
}} }}
</span> </span>
</h2> </h2>
@ -45,8 +45,7 @@
:key="'absent-' + index" :key="'absent-' + index"
class="gray-text" class="gray-text"
> >
<span v-if="display.lgAndUp.value">{{ `${index + 1}. ` }}</span <span v-if="display.lgAndUp.value">{{ `${index + 1}. ` }}</span><span style="white-space: nowrap">{{ name }}</span>
><span style="white-space: nowrap">{{ name }}</span>
</h3> </h3>
<h2> <h2>
<span style="white-space: nowrap">迟到</span> <span style="white-space: nowrap">迟到</span>
@ -60,8 +59,7 @@
:key="'late-' + index" :key="'late-' + index"
class="gray-text" class="gray-text"
> >
<span v-if="display.lgAndUp.value">{{ `${index + 1}. ` }}</span <span v-if="display.lgAndUp.value">{{ `${index + 1}. ` }}</span><span style="white-space: nowrap">{{ name }}</span>
><span style="white-space: nowrap">{{ name }}</span>
</h3> </h3>
<h2> <h2>
<span style="white-space: nowrap">不参与</span> <span style="white-space: nowrap">不参与</span>
@ -75,8 +73,7 @@
:key="'exclude-' + index" :key="'exclude-' + index"
class="gray-text" class="gray-text"
> >
<span v-if="display.lgAndUp.value">{{ `${index + 1}. ` }}</span <span v-if="display.lgAndUp.value">{{ `${index + 1}. ` }}</span><span style="white-space: nowrap">{{ name }}</span>
><span style="white-space: nowrap">{{ name }}</span>
</h3> </h3>
</v-col> </v-col>
</template> </template>

View File

@ -20,7 +20,7 @@
</v-alert> </v-alert>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn <v-btn
v-if="showCancel" v-if="showCancel"
variant="text" variant="text"

View File

@ -41,9 +41,7 @@
label="命名空间" label="命名空间"
prepend-inner-icon="mdi-identifier" prepend-inner-icon="mdi-identifier"
variant="outlined" variant="outlined"
> />
</v-text-field>
<v-text-field <v-text-field
v-model="form.password" v-model="form.password"
@ -51,9 +49,7 @@
prepend-inner-icon="mdi-lock-outline" prepend-inner-icon="mdi-lock-outline"
type="text" type="text"
variant="outlined" variant="outlined"
> />
</v-text-field>
<v-alert <v-alert
v-if="error" v-if="error"
@ -77,7 +73,7 @@
> >
取消 取消
</v-btn> </v-btn>
<v-spacer/> <v-spacer />
<v-btn <v-btn
:disabled="!form.namespace || authenticating" :disabled="!form.namespace || authenticating"
:loading="authenticating" :loading="authenticating"

View File

@ -139,8 +139,6 @@
</div> </div>
</div> </div>
</v-card> </v-card>
</div> </div>
<!-- 步骤 3: 询问使用场景 --> <!-- 步骤 3: 询问使用场景 -->
@ -553,7 +551,7 @@
</v-icon> </v-icon>
上一步 上一步
</v-btn> </v-btn>
<v-spacer/> <v-spacer />
<v-btn <v-btn
v-if="currentStep < totalSteps && currentStep !== 4" v-if="currentStep < totalSteps && currentStep !== 4"
:disabled="currentStep === 3 && !storageType" :disabled="currentStep === 3 && !storageType"

View File

@ -20,7 +20,7 @@
variant="tonal" variant="tonal"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-information"/> <v-icon icon="mdi-information" />
</template> </template>
系统将自动为您创建设备并获取访问令牌无需手动配置 系统将自动为您创建设备并获取访问令牌无需手动配置
</v-alert> </v-alert>
@ -52,7 +52,7 @@
variant="tonal" variant="tonal"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-check-circle"/> <v-icon icon="mdi-check-circle" />
</template> </template>
设备注册成功已自动获取访问令牌 设备注册成功已自动获取访问令牌
</v-alert> </v-alert>
@ -60,7 +60,7 @@
<v-list> <v-list>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-identifier"/> <v-icon icon="mdi-identifier" />
</template> </template>
<v-list-item-title>设备名称</v-list-item-title> <v-list-item-title>设备名称</v-list-item-title>
<v-list-item-subtitle>{{ deviceInfo.deviceName }}</v-list-item-subtitle> <v-list-item-subtitle>{{ deviceInfo.deviceName }}</v-list-item-subtitle>
@ -68,7 +68,7 @@
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-key"/> <v-icon icon="mdi-key" />
</template> </template>
<v-list-item-title>设备 UUID</v-list-item-title> <v-list-item-title>设备 UUID</v-list-item-title>
<v-list-item-subtitle class="font-mono text-caption"> <v-list-item-subtitle class="font-mono text-caption">
@ -83,7 +83,7 @@
variant="tonal" variant="tonal"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-information"/> <v-icon icon="mdi-information" />
</template> </template>
您可以点击下方按钮访问云端控制台来设置密码和管理高级功能 您可以点击下方按钮访问云端控制台来设置密码和管理高级功能
</v-alert> </v-alert>
@ -97,7 +97,7 @@
variant="tonal" variant="tonal"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-alert-circle"/> <v-icon icon="mdi-alert-circle" />
</template> </template>
{{ errorMessage }} {{ errorMessage }}
</v-alert> </v-alert>
@ -105,7 +105,7 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<!-- 注册按钮 --> <!-- 注册按钮 -->
<v-btn <v-btn

View File

@ -21,7 +21,7 @@
</v-alert> </v-alert>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn <v-btn
v-if="showCancel" v-if="showCancel"
variant="text" variant="text"

View File

@ -1,15 +1,26 @@
<template> <template>
<v-container class="fill-height"> <v-container class="fill-height">
<v-responsive class="align-centerfill-height mx-auto" max-width="900"> <v-responsive
<v-img class="mb-4" height="150" src="@/assets/logo.svg"/> class="align-centerfill-height mx-auto"
max-width="900"
>
<v-img
class="mb-4"
height="150"
src="@/assets/logo.svg"
/>
<div class="text-center"> <div class="text-center">
<div class="text-body-2 font-weight-light mb-n1">出现了错误</div> <div class="text-body-2 font-weight-light mb-n1">
出现了错误
</div>
<h1 class="text-h2 font-weight-bold">404</h1> <h1 class="text-h2 font-weight-bold">
404
</h1>
</div> </div>
<div class="py-4"/> <div class="py-4" />
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
@ -21,15 +32,19 @@
variant="outlined" variant="outlined"
> >
<template #image> <template #image>
<v-img position="top right"/> <v-img position="top right" />
</template> </template>
<template #title> <template #title>
<h2 class="text-h5 font-weight-bold">为什么会出现此错误</h2> <h2 class="text-h5 font-weight-bold">
为什么会出现此错误
</h2>
</template> </template>
<template #subtitle> <template #subtitle>
<div class="text-subtitle-1">大概是页面未找到</div> <div class="text-subtitle-1">
大概是页面未找到
</div>
</template> </template>
<v-overlay <v-overlay
@ -70,7 +85,7 @@
rounded="lg" rounded="lg"
title="返回上一页" title="返回上一页"
variant="text" variant="text"
@click="this.$router.back()" @click="$router.back()"
> >
<v-overlay <v-overlay
contained contained

View File

@ -10,7 +10,12 @@
> >
上传 上传
</v-btn> </v-btn>
<v-btn v-else color="success" size="large" @click="$emit('show-sync-message')"> <v-btn
v-else
color="success"
size="large"
@click="$emit('show-sync-message')"
>
同步完成 同步完成
</v-btn> </v-btn>
<v-btn <v-btn
@ -76,7 +81,11 @@
variant="tonal" variant="tonal"
> >
<v-card-title class="text-subtitle-1"> <v-card-title class="text-subtitle-1">
<v-icon icon="mdi-shield-check" size="small" start /> <v-icon
icon="mdi-shield-check"
size="small"
start
/>
屏幕保护技术已启用 屏幕保护技术已启用
</v-card-title> </v-card-title>
<v-card-text class="text-body-2"> <v-card-text class="text-body-2">

View File

@ -1,5 +1,8 @@
<template> <template>
<div ref="gridContainer" class="grid-masonry"> <div
ref="gridContainer"
class="grid-masonry"
>
<TransitionGroup name="grid"> <TransitionGroup name="grid">
<div <div
v-for="item in sortedItems" v-for="item in sortedItems"
@ -22,7 +25,11 @@
@touchmove="handleTouchMove" @touchmove="handleTouchMove"
> >
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" color="primary" icon="mdi-account-group" /> <v-icon
class="mr-2"
color="primary"
icon="mdi-account-group"
/>
出勤统计 出勤统计
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
@ -31,36 +38,78 @@
<span class="text-h6"> <span class="text-h6">
{{ item.data.total - item.data.exclude.length }}/{{ {{ item.data.total - item.data.exclude.length }}/{{
item.data.total - item.data.total -
item.data.absent.length - item.data.absent.length -
item.data.late.length - item.data.late.length -
item.data.exclude.length item.data.exclude.length
}} }}
</span> </span>
</div> </div>
<v-divider class="mb-2" /> <v-divider class="mb-2" />
<div v-if="item.data.absent.length > 0" class="mb-2"> <div
<div class="text-error text-caption mb-1">请假 ({{ item.data.absent.length }})</div> v-if="item.data.absent.length > 0"
<div class="d-flex flex-wrap" style="gap: 4px"> class="mb-2"
<v-chip v-for="name in item.data.absent" :key="name" color="error" size="x-small" variant="flat"> >
<div class="text-error text-caption mb-1">
请假 ({{ item.data.absent.length }})
</div>
<div
class="d-flex flex-wrap"
style="gap: 4px"
>
<v-chip
v-for="name in item.data.absent"
:key="name"
color="error"
size="x-small"
variant="flat"
>
{{ name }} {{ name }}
</v-chip> </v-chip>
</div> </div>
</div> </div>
<div v-if="item.data.late.length > 0" class="mb-2"> <div
<div class="text-warning text-caption mb-1">迟到 ({{ item.data.late.length }})</div> v-if="item.data.late.length > 0"
<div class="d-flex flex-wrap" style="gap: 4px"> class="mb-2"
<v-chip v-for="name in item.data.late" :key="name" color="warning" size="x-small" variant="flat"> >
<div class="text-warning text-caption mb-1">
迟到 ({{ item.data.late.length }})
</div>
<div
class="d-flex flex-wrap"
style="gap: 4px"
>
<v-chip
v-for="name in item.data.late"
:key="name"
color="warning"
size="x-small"
variant="flat"
>
{{ name }} {{ name }}
</v-chip> </v-chip>
</div> </div>
</div> </div>
<div v-if="item.data.exclude.length > 0" class="mb-2"> <div
<div class="text-grey text-caption mb-1">不参与 ({{ item.data.exclude.length }})</div> v-if="item.data.exclude.length > 0"
<div class="d-flex flex-wrap" style="gap: 4px"> class="mb-2"
<v-chip v-for="name in item.data.exclude" :key="name" color="grey" size="x-small" variant="flat"> >
<div class="text-grey text-caption mb-1">
不参与 ({{ item.data.exclude.length }})
</div>
<div
class="d-flex flex-wrap"
style="gap: 4px"
>
<v-chip
v-for="name in item.data.exclude"
:key="name"
color="grey"
size="x-small"
variant="flat"
>
{{ name }} {{ name }}
</v-chip> </v-chip>
</div> </div>
@ -69,8 +118,8 @@
<div <div
v-if=" v-if="
item.data.absent.length === 0 && item.data.absent.length === 0 &&
item.data.late.length === 0 && item.data.late.length === 0 &&
item.data.exclude.length === 0 item.data.exclude.length === 0
" "
class="text-success text-center mt-2" class="text-success text-center mt-2"
> >
@ -91,7 +140,11 @@
@touchmove="handleTouchMove" @touchmove="handleTouchMove"
> >
<v-card-title class="text-primary"> <v-card-title class="text-primary">
<v-icon class="mr-2" icon="mdi-card-text-outline" size="small" /> <v-icon
class="mr-2"
icon="mdi-card-text-outline"
size="small"
/>
{{ item.name }} {{ item.name }}
</v-card-title> </v-card-title>
<v-card-text :style="contentStyle"> <v-card-text :style="contentStyle">
@ -129,7 +182,10 @@
<!-- 单独显示空科目 --> <!-- 单独显示空科目 -->
<div class="empty-subjects mt-4"> <div class="empty-subjects mt-4">
<!-- 移动端优化视图 --> <!-- 移动端优化视图 -->
<div v-if="isMobile" class="d-flex flex-wrap justify-center"> <div
v-if="isMobile"
class="d-flex flex-wrap justify-center"
>
<v-chip <v-chip
v-for="subject in unusedSubjects" v-for="subject in unusedSubjects"
:key="subject.name" :key="subject.name"
@ -138,24 +194,37 @@
variant="tonal" variant="tonal"
@click="handleCardClick('dialog', subject.name)" @click="handleCardClick('dialog', subject.name)"
> >
<v-icon start size="small">mdi-plus</v-icon> <v-icon
start
size="small"
>
mdi-plus
</v-icon>
{{ subject.name }} {{ subject.name }}
</v-chip> </v-chip>
</div> </div>
<template v-else-if="emptySubjectDisplay === 'button'"> <template v-else-if="emptySubjectDisplay === 'button'">
<v-btn-group divided variant="tonal"> <v-btn-group
divided
variant="tonal"
>
<v-btn <v-btn
v-for="subject in unusedSubjects" v-for="subject in unusedSubjects"
:key="subject.name" :key="subject.name"
@click="handleCardClick('dialog', subject.name)" @click="handleCardClick('dialog', subject.name)"
> >
<v-icon start> mdi-plus</v-icon> <v-icon start>
mdi-plus
</v-icon>
{{ subject.name }} {{ subject.name }}
</v-btn> </v-btn>
</v-btn-group> </v-btn-group>
</template> </template>
<div v-else class="empty-subjects-grid"> <div
v-else
class="empty-subjects-grid"
>
<TransitionGroup name="v-list"> <TransitionGroup name="v-list">
<v-card <v-card
v-for="subject in unusedSubjects" v-for="subject in unusedSubjects"
@ -168,8 +237,15 @@
{{ subject.name }} {{ subject.name }}
</v-card-title> </v-card-title>
<v-card-text class="text-center"> <v-card-text class="text-center">
<v-icon color="grey" size="small"> mdi-plus</v-icon> <v-icon
<div class="text-caption text-grey">点击添加作业</div> color="grey"
size="small"
>
mdi-plus
</v-icon>
<div class="text-caption text-grey">
点击添加作业
</div>
</v-card-text> </v-card-text>
</v-card> </v-card>
</TransitionGroup> </TransitionGroup>

View File

@ -1,25 +1,46 @@
<template> <template>
<v-card border hover rounded="xl"> <v-card
border
hover
rounded="xl"
>
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon class="mr-2" icon="mdi-information" size="large"/> <v-icon
class="mr-2"
icon="mdi-information"
size="large"
/>
</template> </template>
<v-card-title class="text-h6">关于</v-card-title> <v-card-title class="text-h6">
关于
</v-card-title>
</v-card-item> </v-card-item>
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col class="mx-auto" cols="12" md="8"> <v-col
class="mx-auto"
cols="12"
md="8"
>
<div class="d-flex flex-column align-start"> <div class="d-flex flex-column align-start">
<v-avatar class="mb-4" size="120"> <v-avatar
class="mb-4"
size="120"
>
<v-img <v-img
alt="Classworks" alt="Classworks"
src="../../assets/cslogo.png" src="../../assets/cslogo.png"
/> />
</v-avatar> </v-avatar>
<h2 class="text-h5 mb-2">Classworks</h2> <h2 class="text-h5 mb-2">
<p class="text-body-1 mb-4">适用于班级大屏的作业板小工具</p> Classworks
</h2>
<p class="text-body-1 mb-4">
适用于班级大屏的作业板小工具
</p>
<div class="d-flex gap-2 flex-wrap mb-6"> <div class="d-flex gap-2 flex-wrap mb-6">
<v-btn <v-btn
@ -57,9 +78,11 @@
</v-btn> </v-btn>
</div> </div>
<v-divider class="mb-4 w-100"></v-divider> <v-divider class="mb-4 w-100" />
<h3 class="text-h6 mb-2">备注与致谢</h3> <h3 class="text-h6 mb-2">
备注与致谢
</h3>
<v-list class="mb-4 bg-transparent"> <v-list class="mb-4 bg-transparent">
<v-list-item <v-list-item
append-icon="mdi-link" append-icon="mdi-link"
@ -98,7 +121,7 @@
新一代开源编程社区 新一代开源编程社区
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-divider class="ma-1"></v-divider> <v-divider class="ma-1" />
<v-list-item <v-list-item
append-icon="mdi-link" append-icon="mdi-link"
href="https://github.com/HUSX100/IslandCaller" href="https://github.com/HUSX100/IslandCaller"
@ -141,12 +164,14 @@
fullscreen fullscreen
transition="dialog-bottom-transition" transition="dialog-bottom-transition"
> >
<v-card <v-card>
>
<v-toolbar> <v-toolbar>
<v-btn icon="mdi-close" @click="showDeps = false"></v-btn> <v-btn
icon="mdi-close"
@click="showDeps = false"
/>
<v-toolbar-title>使用的第三方库</v-toolbar-title> <v-toolbar-title>使用的第三方库</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer />
</v-toolbar> </v-toolbar>
<v-card-text> <v-card-text>
<v-list> <v-list>
@ -170,45 +195,108 @@
</v-dialog> </v-dialog>
<!-- 报告问题对话框 --> <!-- 报告问题对话框 -->
<v-dialog v-model="showReportDialog" max-width="640"> <v-dialog
v-model="showReportDialog"
max-width="640"
>
<v-card> <v-card>
<v-toolbar density="compact"> <v-toolbar density="compact">
<v-btn icon="mdi-close" @click="showReportDialog = false"></v-btn> <v-btn
icon="mdi-close"
@click="showReportDialog = false"
/>
<v-toolbar-title>报告问题</v-toolbar-title> <v-toolbar-title>报告问题</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer />
</v-toolbar> </v-toolbar>
<v-card-text> <v-card-text>
<p class="mb-4"> <p class="mb-4">
调试ID与下方的浏览器环境信息将帮助我们快速定位问题请在反馈中一并附上 调试ID与下方的浏览器环境信息将帮助我们快速定位问题请在反馈中一并附上
</p> </p>
<v-sheet class="mb-3 pa-3 bg-grey-lighten-4 rounded" style="max-height: 260px; overflow: auto;"> <v-sheet
<pre class="text-body-2" style="white-space: pre-wrap; margin: 0;">{{ envBoxText }}</pre> class="mb-3 pa-3 bg-grey-lighten-4 rounded"
style="max-height: 260px; overflow: auto;"
>
<pre
class="text-body-2"
style="white-space: pre-wrap; margin: 0;"
>{{ envBoxText }}</pre>
</v-sheet> </v-sheet>
<div class="d-flex gap-2 flex-wrap mb-4"> <div class="d-flex gap-2 flex-wrap mb-4">
<v-btn size="small" variant="text" prepend-icon="mdi-refresh" @click="reloadVisitorId" :loading="visitorLoading">刷新</v-btn> <v-btn
<v-btn size="small" variant="text" prepend-icon="mdi-content-copy" @click="copyEnvInfo">复制信息</v-btn> size="small"
<v-btn size="small" variant="text" prepend-icon="mdi-open-in-new" @click="goToDebug">查看 /debug 页面</v-btn> variant="text"
prepend-icon="mdi-refresh"
:loading="visitorLoading"
@click="reloadVisitorId"
>
刷新
</v-btn>
<v-btn
size="small"
variant="text"
prepend-icon="mdi-content-copy"
@click="copyEnvInfo"
>
复制信息
</v-btn>
<v-btn
size="small"
variant="text"
prepend-icon="mdi-open-in-new"
@click="goToDebug"
>
查看 /debug 页面
</v-btn>
</div> </div>
<v-alert v-if="copyOk" type="success" density="compact" class="mb-4">已复制到剪贴板</v-alert> <v-alert
<h4 class="text-subtitle-1 mb-2">反馈渠道</h4> v-if="copyOk"
<v-list lines="one" class="bg-transparent"> type="success"
<v-list-item :href="qqGroupLink" target="_blank" prepend-icon="mdi-qqchat"> density="compact"
class="mb-4"
>
已复制到剪贴板
</v-alert>
<h4 class="text-subtitle-1 mb-2">
反馈渠道
</h4>
<v-list
lines="one"
class="bg-transparent"
>
<v-list-item
:href="qqGroupLink"
target="_blank"
prepend-icon="mdi-qqchat"
>
<v-list-item-title>QQ群 ({{ qqGroupNumber }})</v-list-item-title> <v-list-item-title>QQ群 ({{ qqGroupNumber }})</v-list-item-title>
<v-list-item-subtitle>964979747</v-list-item-subtitle> <v-list-item-subtitle>964979747</v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item :href="githubIssueUrl" target="_blank" prepend-icon="mdi-github"> <v-list-item
:href="githubIssueUrl"
target="_blank"
prepend-icon="mdi-github"
>
<v-list-item-title>GitHub Issue</v-list-item-title> <v-list-item-title>GitHub Issue</v-list-item-title>
<v-list-item-subtitle>ZeroCatDev/Classworks</v-list-item-subtitle> <v-list-item-subtitle>ZeroCatDev/Classworks</v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item :href="mailtoLink" target="_blank" prepend-icon="mdi-email"> <v-list-item
:href="mailtoLink"
target="_blank"
prepend-icon="mdi-email"
>
<v-list-item-title>邮件</v-list-item-title> <v-list-item-title>邮件</v-list-item-title>
<v-list-item-subtitle>sun@wuyuan.dev</v-list-item-subtitle> <v-list-item-subtitle>sun@wuyuan.dev</v-list-item-subtitle>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn variant="text" @click="showReportDialog = false">关闭</v-btn> <v-btn
variant="text"
@click="showReportDialog = false"
>
关闭
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@ -1,7 +1,17 @@
<template> <template>
<v-card :border="border" class="setting-group"> <v-card
<v-card-title v-if="title" class="d-flex align-center"> :border="border"
<v-icon v-if="icon" :icon="icon" class="mr-2"/> class="setting-group"
>
<v-card-title
v-if="title"
class="d-flex align-center"
>
<v-icon
v-if="icon"
:icon="icon"
class="mr-2"
/>
{{ title }} {{ title }}
</v-card-title> </v-card-title>
@ -18,7 +28,7 @@
</v-card-text> </v-card-text>
<v-card-actions v-if="$slots.actions"> <v-card-actions v-if="$slots.actions">
<slot name="actions"></slot> <slot name="actions" />
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</template> </template>

View File

@ -1,7 +1,10 @@
<template> <template>
<v-list-item :disabled="disabled" class="setting-item"> <v-list-item
:disabled="disabled"
class="setting-item"
>
<template #prepend> <template #prepend>
<v-icon :icon="settingIcon"/> <v-icon :icon="settingIcon" />
</template> </template>
<v-list-item-title class="text-wrap"> <v-list-item-title class="text-wrap">
@ -14,7 +17,10 @@
<template #append> <template #append>
<div class="d-flex flex-column flex-sm-row align-center"> <div class="d-flex flex-column flex-sm-row align-center">
<div v-if="type !== 'string' || hasOptions" class="me-2"> <div
v-if="type !== 'string' || hasOptions"
class="me-2"
>
<!-- 根据设置类型渲染不同的控件 --> <!-- 根据设置类型渲染不同的控件 -->
<v-switch <v-switch
v-if="type === 'boolean'" v-if="type === 'boolean'"
@ -40,7 +46,10 @@
@update:model-value="updateSetting" @update:model-value="updateSetting"
/> />
<div v-else-if="type === 'number'" class="d-flex align-center"> <div
v-else-if="type === 'number'"
class="d-flex align-center"
>
<v-btn <v-btn
:disabled="disabled || localValue <= minValue" :disabled="disabled || localValue <= minValue"
icon="mdi-minus" icon="mdi-minus"
@ -76,7 +85,7 @@
</div> </div>
<v-menu location="bottom"> <v-menu location="bottom">
<template v-slot:activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
:disabled="disabled" :disabled="disabled"
class="ml-2" class="ml-2"
@ -88,24 +97,36 @@
</template> </template>
<v-list density="compact"> <v-list density="compact">
<v-list-item @click="copySettingId"> <v-list-item @click="copySettingId">
<template v-slot:prepend> <template #prepend>
<v-icon icon="mdi-key" size="small"/> <v-icon
icon="mdi-key"
size="small"
/>
</template> </template>
<v-list-item-title>复制设置ID</v-list-item-title> <v-list-item-title>复制设置ID</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="copySettingValue"> <v-list-item @click="copySettingValue">
<template v-slot:prepend> <template #prepend>
<v-icon icon="mdi-content-copy" size="small"/> <v-icon
icon="mdi-content-copy"
size="small"
/>
</template> </template>
<v-list-item-title>复制设置值</v-list-item-title> <v-list-item-title>复制设置值</v-list-item-title>
</v-list-item> </v-list-item>
<v-divider></v-divider> <v-divider />
<v-list-item :disabled="isDefaultValue" @click="resetToDefault"> <v-list-item
<template v-slot:prepend> :disabled="isDefaultValue"
<v-icon icon="mdi-restore" size="small"/> @click="resetToDefault"
>
<template #prepend>
<v-icon
icon="mdi-restore"
size="small"
/>
</template> </template>
<v-list-item-title>重置为默认值</v-list-item-title> <v-list-item-title>重置为默认值</v-list-item-title>
</v-list-item> </v-list-item>
@ -116,7 +137,10 @@
</v-list-item> </v-list-item>
<!-- 文本框显示在下方 --> <!-- 文本框显示在下方 -->
<div v-if="type === 'string' && !hasOptions" class="px-4 pb-2 pt-0"> <div
v-if="type === 'string' && !hasOptions"
class="px-4 pb-2 pt-0"
>
<v-text-field <v-text-field
v-model="localValue" v-model="localValue"
:disabled="disabled" :disabled="disabled"

View File

@ -1,30 +1,46 @@
<template> <template>
<div class="settings-explorer"> <div class="settings-explorer">
<div> <div>
<v-text-field v-model="searchQuery" class="mb-4" clearable density="comfortable" label="搜索设置" <v-text-field
prepend-inner-icon="mdi-magnify" variant="outlined"/> v-model="searchQuery"
class="mb-4"
clearable
density="comfortable"
label="搜索设置"
prepend-inner-icon="mdi-magnify"
variant="outlined"
/>
<v-list> <v-list>
<div v-for="setting in allSettings" :key="setting.key"> <div
<setting-item :key="setting.key" :disabled="setting.requireDeveloper && !isDeveloperMode" v-for="setting in allSettings"
:setting-key="setting.key" @error="onSettingError" :key="setting.key"
@update="onSettingUpdate"/> >
<v-divider class="my-2"/> <setting-item
:key="setting.key"
:disabled="setting.requireDeveloper && !isDeveloperMode"
:setting-key="setting.key"
@error="onSettingError"
@update="onSettingUpdate"
/>
<v-divider class="my-2" />
</div> </div>
</v-list> </v-list>
<v-card border> <v-card border>
<v-card-title class="text-subtitle-1">当前配置</v-card-title> <v-card-title class="text-subtitle-1">
当前配置
</v-card-title>
<v-card-text> <v-card-text>
<pre class="settings-json">{{ formattedSettings }}</pre> <pre class="settings-json">{{ formattedSettings }}</pre>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn @click="copySettingsToClipboard"> <v-btn @click="copySettingsToClipboard">
复制到剪贴板 复制到剪贴板
<v-icon right>mdi-content-copy</v-icon> <v-icon right>
mdi-content-copy
</v-icon>
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@ -6,11 +6,20 @@
> >
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon class="mr-2" icon="mdi-account-group" size="large"/> <v-icon
class="mr-2"
icon="mdi-account-group"
size="large"
/>
</template> </template>
<v-card-title class="text-h6">学生列表</v-card-title> <v-card-title class="text-h6">
学生列表
</v-card-title>
<template #append> <template #append>
<unsaved-warning :show="unsavedChanges" message="有未保存的更改"/> <unsaved-warning
:show="unsavedChanges"
message="有未保存的更改"
/>
<v-btn <v-btn
:disabled="modelValue.list.length === 0" :disabled="modelValue.list.length === 0"
class="mr-2" class="mr-2"
@ -39,7 +48,13 @@
indeterminate indeterminate
/> />
<v-alert v-if="error" class="mb-4" closable type="error" variant="tonal"> <v-alert
v-if="error"
class="mb-4"
closable
type="error"
variant="tonal"
>
{{ error }} {{ error }}
</v-alert> </v-alert>
@ -47,7 +62,11 @@
<!-- 普通编辑模式 --> <!-- 普通编辑模式 -->
<div v-if="!modelValue.advanced"> <div v-if="!modelValue.advanced">
<v-row class="mb-6"> <v-row class="mb-6">
<v-col cols="12" md="4" sm="6"> <v-col
cols="12"
md="4"
sm="6"
>
<v-text-field <v-text-field
v-model="newStudentName" v-model="newStudentName"
class="mb-4" class="mb-4"
@ -88,7 +107,10 @@
v-bind="props" v-bind="props"
> >
<v-card-text class="d-flex align-center pa-3"> <v-card-text class="d-flex align-center pa-3">
<v-menu :open-on-hover="!isMobile" location="bottom"> <v-menu
:open-on-hover="!isMobile"
location="bottom"
>
<template #activator="{ props: menuProps }"> <template #activator="{ props: menuProps }">
<v-btn <v-btn
class="mr-3 font-weight-medium" class="mr-3 font-weight-medium"
@ -100,7 +122,10 @@
</v-btn> </v-btn>
</template> </template>
<v-list density="compact" nav> <v-list
density="compact"
nav
>
<v-list-item <v-list-item
:disabled="index === 0" :disabled="index === 0"
prepend-icon="mdi-arrow-up-bold" prepend-icon="mdi-arrow-up-bold"
@ -108,7 +133,7 @@
> >
置顶 置顶
</v-list-item> </v-list-item>
<v-divider/> <v-divider />
<v-list-item <v-list-item
:disabled="index === 0" :disabled="index === 0"
prepend-icon="mdi-arrow-up" prepend-icon="mdi-arrow-up"
@ -172,7 +197,10 @@
</div> </div>
<!-- 高级编辑模式 --> <!-- 高级编辑模式 -->
<div v-else class="pt-2"> <div
v-else
class="pt-2"
>
<v-textarea <v-textarea
v-model="modelValue.text" v-model="modelValue.text"
hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行" hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行"
@ -187,7 +215,10 @@
</v-expand-transition> </v-expand-transition>
<v-row class="mt-6"> <v-row class="mt-6">
<v-col class="d-flex gap-2" cols="12"> <v-col
class="d-flex gap-2"
cols="12"
>
<v-btn <v-btn
:disabled="loading" :disabled="loading"
:loading="loading" :loading="loading"

View File

@ -1,7 +1,15 @@
<template> <template>
<v-card :disabled="!hasNamespaceInfo" :loading="loading" class="my-4"> <v-card
:disabled="!hasNamespaceInfo"
:loading="loading"
class="my-4"
>
<template #loader> <template #loader>
<v-progress-linear v-if="loading" color="primary" indeterminate/> <v-progress-linear
v-if="loading"
color="primary"
indeterminate
/>
</template> </template>
@ -175,7 +183,7 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn <v-btn
:loading="loading" :loading="loading"
color="primary" color="primary"
@ -213,7 +221,7 @@
<p>您确定要重新初始化云端存储吗</p> <p>您确定要重新初始化云端存储吗</p>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn <v-btn
variant="text" variant="text"
@click="showReinitDialog = false" @click="showReinitDialog = false"

View File

@ -1,16 +1,22 @@
<template> <template>
<settings-card icon="mdi-database-cog" title="数据源设置"> <settings-card
icon="mdi-database-cog"
title="数据源设置"
>
<v-list> <v-list>
<!-- 服务器模式设置 --> <!-- 服务器模式设置 -->
<template <template
v-if=" v-if="
currentProvider === 'kv-server' || currentProvider === 'kv-server' ||
currentProvider === 'classworkscloud' currentProvider === 'classworkscloud'
" "
> >
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-lan-connect"/> <v-icon
class="mr-3"
icon="mdi-lan-connect"
/>
</template> </template>
<v-list-item-title>检查服务器连接</v-list-item-title> <v-list-item-title>检查服务器连接</v-list-item-title>
<template #append> <template #append>
@ -22,90 +28,131 @@
测试连接 测试连接
</v-btn> </v-btn>
</template> </template>
</v-list-item </v-list-item><!-- 数据迁移仅对KV本地存储有效 -->
><!-- 数据迁移仅对KV本地存储有效 -->
</template> </template>
<!-- 本地存储设置 --> <!-- 本地存储设置 -->
<template v-if="currentProvider === 'kv-local'"> <template v-if="currentProvider === 'kv-local'">
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-database"/> <v-icon
class="mr-3"
icon="mdi-database"
/>
</template> </template>
<v-list-item-title>清除数据库缓存</v-list-item-title> <v-list-item-title>清除数据库缓存</v-list-item-title>
<v-list-item-subtitle <v-list-item-subtitle>
>这将清除所有本地数据库中的数据 这将清除所有本地数据库中的数据
</v-list-item-subtitle </v-list-item-subtitle>
>
<template #append> <template #append>
<v-btn color="error" variant="tonal" @click="confirmClearIndexedDB"> <v-btn
color="error"
variant="tonal"
@click="confirmClearIndexedDB"
>
清除 清除
</v-btn> </v-btn>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-database-export"/> <v-icon
class="mr-3"
icon="mdi-database-export"
/>
</template> </template>
<v-list-item-title>导出数据库</v-list-item-title> <v-list-item-title>导出数据库</v-list-item-title>
<template #append> <template #append>
<v-btn variant="tonal" @click="exportData"> 导出</v-btn> <v-btn
variant="tonal"
@click="exportData"
>
导出
</v-btn>
</template> </template>
</v-list-item> </v-list-item>
</template> </template>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-database-import"/> <v-icon
class="mr-3"
icon="mdi-database-import"
/>
</template> </template>
<v-list-item-title>迁移旧数据</v-list-item-title> <v-list-item-title>迁移旧数据</v-list-item-title>
<v-list-item-subtitle <v-list-item-subtitle>
>将旧的存储格式数据转移到新的KV存储 将旧的存储格式数据转移到新的KV存储
</v-list-item-subtitle </v-list-item-subtitle>
>
<template #append> <template #append>
<v-btn :loading="migrateLoading" variant="tonal" @click="migrateData"> <v-btn
:loading="migrateLoading"
variant="tonal"
@click="migrateData"
>
迁移 迁移
</v-btn> </v-btn>
</template> </template>
</v-list-item </v-list-item><!-- 显示机器ID -->
><!-- 显示机器ID -->
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-identifier"/> <v-icon
class="mr-3"
icon="mdi-identifier"
/>
</template> </template>
<v-list-item-title>本机唯一标识符</v-list-item-title> <v-list-item-title>本机唯一标识符</v-list-item-title>
<v-list-item-subtitle v-if="machineId">{{ <v-list-item-subtitle v-if="machineId">
machineId {{
machineId
}} }}
</v-list-item-subtitle> </v-list-item-subtitle>
<v-list-item-subtitle v-else>正在加载...</v-list-item-subtitle> <v-list-item-subtitle v-else>
正在加载...
</v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-lan-connect"/> <v-icon
class="mr-3"
icon="mdi-lan-connect"
/>
</template> </template>
<v-list-item-title>查看本地缓存</v-list-item-title> <v-list-item-title>查看本地缓存</v-list-item-title>
<template #append> <template #append>
<v-btn to="/cachemanagement" variant="tonal"> 查看</v-btn> <v-btn
to="/cachemanagement"
variant="tonal"
>
查看
</v-btn>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>
<!-- 确认对话框 --> <!-- 确认对话框 -->
<v-dialog v-model="confirmDialog" max-width="400"> <v-dialog
v-model="confirmDialog"
max-width="400"
>
<v-card> <v-card>
<v-card-title>{{ confirmTitle }}</v-card-title> <v-card-title>{{ confirmTitle }}</v-card-title>
<v-card-text>{{ confirmMessage }}</v-card-text> <v-card-text>{{ confirmMessage }}</v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn color="grey" variant="text" @click="confirmDialog = false" <v-btn
>取消 color="grey"
</v-btn variant="text"
@click="confirmDialog = false"
> >
<v-btn color="error" variant="tonal" @click="handleConfirm" 取消
>确认 </v-btn>
</v-btn <v-btn
color="error"
variant="tonal"
@click="handleConfirm"
> >
确认
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@ -1,33 +1,36 @@
<template> <template>
<settings-card border icon="mdi-monitor" title="显示设置"> <settings-card
border
icon="mdi-monitor"
title="显示设置"
>
<v-list> <v-list>
<setting-item :setting-key="'display.emptySubjectDisplay'"/> <setting-item :setting-key="'display.emptySubjectDisplay'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.dynamicSort'"/> <setting-item :setting-key="'display.dynamicSort'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.showRandomButton'"/> <setting-item :setting-key="'display.showRandomButton'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.showFullscreenButton'"/> <setting-item :setting-key="'display.showFullscreenButton'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.cardHoverEffect'"/> <setting-item :setting-key="'display.cardHoverEffect'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.enhancedTouchMode'"/> <setting-item :setting-key="'display.enhancedTouchMode'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.showQuickTools'"/> <setting-item :setting-key="'display.showQuickTools'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'display.showAntiScreenBurnCard'"/> <setting-item :setting-key="'display.showAntiScreenBurnCard'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.showExamScheduleButton'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.showExamScheduleButton'" />
</v-list> </v-list>
</settings-card> </settings-card>
</template> </template>

View File

@ -6,13 +6,22 @@
@click="handleClick" @click="handleClick"
> >
<v-card-text> <v-card-text>
<div ref="typewriter" class="typewriter-text"></div> <div
<div ref="sourceWriter" class="source-text"></div> ref="typewriter"
class="typewriter-text"
/>
<div
ref="sourceWriter"
class="source-text"
/>
</v-card-text> </v-card-text>
<transition name="fade"> <transition name="fade">
<v-chip v-if="currentQuote?.contributor" class="contributor"> <v-chip
v-if="currentQuote?.contributor"
class="contributor"
>
<v-avatar start> <v-avatar start>
<v-img :src="`https://github.com/${currentQuote.contributor}.png`"/> <v-img :src="`https://github.com/${currentQuote.contributor}.png`" />
</v-avatar> </v-avatar>
{{ currentQuote.contributor }} {{ currentQuote.contributor }}
</v-chip> </v-chip>
@ -49,6 +58,10 @@ export default {
this.initTypewriters(); this.initTypewriters();
}, },
beforeUnmount() {
[this.typewriter, this.sourceWriter].forEach(writer => writer?.stop());
},
methods: { methods: {
initTypewriters() { initTypewriters() {
this.typewriter = new Typewriter(this.$refs.typewriter, TYPEWRITER_CONFIG.main); this.typewriter = new Typewriter(this.$refs.typewriter, TYPEWRITER_CONFIG.main);
@ -93,10 +106,6 @@ export default {
console.error("复制失败:", err); console.error("复制失败:", err);
} }
} }
},
beforeUnmount() {
[this.typewriter, this.sourceWriter].forEach(writer => writer?.stop());
} }
}; };
</script> </script>

View File

@ -1,28 +1,30 @@
<template> <template>
<settings-card icon="mdi-cog" title="编辑设置"> <settings-card
icon="mdi-cog"
title="编辑设置"
>
<v-list> <v-list>
<setting-item :setting-key="'edit.autoSave'"/> <setting-item :setting-key="'edit.autoSave'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'edit.blockNonTodayAutoSave'"/> <setting-item :setting-key="'edit.blockNonTodayAutoSave'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'edit.confirmNonTodaySave'"/> <setting-item :setting-key="'edit.confirmNonTodaySave'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'edit.refreshBeforeEdit'"/> <setting-item :setting-key="'edit.refreshBeforeEdit'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'edit.autoSavePromptText'"/> <setting-item :setting-key="'edit.autoSavePromptText'" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item :setting-key="'edit.manualSavePromptText'"/> <setting-item :setting-key="'edit.manualSavePromptText'" />
</v-list> </v-list>
</settings-card> </settings-card>
</template> </template>

View File

@ -48,8 +48,15 @@
</div> </div>
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col
<setting-group border icon="mdi-book" title="科目配置"> cols="12"
md="6"
>
<setting-group
border
icon="mdi-book"
title="科目配置"
>
<v-list> <v-list>
<v-list-item> <v-list-item>
<v-text-field <v-text-field
@ -63,8 +70,14 @@
/> />
</v-list-item> </v-list-item>
<v-list-item v-for="subject in subjectList" :key="subject"> <v-list-item
<v-card border class="w-100 mb-2"> v-for="subject in subjectList"
:key="subject"
>
<v-card
border
class="w-100 mb-2"
>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-text-field <v-text-field
v-model="editedSubjects[subject]" v-model="editedSubjects[subject]"
@ -74,7 +87,7 @@
variant="plain" variant="plain"
@blur="updateSubject(subject)" @blur="updateSubject(subject)"
/> />
<v-spacer/> <v-spacer />
<v-btn <v-btn
color="error" color="error"
icon="mdi-delete" icon="mdi-delete"
@ -95,17 +108,24 @@
@keyup.enter="() => addBookType(subject)" @keyup.enter="() => addBookType(subject)"
/> />
<v-list border density="compact" rounded> <v-list
border
density="compact"
rounded
>
<v-list-item <v-list-item
v-for="(books, bookType) in config.subjects[subject].books" v-for="(books, bookType) in config.subjects[subject].books"
:key="bookType" :key="bookType"
:title="bookType" :title="bookType"
@click="openSubjectBookDialog(subject, bookType, books)" @click="openSubjectBookDialog(subject, bookType, books)"
> >
<template v-slot:prepend> <template #prepend>
<v-icon class="mr-2" icon="mdi-book-open-variant"/> <v-icon
class="mr-2"
icon="mdi-book-open-variant"
/>
</template> </template>
<template v-slot:append> <template #append>
<v-chip <v-chip
class="mr-2" class="mr-2"
color="info" color="info"
@ -130,8 +150,15 @@
</setting-group> </setting-group>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col
<setting-group border icon="mdi-cog" title="通用配置"> cols="12"
md="6"
>
<setting-group
border
icon="mdi-cog"
title="通用配置"
>
<v-list> <v-list>
<v-list-item> <v-list-item>
<v-text-field <v-text-field
@ -146,17 +173,24 @@
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<v-list border density="compact" rounded> <v-list
border
density="compact"
rounded
>
<v-list-item <v-list-item
v-for="(books, bookType) in config.commonSubject.books" v-for="(books, bookType) in config.commonSubject.books"
:key="bookType" :key="bookType"
:title="bookType" :title="bookType"
@click="openSubjectBookDialog('common', bookType, books)" @click="openSubjectBookDialog('common', bookType, books)"
> >
<template v-slot:prepend> <template #prepend>
<v-icon class="mr-2" icon="mdi-book-multiple"/> <v-icon
class="mr-2"
icon="mdi-book-multiple"
/>
</template> </template>
<template v-slot:append> <template #append>
<v-chip <v-chip
class="mr-2" class="mr-2"
color="info" color="info"
@ -176,7 +210,7 @@
</v-list> </v-list>
</v-list-item> </v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2" />
<v-list-item> <v-list-item>
<v-text-field <v-text-field
@ -191,14 +225,18 @@
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<v-list border density="compact" rounded> <v-list
border
density="compact"
rounded
>
<v-list-item <v-list-item
v-for="action in config.actions" v-for="action in config.actions"
:key="action" :key="action"
:title="action" :title="action"
@click="openActionDialog(action)" @click="openActionDialog(action)"
> >
<template v-slot:append> <template #append>
<v-btn <v-btn
color="error" color="error"
icon="mdi-delete" icon="mdi-delete"
@ -216,7 +254,10 @@
</v-row> </v-row>
<!-- 编辑弹框 --> <!-- 编辑弹框 -->
<v-dialog v-model="dialog.show" max-width="600px"> <v-dialog
v-model="dialog.show"
max-width="600px"
>
<v-card> <v-card>
<v-card-title class="text-h5 pa-4"> <v-card-title class="text-h5 pa-4">
{{ dialog.title }} {{ dialog.title }}
@ -235,22 +276,43 @@
/> />
</v-col> </v-col>
<v-col v-if="dialog.editedItem.type === 'subjectBook'" cols="12"> <v-col
<div class="text-subtitle-2 mb-2">所属科目</div> v-if="dialog.editedItem.type === 'subjectBook'"
<v-chip color="primary">{{ dialog.editedItem.subject }}</v-chip> cols="12"
>
<div class="text-subtitle-2 mb-2">
所属科目
</div>
<v-chip color="primary">
{{ dialog.editedItem.subject }}
</v-chip>
</v-col> </v-col>
<v-col v-if="['subjectBook', 'commonBook'].includes(dialog.editedItem.type)" cols="12"> <v-col
v-if="['subjectBook', 'commonBook'].includes(dialog.editedItem.type)"
cols="12"
>
<v-card variant="outlined"> <v-card variant="outlined">
<v-card-title class="text-subtitle-1 py-2">需完成部分</v-card-title> <v-card-title class="text-subtitle-1 py-2">
需完成部分
</v-card-title>
<v-card-text class="pt-0"> <v-card-text class="pt-0">
<v-list border class="mb-2" density="compact" rounded> <v-list
border
class="mb-2"
density="compact"
rounded
>
<v-list-item <v-list-item
v-for="(task, index) in dialog.editedItem.tasks" v-for="(task, index) in dialog.editedItem.tasks"
:key="index" :key="index"
> >
<template v-slot:prepend> <template #prepend>
<v-icon class="mr-2" icon="mdi-checkbox-blank-circle-outline" size="small"/> <v-icon
class="mr-2"
icon="mdi-checkbox-blank-circle-outline"
size="small"
/>
</template> </template>
<v-text-field <v-text-field
v-model="dialog.editedItem.tasks[index]" v-model="dialog.editedItem.tasks[index]"
@ -258,7 +320,7 @@
hide-details hide-details
variant="plain" variant="plain"
/> />
<template v-slot:append> <template #append>
<v-btn <v-btn
color="error" color="error"
icon="mdi-delete" icon="mdi-delete"
@ -287,7 +349,7 @@
</v-card-text> </v-card-text>
<v-card-actions class="pa-4"> <v-card-actions class="pa-4">
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="primary" color="primary"
variant="elevated" variant="elevated"

View File

@ -1,36 +1,60 @@
<template> <template>
<settings-card :loading="loading" icon="mdi-database-edit" title="KV数据库管理"> <settings-card
:loading="loading"
icon="mdi-database-edit"
title="KV数据库管理"
>
<v-list> <v-list>
<!-- 数据库连接状态 --> <!-- 数据库连接状态 -->
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon :color="connectionColor" :icon="connectionIcon" class="mr-3"/> <v-icon
:color="connectionColor"
:icon="connectionIcon"
class="mr-3"
/>
</template> </template>
<v-list-item-title>数据库状态</v-list-item-title> <v-list-item-title>数据库状态</v-list-item-title>
<v-list-item-subtitle>{{ connectionStatus }}</v-list-item-subtitle> <v-list-item-subtitle>{{ connectionStatus }}</v-list-item-subtitle>
<template #append> <template #append>
<v-btn :loading="loading" variant="tonal" @click="refreshConnection"> <v-btn
:loading="loading"
variant="tonal"
@click="refreshConnection"
>
刷新 刷新
</v-btn> </v-btn>
</template> </template>
</v-list-item> </v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2" />
<!-- 数据列表 --> <!-- 数据列表 -->
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-format-list-bulleted"/> <v-icon
class="mr-3"
icon="mdi-format-list-bulleted"
/>
</template> </template>
<v-list-item-title>数据条目</v-list-item-title> <v-list-item-title>数据条目</v-list-item-title>
<v-list-item-subtitle> {{ kvData.length }} 条记录</v-list-item-subtitle> <v-list-item-subtitle> {{ kvData.length }} 条记录</v-list-item-subtitle>
<template #append> <template #append>
<v-btn-group variant="tonal"> <v-btn-group variant="tonal">
<v-btn :loading="loadingData" @click="loadKvData"> <v-btn
:loading="loadingData"
@click="loadKvData"
>
加载数据 加载数据
</v-btn> </v-btn>
<v-btn :disabled="!isKvProvider" @click="createNewItem"> <v-btn
<v-icon class="mr-1" icon="mdi-plus"/> :disabled="!isKvProvider"
@click="createNewItem"
>
<v-icon
class="mr-1"
icon="mdi-plus"
/>
新建 新建
</v-btn> </v-btn>
</v-btn-group> </v-btn-group>
@ -39,11 +63,18 @@
</v-list> </v-list>
<!-- 数据表格 --> <!-- 数据表格 -->
<v-card v-if="kvData.length > 0" class="mt-4" variant="outlined"> <v-card
v-if="kvData.length > 0"
class="mt-4"
variant="outlined"
>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-table"/> <v-icon
class="mr-2"
icon="mdi-table"
/>
KV数据列表 KV数据列表
<v-spacer/> <v-spacer />
<v-text-field <v-text-field
v-model="searchQuery" v-model="searchQuery"
clearable clearable
@ -69,7 +100,10 @@
</template> </template>
<template #[`item.actions`]="{ item }"> <template #[`item.actions`]="{ item }">
<v-btn-group density="compact" variant="text"> <v-btn-group
density="compact"
variant="text"
>
<v-btn <v-btn
icon="mdi-eye" icon="mdi-eye"
size="small" size="small"
@ -102,13 +136,23 @@
</v-card> </v-card>
<!-- 查看数据对话框 --> <!-- 查看数据对话框 -->
<v-dialog v-model="viewDialog" max-width="800px"> <v-dialog
v-model="viewDialog"
max-width="800px"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-eye"/> <v-icon
class="mr-2"
icon="mdi-eye"
/>
查看数据 查看数据
<v-spacer/> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="viewDialog = false"/> <v-btn
icon="mdi-close"
variant="text"
@click="viewDialog = false"
/>
</v-card-title> </v-card-title>
<v-card-subtitle v-if="selectedItem"> <v-card-subtitle v-if="selectedItem">
@ -128,12 +172,21 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn variant="tonal" @click="copyToClipboard(selectedItem?.value)"> <v-btn
<v-icon class="mr-1" icon="mdi-content-copy"/> variant="tonal"
@click="copyToClipboard(selectedItem?.value)"
>
<v-icon
class="mr-1"
icon="mdi-content-copy"
/>
复制数据 复制数据
</v-btn> </v-btn>
<v-btn variant="text" @click="viewDialog = false"> <v-btn
variant="text"
@click="viewDialog = false"
>
关闭 关闭
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -141,13 +194,23 @@
</v-dialog> </v-dialog>
<!-- 编辑数据对话框 --> <!-- 编辑数据对话框 -->
<v-dialog v-model="editDialog" max-width="800px"> <v-dialog
v-model="editDialog"
max-width="800px"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-pencil"/> <v-icon
class="mr-2"
icon="mdi-pencil"
/>
编辑数据 编辑数据
<v-spacer/> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="closeEditDialog"/> <v-btn
icon="mdi-close"
variant="text"
@click="closeEditDialog"
/>
</v-card-title> </v-card-title>
<v-card-subtitle v-if="editingItem"> <v-card-subtitle v-if="editingItem">
@ -167,8 +230,11 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn variant="text" @click="closeEditDialog"> <v-btn
variant="text"
@click="closeEditDialog"
>
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
@ -185,13 +251,23 @@
</v-dialog> </v-dialog>
<!-- 新建数据对话框 --> <!-- 新建数据对话框 -->
<v-dialog v-model="createDialog" max-width="800px"> <v-dialog
v-model="createDialog"
max-width="800px"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-plus"/> <v-icon
class="mr-2"
icon="mdi-plus"
/>
新建数据 新建数据
<v-spacer/> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="closeCreateDialog"/> <v-btn
icon="mdi-close"
variant="text"
@click="closeCreateDialog"
/>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
@ -211,15 +287,18 @@
:error-messages="isValidNewJson ? [] : ['请输入有效的JSON格式']" :error-messages="isValidNewJson ? [] : ['请输入有效的JSON格式']"
class="font-monospace" class="font-monospace"
label="数据内容 (JSON格式)" label="数据内容 (JSON格式)"
placeholder='请输入JSON数据{"name": "value"}' placeholder="请输入JSON数据{&quot;name&quot;: &quot;value&quot;}"
rows="15" rows="15"
variant="outlined" variant="outlined"
/> />
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn variant="text" @click="closeCreateDialog"> <v-btn
variant="text"
@click="closeCreateDialog"
>
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
@ -236,13 +315,23 @@
</v-dialog> </v-dialog>
<!-- 云端地址对话框 --> <!-- 云端地址对话框 -->
<v-dialog v-model="cloudUrlDialog" max-width="800px"> <v-dialog
v-model="cloudUrlDialog"
max-width="800px"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-cloud-download"/> <v-icon
class="mr-2"
icon="mdi-cloud-download"
/>
获取云端访问地址 获取云端访问地址
<v-spacer/> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="cloudUrlDialog = false"/> <v-btn
icon="mdi-close"
variant="text"
@click="cloudUrlDialog = false"
/>
</v-card-title> </v-card-title>
<v-card-subtitle v-if="selectedCloudItem"> <v-card-subtitle v-if="selectedCloudItem">
@ -250,19 +339,43 @@
</v-card-subtitle> </v-card-subtitle>
<v-card-text> <v-card-text>
<v-alert v-if="cloudUrlError" class="mb-4" type="error" variant="tonal"> <v-alert
v-if="cloudUrlError"
class="mb-4"
type="error"
variant="tonal"
>
{{ cloudUrlError }} {{ cloudUrlError }}
</v-alert> </v-alert>
<v-alert v-if="cloudUrlResult && cloudUrlResult.success" class="mb-4" type="success" variant="tonal"> <v-alert
v-if="cloudUrlResult && cloudUrlResult.success"
class="mb-4"
type="success"
variant="tonal"
>
<v-alert-title>云端地址获取成功</v-alert-title> <v-alert-title>云端地址获取成功</v-alert-title>
<div class="mt-2"> <div class="mt-2">
<div v-if="cloudUrlResult.migrated" class="mb-2"> <div
<v-icon class="mr-1" color="success" icon="mdi-database-arrow-up"/> v-if="cloudUrlResult.migrated"
class="mb-2"
>
<v-icon
class="mr-1"
color="success"
icon="mdi-database-arrow-up"
/>
数据已从本地迁移到云端 数据已从本地迁移到云端
</div> </div>
<div v-if="cloudUrlResult.configured" class="mb-2"> <div
<v-icon class="mr-1" color="info" icon="mdi-cog"/> v-if="cloudUrlResult.configured"
class="mb-2"
>
<v-icon
class="mr-1"
color="info"
icon="mdi-cog"
/>
云端配置已自动设置 云端配置已自动设置
</div> </div>
</div> </div>
@ -279,10 +392,16 @@
@click:append-inner="copyCloudUrl" @click:append-inner="copyCloudUrl"
/> />
<v-expansion-panels v-if="cloudUrlResult && cloudUrlResult.url" class="mt-4"> <v-expansion-panels
v-if="cloudUrlResult && cloudUrlResult.url"
class="mt-4"
>
<v-expansion-panel> <v-expansion-panel>
<v-expansion-panel-title> <v-expansion-panel-title>
<v-icon class="mr-2" icon="mdi-cog"/> <v-icon
class="mr-2"
icon="mdi-cog"
/>
高级选项 高级选项
</v-expansion-panel-title> </v-expansion-panel-title>
<v-expansion-panel-text> <v-expansion-panel-text>
@ -303,7 +422,10 @@
variant="tonal" variant="tonal"
@click="refreshCloudUrl" @click="refreshCloudUrl"
> >
<v-icon class="mr-1" icon="mdi-refresh"/> <v-icon
class="mr-1"
icon="mdi-refresh"
/>
重新获取 重新获取
</v-btn> </v-btn>
</v-expansion-panel-text> </v-expansion-panel-text>
@ -312,8 +434,11 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn variant="text" @click="cloudUrlDialog = false"> <v-btn
variant="text"
@click="cloudUrlDialog = false"
>
关闭 关闭
</v-btn> </v-btn>
<v-btn <v-btn
@ -322,7 +447,10 @@
variant="tonal" variant="tonal"
@click="openCloudUrl" @click="openCloudUrl"
> >
<v-icon class="mr-1" icon="mdi-open-in-new"/> <v-icon
class="mr-1"
icon="mdi-open-in-new"
/>
在新窗口打开 在新窗口打开
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -330,24 +458,37 @@
</v-dialog> </v-dialog>
<!-- 删除确认对话框 --> <!-- 删除确认对话框 -->
<v-dialog v-model="deleteDialog" max-width="400px"> <v-dialog
v-model="deleteDialog"
max-width="400px"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center text-error"> <v-card-title class="d-flex align-center text-error">
<v-icon class="mr-2" icon="mdi-alert"/> <v-icon
class="mr-2"
icon="mdi-alert"
/>
确认删除 确认删除
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
确定要删除键名为 <code>{{ itemToDelete?.key }}</code> 的数据吗 确定要删除键名为 <code>{{ itemToDelete?.key }}</code> 的数据吗
<br><br> <br><br>
<v-alert class="mt-2" type="warning" variant="tonal"> <v-alert
class="mt-2"
type="warning"
variant="tonal"
>
此操作不可撤销请谨慎操作 此操作不可撤销请谨慎操作
</v-alert> </v-alert>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn variant="text" @click="deleteDialog = false"> <v-btn
variant="text"
@click="deleteDialog = false"
>
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn

View File

@ -1,22 +1,23 @@
<template> <template>
<settings-card icon="mdi-cog" title="编辑设置"> <settings-card
icon="mdi-cog"
title="编辑设置"
>
<v-list> <v-list>
<setting-item setting-key="randomPicker.enabled"/> <setting-item setting-key="randomPicker.enabled" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item setting-key="randomPicker.mode"/> <setting-item setting-key="randomPicker.mode" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item setting-key="randomPicker.minNumber"/> <setting-item setting-key="randomPicker.minNumber" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item setting-key="randomPicker.maxNumber"/> <setting-item setting-key="randomPicker.maxNumber" />
<v-divider class="my-2"/> <v-divider class="my-2" />
<setting-item setting-key="randomPicker.defaultCount"/> <setting-item setting-key="randomPicker.defaultCount" />
<v-divider class="my-2"/>
<setting-item setting-key="randomPicker.animation"/>
<v-divider class="my-2" />
<setting-item setting-key="randomPicker.animation" />
</v-list> </v-list>
</settings-card> </settings-card>
</template> </template>

View File

@ -1,13 +1,21 @@
<template> <template>
<settings-card icon="mdi-refresh-circle" title="刷新设置"> <settings-card
icon="mdi-refresh-circle"
title="刷新设置"
>
<v-form> <v-form>
<v-list> <v-list>
<setting-item setting-key="refresh.auto" title="自动刷新"/> <setting-item
<v-divider class="my-2"/> setting-key="refresh.auto"
title="自动刷新"
/>
<v-divider class="my-2" />
<setting-item setting-key="refresh.interval" title="刷新间隔"/> <setting-item
setting-key="refresh.interval"
title="刷新间隔"
/>
</v-list> </v-list>
</v-form> </v-form>
</settings-card> </settings-card>
</template> </template>

View File

@ -58,10 +58,16 @@
</div> </div>
<!-- 添加新科目 --> <!-- 添加新科目 -->
<v-card class="mb-4" variant="outlined"> <v-card
class="mb-4"
variant="outlined"
>
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col cols="12" sm="6"> <v-col
cols="12"
sm="6"
>
<v-text-field <v-text-field
v-model="newSubjectName" v-model="newSubjectName"
:rules="[v => !!v || '科目名称不能为空']" :rules="[v => !!v || '科目名称不能为空']"
@ -85,7 +91,7 @@
v-for="(subject, index) in subjects" v-for="(subject, index) in subjects"
:key="subject.order" :key="subject.order"
> >
<template v-slot:prepend> <template #prepend>
<div class="d-flex flex-column align-center mr-2"> <div class="d-flex flex-column align-center mr-2">
<v-btn <v-btn
:disabled="index === 0" :disabled="index === 0"
@ -114,7 +120,7 @@
/> />
</v-list-item-title> </v-list-item-title>
<template v-slot:append> <template #append>
<v-btn <v-btn
color="error" color="error"
icon="mdi-delete" icon="mdi-delete"

View File

@ -1,9 +1,15 @@
<template> <template>
<settings-card icon="mdi-palette" title="主题设置"> <settings-card
icon="mdi-palette"
title="主题设置"
>
<v-list> <v-list>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-theme-light-dark"/> <v-icon
class="mr-3"
icon="mdi-theme-light-dark"
/>
</template> </template>
<v-list-item-title>主题模式</v-list-item-title> <v-list-item-title>主题模式</v-list-item-title>
<v-list-item-subtitle>选择明亮或暗黑主题</v-list-item-subtitle> <v-list-item-subtitle>选择明亮或暗黑主题</v-list-item-subtitle>
@ -14,11 +20,17 @@
density="comfortable" density="comfortable"
> >
<v-btn value="light"> <v-btn value="light">
<v-icon class="mr-2" icon="mdi-white-balance-sunny"/> <v-icon
class="mr-2"
icon="mdi-white-balance-sunny"
/>
明亮 明亮
</v-btn> </v-btn>
<v-btn value="dark"> <v-btn value="dark">
<v-icon class="mr-2" icon="mdi-moon-waning-crescent"/> <v-icon
class="mr-2"
icon="mdi-moon-waning-crescent"
/>
暗黑 暗黑
</v-btn> </v-btn>
</v-btn-toggle> </v-btn-toggle>
@ -37,6 +49,11 @@ export default {
name: 'ThemeSettingsCard', name: 'ThemeSettingsCard',
components: {SettingsCard}, components: {SettingsCard},
setup() {
const theme = useTheme();
return {theme};
},
data() { data() {
return { return {
localTheme: getSetting('theme.mode') localTheme: getSetting('theme.mode')
@ -50,11 +67,6 @@ export default {
} }
}, },
setup() {
const theme = useTheme();
return {theme};
},
methods: { methods: {
updateTheme(mode) { updateTheme(mode) {
this.theme.global.name.value = mode; this.theme.global.name.value = mode;

View File

@ -1,7 +1,7 @@
<template> <template>
<v-app> <v-app>
<v-main> <v-main>
<router-view/> <router-view />
</v-main> </v-main>
</v-app> </v-app>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<error404/> <error404 />
</template> </template>
<script setup> <script setup>

View File

@ -3,39 +3,82 @@
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<div class="d-flex align-center mb-6"> <div class="d-flex align-center mb-6">
<v-icon class="mr-3" color="primary" size="x-large">mdi-database-cog-outline</v-icon> <v-icon
class="mr-3"
color="primary"
size="x-large"
>
mdi-database-cog-outline
</v-icon>
<div> <div>
<h1 class="text-h4 ">缓存管理</h1> <h1 class="text-h4 ">
<div class="text-subtitle-1 text-grey">管理应用的本地缓存资源</div> 缓存管理
</h1>
<div class="text-subtitle-1 text-grey">
管理应用的本地缓存资源
</div>
</div> </div>
</div> </div>
<v-card class="mb-6" color="info" density="compact" variant="tonal"> <v-card
class="mb-6"
color="info"
density="compact"
variant="tonal"
>
<v-card-text class="d-flex align-center"> <v-card-text class="d-flex align-center">
<v-icon class="mr-2" color="info">mdi-information-outline</v-icon> <v-icon
class="mr-2"
color="info"
>
mdi-information-outline
</v-icon>
<span>在这里您可以查看和管理应用的缓存文件清除缓存可能会导致应用需要重新下载资源但有助于解决某些显示问题</span> <span>在这里您可以查看和管理应用的缓存文件清除缓存可能会导致应用需要重新下载资源但有助于解决某些显示问题</span>
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-row> <v-row>
<v-col cols="12" md="8"> <v-col
<v-card class="mb-4" variant="tonal"> cols="12"
md="8"
>
<v-card
class="mb-4"
variant="tonal"
>
<v-card-text> <v-card-text>
<div class="d-flex align-center mb-2"> <div class="d-flex align-center mb-2">
<v-icon class="mr-2" color="primary">mdi-information</v-icon> <v-icon
class="mr-2"
color="primary"
>
mdi-information
</v-icon>
<span class="text-h6">什么是缓存</span> <span class="text-h6">什么是缓存</span>
</div> </div>
<p> <p>
缓存是浏览器在本地存储的网站资源副本如图片脚本和样式表等这些缓存可以加快页面加载速度减少数据使用并在离线时提供基本功能</p> 缓存是浏览器在本地存储的网站资源副本如图片脚本和样式表等这些缓存可以加快页面加载速度减少数据使用并在离线时提供基本功能
</p>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
<v-col cols="12" md="4"> <v-col
<v-card class="mb-4" variant="tonal"> cols="12"
md="4"
>
<v-card
class="mb-4"
variant="tonal"
>
<v-card-text> <v-card-text>
<div class="d-flex align-center mb-2"> <div class="d-flex align-center mb-2">
<v-icon class="mr-2" color="warning">mdi-lightbulb-outline</v-icon> <v-icon
class="mr-2"
color="warning"
>
mdi-lightbulb-outline
</v-icon>
<span class="text-h6">何时清除缓存</span> <span class="text-h6">何时清除缓存</span>
</div> </div>
<ul class="pl-4"> <ul class="pl-4">
@ -48,7 +91,7 @@
</v-col> </v-col>
</v-row> </v-row>
<CacheManager/> <CacheManager />
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>

View File

@ -38,7 +38,7 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
<MigrationTool ref="migrationTool"/> <MigrationTool ref="migrationTool" />
</v-col> </v-col>
</v-row> </v-row>
@ -83,7 +83,7 @@
</v-alert> </v-alert>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer />
<v-btn <v-btn
color="grey-darken-1" color="grey-darken-1"
variant="text" variant="text"

View File

@ -1,7 +1,17 @@
<template> <template>
<v-container class="fill-height" fluid> <v-container
<v-row align="center" justify="center"> class="fill-height"
<v-col cols="12" md="6" sm="8"> fluid
>
<v-row
align="center"
justify="center"
>
<v-col
cols="12"
md="6"
sm="8"
>
<v-card> <v-card>
<v-card-title class="text-h5"> <v-card-title class="text-h5">
{{ status === 'processing' ? '正在处理授权...' : status === 'success' ? '授权成功' : '授权失败' }} {{ status === 'processing' ? '正在处理授权...' : status === 'success' ? '授权成功' : '授权失败' }}
@ -12,12 +22,17 @@
class="mb-4" class="mb-4"
color="primary" color="primary"
indeterminate indeterminate
></v-progress-linear> />
<p>{{ message }}</p> <p>{{ message }}</p>
</v-card-text> </v-card-text>
<v-card-actions v-if="status !== 'processing'"> <v-card-actions v-if="status !== 'processing'">
<v-spacer></v-spacer> <v-spacer />
<v-btn color="primary" @click="goToHome">返回首页</v-btn> <v-btn
color="primary"
@click="goToHome"
>
返回首页
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-col> </v-col>

View File

@ -2,9 +2,17 @@
<v-container class="fill-height"> <v-container class="fill-height">
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-card class="elevation-12" border> <v-card
class="elevation-12"
border
>
<v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4"> <v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4">
<v-icon color="white" class="mr-2">mdi-swap-horizontal</v-icon> <v-icon
color="white"
class="mr-2"
>
mdi-swap-horizontal
</v-icon>
课程表转换工具 课程表转换工具
</v-card-title> </v-card-title>
<v-card-subtitle> <v-card-subtitle>
@ -22,7 +30,9 @@
@click:close="error = ''" @click:close="error = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-alert-circle</v-icon> <v-icon class="mr-2">
mdi-alert-circle
</v-icon>
{{ error }} {{ error }}
</div> </div>
</v-alert> </v-alert>
@ -38,38 +48,75 @@
@click:close="success = ''" @click:close="success = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-check-circle</v-icon> <v-icon class="mr-2">
mdi-check-circle
</v-icon>
{{ success }} {{ success }}
</div> </div>
</v-alert> </v-alert>
<!-- 输入方式选择 --> <!-- 输入方式选择 -->
<v-tabs v-model="activeTab" class="mb-4 mx-2" color="primary" rounded> <v-tabs
<v-tab value="text" class="px-5"> v-model="activeTab"
<v-icon start>mdi-text-box</v-icon> class="mb-4 mx-2"
color="primary"
rounded
>
<v-tab
value="text"
class="px-5"
>
<v-icon start>
mdi-text-box
</v-icon>
文本粘贴 文本粘贴
</v-tab> </v-tab>
<v-tab value="file" class="px-5"> <v-tab
<v-icon start>mdi-file-upload</v-icon> value="file"
class="px-5"
>
<v-icon start>
mdi-file-upload
</v-icon>
文件上传 文件上传
</v-tab> </v-tab>
</v-tabs> </v-tabs>
<!-- 格式选择 --> <!-- 格式选择 -->
<v-btn-toggle v-model="formatMode" color="primary" class="mb-4 mx-2" mandatory density="comfortable" border <v-btn-toggle
rounded> v-model="formatMode"
<v-btn value="auto">自动检测</v-btn> color="primary"
<v-btn value="json">JSON</v-btn> class="mb-4 mx-2"
<v-btn value="yaml" :disabled="!yamlLibLoaded"> mandatory
density="comfortable"
border
rounded
>
<v-btn value="auto">
自动检测
</v-btn>
<v-btn value="json">
JSON
</v-btn>
<v-btn
value="yaml"
:disabled="!yamlLibLoaded"
>
YAML YAML
<v-tooltip activator="parent" location="bottom"> <v-tooltip
activator="parent"
location="bottom"
>
{{ yamlLibLoaded ? 'YAML解析库已加载' : '正在加载YAML解析库...' }} {{ yamlLibLoaded ? 'YAML解析库已加载' : '正在加载YAML解析库...' }}
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
</v-btn-toggle> </v-btn-toggle>
<!-- 添加当前检测到的格式提示 --> <!-- 添加当前检测到的格式提示 -->
<div v-if="jsonText && formatMode === 'auto'" class="text-caption mb-2"> <div
v-if="jsonText && formatMode === 'auto'"
class="text-caption mb-2"
>
检测到的格式: {{ isYaml(jsonText) ? 'YAML' : 'JSON' }} 检测到的格式: {{ isYaml(jsonText) ? 'YAML' : 'JSON' }}
</div> </div>
@ -85,7 +132,7 @@
rows="6" rows="6"
placeholder="请在此粘贴CSES格式的数据..." placeholder="请在此粘贴CSES格式的数据..."
@input="handleTextChange" @input="handleTextChange"
></v-textarea> />
</div> </div>
</v-window-item> </v-window-item>
<v-window-item value="file"> <v-window-item value="file">
@ -96,13 +143,13 @@
prepend-icon="mdi-file-upload" prepend-icon="mdi-file-upload"
:loading="loading" :loading="loading"
:disabled="loading" :disabled="loading"
@change="handleFileChange"
hint="支持JSON、YAML格式文件" hint="支持JSON、YAML格式文件"
persistent-hint persistent-hint
:rules="[ :rules="[
v => !v || v.size < 2000000 || '文件大小不能超过 2 MB', v => !v || v.size < 2000000 || '文件大小不能超过 2 MB',
]" ]"
></v-file-input> @change="handleFileChange"
/>
<v-alert <v-alert
v-if="file && formatMode === 'auto'" v-if="file && formatMode === 'auto'"
@ -118,17 +165,33 @@
<!-- 设置面板 --> <!-- 设置面板 -->
<v-col cols="12"> <v-col cols="12">
<v-card flat class="pa-4 rounded-lg" border> <v-card
flat
class="pa-4 rounded-lg"
border
>
<div class="d-flex align-center mb-3"> <div class="d-flex align-center mb-3">
<v-icon color="primary" class="mr-2">mdi-calendar-multiselect</v-icon> <v-icon
<h3 class="text-subtitle-1 font-weight-medium mr-auto">选择导出天数</h3> color="primary"
class="mr-2"
>
mdi-calendar-multiselect
</v-icon>
<h3 class="text-subtitle-1 font-weight-medium mr-auto">
选择导出天数
</h3>
<v-btn <v-btn
variant="text" variant="text"
color="primary" color="primary"
class="ml-2" class="ml-2"
@click="selectAllDays" @click="selectAllDays"
> >
<v-icon start size="small">mdi-checkbox-multiple-marked</v-icon> <v-icon
start
size="small"
>
mdi-checkbox-multiple-marked
</v-icon>
全选 全选
</v-btn> </v-btn>
<v-btn <v-btn
@ -137,19 +200,37 @@
class="ml-2" class="ml-2"
@click="clearSelectedDays" @click="clearSelectedDays"
> >
<v-icon start size="small">mdi-checkbox-multiple-blank-outline</v-icon> <v-icon
start
size="small"
>
mdi-checkbox-multiple-blank-outline
</v-icon>
清除 清除
</v-btn> </v-btn>
</div> </div>
<v-chip-group v-model="selectedDays" multiple class="mb-2" color="primary"> <v-chip-group
<v-chip v-for="day in 7" :key="day" :value="day" filter variant="tonal" class="filter-chip" label> v-model="selectedDays"
multiple
class="mb-2"
color="primary"
>
<v-chip
v-for="day in 7"
:key="day"
:value="day"
filter
variant="tonal"
class="filter-chip"
label
>
{{ dayNames[day] }} {{ dayNames[day] }}
<v-badge <v-badge
v-if="getDaySchedule(day).length > 0" v-if="getDaySchedule(day).length > 0"
:content="getDaySchedule(day).length" :content="getDaySchedule(day).length"
color="primary" color="primary"
inline inline
></v-badge> />
</v-chip> </v-chip>
</v-chip-group> </v-chip-group>
</v-card> </v-card>
@ -157,31 +238,51 @@
<!-- 改进设置选项卡显示为开关组 --> <!-- 改进设置选项卡显示为开关组 -->
<v-col cols="12"> <v-col cols="12">
<v-card flat class="pa-4 rounded-lg" border> <v-card
flat
class="pa-4 rounded-lg"
border
>
<div class="d-flex align-center mb-3"> <div class="d-flex align-center mb-3">
<v-icon color="primary" class="mr-2">mdi-cog</v-icon> <v-icon
<h3 class="text-subtitle-1 font-weight-medium">显示配置</h3> color="primary"
class="mr-2"
>
mdi-cog
</v-icon>
<h3 class="text-subtitle-1 font-weight-medium">
显示配置
</h3>
</div> </div>
<v-row> <v-row>
<v-col cols="12" sm="6"> <v-col
cols="12"
sm="6"
>
<v-switch <v-switch
v-model="settings.hideTeacherName" v-model="settings.hideTeacherName"
label="不显示教师姓名" label="不显示教师姓名"
color="primary" color="primary"
inset inset
hide-details hide-details
></v-switch> />
</v-col> </v-col>
<v-col cols="12" sm="6"> <v-col
cols="12"
sm="6"
>
<v-switch <v-switch
v-model="settings.hideRoom" v-model="settings.hideRoom"
label="不显示教室信息" label="不显示教室信息"
color="primary" color="primary"
inset inset
hide-details hide-details
></v-switch> />
</v-col> </v-col>
<v-col cols="12" sm="6"> <v-col
cols="12"
sm="6"
>
<v-text-field <v-text-field
v-model.number="settings.totalWeeks" v-model.number="settings.totalWeeks"
label="总周数" label="总周数"
@ -193,19 +294,23 @@
variant="outlined" variant="outlined"
prepend-inner-icon="mdi-calendar-week" prepend-inner-icon="mdi-calendar-week"
class="mt-3" class="mt-3"
></v-text-field> />
</v-col> </v-col>
</v-row> </v-row>
</v-card> </v-card>
</v-col> </v-col>
<!-- 添加加载状态的骨架屏 --> <!-- 添加加载状态的骨架屏 -->
<v-card v-if="loading" class="my-4" outlined> <v-card
v-if="loading"
class="my-4"
outlined
>
<v-card-text> <v-card-text>
<v-skeleton-loader <v-skeleton-loader
type="table" type="table"
class="mx-auto" class="mx-auto"
></v-skeleton-loader> />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -230,12 +335,31 @@
</v-alert> </v-alert>
<!-- 课程表预览 --> <!-- 课程表预览 -->
<v-card v-if="processedData" class="my-4" elevation="1"> <v-card
v-if="processedData"
class="my-4"
elevation="1"
>
<v-card-title class="d-flex align-center pa-4 bg-primary-lighten-5"> <v-card-title class="d-flex align-center pa-4 bg-primary-lighten-5">
<v-icon color="primary" class="mr-2">mdi-table</v-icon> <v-icon
color="primary"
class="mr-2"
>
mdi-table
</v-icon>
<span class="font-weight-bold">课程表</span> <span class="font-weight-bold">课程表</span>
<v-chip color="primary" class="ml-3" size="small" pill> <v-chip
<v-icon start size="x-small">mdi-book-open-variant</v-icon> color="primary"
class="ml-3"
size="small"
pill
>
<v-icon
start
size="x-small"
>
mdi-book-open-variant
</v-icon>
{{ processedData.tableData.length }} 节课程 {{ processedData.tableData.length }} 节课程
</v-chip> </v-chip>
</v-card-title> </v-card-title>
@ -263,8 +387,15 @@
/> />
</template> </template>
<template v-for="day in 7" #[`item.${day}`]="{ item }" :key="day"> <template
<div v-if="item[day]" class="course-cell"> v-for="day in 7"
#[`item.${day}`]="{ item }"
:key="day"
>
<div
v-if="item[day]"
class="course-cell"
>
<template v-if="Array.isArray(item[day])"> <template v-if="Array.isArray(item[day])">
<div <div
v-for="(course, index) in item[day]" v-for="(course, index) in item[day]"
@ -275,12 +406,12 @@
<span <span
v-if="!settings.hideTeacherName && course.teacher" v-if="!settings.hideTeacherName && course.teacher"
> >
<br/>{{ course.teacher }} <br>{{ course.teacher }}
</span> </span>
<span <span
v-if="!settings.hideRoom && course.room" v-if="!settings.hideRoom && course.room"
> >
<br/>{{ course.room }} <br>{{ course.room }}
</span> </span>
<span <span
v-if="course.weekType" v-if="course.weekType"
@ -295,12 +426,12 @@
<span <span
v-if="!settings.hideTeacherName && item[day].teacher" v-if="!settings.hideTeacherName && item[day].teacher"
> >
<br/>{{ item[day].teacher }} <br>{{ item[day].teacher }}
</span> </span>
<span <span
v-if="!settings.hideRoom && item[day].room" v-if="!settings.hideRoom && item[day].room"
> >
<br/>{{ item[day].room }} <br>{{ item[day].room }}
</span> </span>
<span <span
v-if="item[day].weekType" v-if="item[day].weekType"
@ -316,18 +447,48 @@
</v-card> </v-card>
<!-- 时间表 --> <!-- 时间表 -->
<v-card v-if="hasExportData" class="my-4" elevation="1"> <v-card
v-if="hasExportData"
class="my-4"
elevation="1"
>
<v-card-title class="d-flex align-center pa-4 bg-primary-lighten-5"> <v-card-title class="d-flex align-center pa-4 bg-primary-lighten-5">
<v-icon color="primary" class="mr-2">mdi-timetable</v-icon> <v-icon
color="primary"
class="mr-2"
>
mdi-timetable
</v-icon>
<span class="font-weight-bold">每日课程时间表</span> <span class="font-weight-bold">每日课程时间表</span>
<v-chip class="ml-3" size="small" color="primary" pill> <v-chip
<v-icon start size="x-small">mdi-clock-outline</v-icon> class="ml-3"
size="small"
color="primary"
pill
>
<v-icon
start
size="x-small"
>
mdi-clock-outline
</v-icon>
{{ totalClassHours }} 课时 {{ totalClassHours }} 课时
</v-chip> </v-chip>
<v-tooltip v-if="exportPeriods.length > 0"> <v-tooltip v-if="exportPeriods.length > 0">
<template v-slot:activator="{ props }"> <template #activator="{ props }">
<v-chip class="ml-2" size="small" color="info" v-bind="props" pill> <v-chip
<v-icon start size="x-small">mdi-information-outline</v-icon> class="ml-2"
size="small"
color="info"
v-bind="props"
pill
>
<v-icon
start
size="x-small"
>
mdi-information-outline
</v-icon>
节次已重排 节次已重排
</v-chip> </v-chip>
</template> </template>
@ -336,93 +497,165 @@
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<!-- 美化日期导航标签 --> <!-- 美化日期导航标签 -->
<v-tabs v-if="daysWithSchedule.length > 0" v-model="activeDay" class="mb-4" color="primary" grow <v-tabs
align-tabs="center"> v-if="daysWithSchedule.length > 0"
<v-tab v-for="day in daysWithSchedule" :key="day" :value="day" class="px-2 font-weight-medium"> v-model="activeDay"
class="mb-4"
color="primary"
grow
align-tabs="center"
>
<v-tab
v-for="day in daysWithSchedule"
:key="day"
:value="day"
class="px-2 font-weight-medium"
>
{{ dayNames[day] }} {{ dayNames[day] }}
<v-badge <v-badge
:content="getDaySchedule(day).length" :content="getDaySchedule(day).length"
color="primary" color="primary"
inline inline
></v-badge> />
</v-tab> </v-tab>
</v-tabs> </v-tabs>
<!-- 当前选中日期的课程表 --> <!-- 当前选中日期的课程表 -->
<v-window v-model="activeDay"> <v-window v-model="activeDay">
<v-window-item v-for="day in daysWithSchedule" :key="day" :value="day"> <v-window-item
<v-table density="compact" class="rounded" :headers-length="6" disable-sort> v-for="day in daysWithSchedule"
:key="day"
:value="day"
>
<v-table
density="compact"
class="rounded"
:headers-length="6"
disable-sort
>
<thead> <thead>
<tr> <tr>
<th class="text-center">节次</th> <th class="text-center">
<th>课程</th> 节次
<th>时间</th> </th>
<th>教师</th> <th>课程</th>
<th>教室</th> <th>时间</th>
<th>周次</th> <th>教师</th>
</tr> <th>教室</th>
<th>周次</th>
</tr>
</thead> </thead>
<tbody> <tbody>
<template v-for="(group, index) in groupByPeriod(getDaySchedule(day))" :key="index"> <template
<tr> v-for="(group, index) in groupByPeriod(getDaySchedule(day))"
<td class="text-center font-weight-bold"> :key="index"
{{ group.period }} >
<v-tooltip v-if="group.originalPeriod !== group.period"> <tr>
<template v-slot:activator="{ props }"> <td class="text-center font-weight-bold">
<v-icon size="x-small" v-bind="props" color="info" class="ml-1">mdi-sync</v-icon> {{ group.period }}
<v-tooltip v-if="group.originalPeriod !== group.period">
<template #activator="{ props }">
<v-icon
size="x-small"
v-bind="props"
color="info"
class="ml-1"
>
mdi-sync
</v-icon>
</template>
原节次: {{ group.originalPeriod }}
</v-tooltip>
</td>
<td>
<div
v-for="(item, i) in group.items"
:key="i"
class="mb-1"
>
<v-chip
size="small"
:color="getSubjectColor(item.subject)"
label
text-color="white"
class="mr-1"
>
{{ item.subject }}
</v-chip>
<v-chip
v-if="group.items.length > 1"
size="x-small"
class="ml-1"
:color="item.weekType === '单' ? 'warning' : 'success'"
>
{{ item.weekType }}
</v-chip>
</div>
</td>
<td>
<div
v-for="(timeSlot, i) in group.uniqueTimeSlots"
:key="i"
class="mb-1"
>
<v-chip
size="x-small"
class="time-chip"
>
{{ formatTime(timeSlot.startTime) }} - {{ formatTime(timeSlot.endTime) }}
</v-chip>
</div>
</td>
<td>
<template v-if="!settings.hideTeacherName">
<div
v-for="(item, i) in group.items"
:key="i"
class="mb-1"
>
{{ item.teacher || '-' }}
</div>
</template> </template>
原节次: {{ group.originalPeriod }} <template v-else>
</v-tooltip> -
</td> </template>
<td> </td>
<div v-for="(item, i) in group.items" :key="i" class="mb-1"> <td>
<v-chip size="small" :color="getSubjectColor(item.subject)" label text-color="white" <template v-if="!settings.hideRoom">
class="mr-1"> <div
{{ item.subject }} v-for="(item, i) in group.items"
</v-chip> :key="i"
<v-chip v-if="group.items.length > 1" size="x-small" class="ml-1" class="mb-1"
:color="item.weekType === '单' ? 'warning' : 'success'"> >
{{ item.weekType }} {{ item.room || '-' }}
</v-chip> </div>
</div> </template>
</td> <template v-else>
<td> -
<div v-for="(timeSlot, i) in group.uniqueTimeSlots" :key="i" class="mb-1"> </template>
<v-chip size="x-small" class="time-chip"> </td>
{{ formatTime(timeSlot.startTime) }} - {{ formatTime(timeSlot.endTime) }} <td>
</v-chip> <div
</div> v-for="(item, i) in group.items"
</td> :key="i"
<td> class="mb-1"
<template v-if="!settings.hideTeacherName"> >
<div v-for="(item, i) in group.items" :key="i" class="mb-1"> {{ item.weeks }}
{{ item.teacher || '-' }}
</div> </div>
</template> </td>
<template v-else>-</template> </tr>
</td> </template>
<td>
<template v-if="!settings.hideRoom">
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
{{ item.room || '-' }}
</div>
</template>
<template v-else>-</template>
</td>
<td>
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
{{ item.weeks }}
</div>
</td>
</tr>
</template>
</tbody> </tbody>
</v-table> </v-table>
</v-window-item> </v-window-item>
</v-window> </v-window>
<!-- 无数据提示 --> <!-- 无数据提示 -->
<v-alert v-if="hasExportData && daysWithSchedule.length === 0" type="info" class="mt-3"> <v-alert
v-if="hasExportData && daysWithSchedule.length === 0"
type="info"
class="mt-3"
>
没有找到任何课程数据 没有找到任何课程数据
</v-alert> </v-alert>
</v-card-text> </v-card-text>
@ -430,24 +663,24 @@
</v-card-text> </v-card-text>
<v-card-actions class=""> <v-card-actions class="">
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="primary" color="primary"
variant="outlined" variant="outlined"
:loading="loading" :loading="loading"
:disabled="(!jsonText && !file) || loading" :disabled="(!jsonText && !file) || loading"
@click="processInput"
prepend-icon="mdi-cog-refresh" prepend-icon="mdi-cog-refresh"
@click="processInput"
> >
处理数据 处理数据
</v-btn> </v-btn>
<v-btn <v-btn
color="info" color="info"
:disabled="!hasExportData" :disabled="!hasExportData"
@click="showExportPreview"
class="ml-2" class="ml-2"
prepend-icon="mdi-eye" prepend-icon="mdi-eye"
border border
@click="showExportPreview"
> >
刷新 刷新
</v-btn> </v-btn>
@ -455,10 +688,10 @@
color="success" color="success"
variant="outlined" variant="outlined"
:disabled="!hasExportData" :disabled="!hasExportData"
@click="downloadCSV"
class="ml-2" class="ml-2"
prepend-icon="mdi-download" prepend-icon="mdi-download"
border border
@click="downloadCSV"
> >
下载CSV 下载CSV
</v-btn> </v-btn>
@ -735,6 +968,22 @@ export default {
return days; return days;
} }
}, },
async mounted() {
// YAML
try {
await loadJsYaml();
this.yamlLibLoaded = true;
} catch (error) {
this.error = error.message;
}
// daysWithSchedule
this.$watch('daysWithSchedule', (newVal) => {
if (newVal.length > 0 && !this.activeDay) {
this.activeDay = newVal[0];
}
});
},
methods: { methods: {
async handleFileChange() { async handleFileChange() {
this.resetData(); this.resetData();
@ -1241,22 +1490,6 @@ export default {
clearSelectedDays() { clearSelectedDays() {
this.selectedDays = []; this.selectedDays = [];
} }
},
async mounted() {
// YAML
try {
await loadJsYaml();
this.yamlLibLoaded = true;
} catch (error) {
this.error = error.message;
}
// daysWithSchedule
this.$watch('daysWithSchedule', (newVal) => {
if (newVal.length > 0 && !this.activeDay) {
this.activeDay = newVal[0];
}
});
} }
}; };
</script> </script>

View File

@ -26,7 +26,7 @@
label="server.authDomain" label="server.authDomain"
/> />
</v-form> </v-form>
<v-divider class="my-4"/> <v-divider class="my-4" />
<v-btn <v-btn
class="me-2" class="me-2"

View File

@ -42,7 +42,7 @@
<v-list-item-subtitle>{{ currentDataKey }}</v-list-item-subtitle> <v-list-item-subtitle>{{ currentDataKey }}</v-list-item-subtitle>
</v-list-item> </v-list-item>
</v-list> </v-list>
<v-divider class="my-4"/> <v-divider class="my-4" />
<v-row> <v-row>
<v-col <v-col
cols="12" cols="12"
@ -83,11 +83,17 @@
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
<v-divider class="my-4"/> <v-divider class="my-4" />
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-card border color="primary" variant="tonal"> <v-card
<v-card-title class="text-subtitle-1">聊天室消息</v-card-title> border
color="primary"
variant="tonal"
>
<v-card-title class="text-subtitle-1">
聊天室消息
</v-card-title>
<v-card-text> <v-card-text>
<v-textarea <v-textarea
v-model="chatInput" v-model="chatInput"
@ -97,7 +103,7 @@
rows="2" rows="2"
/> />
<div class="d-flex"> <div class="d-flex">
<v-spacer/> <v-spacer />
<v-btn <v-btn
:disabled="!canSendChat" :disabled="!canSendChat"
color="primary" color="primary"
@ -178,7 +184,7 @@
<v-card border> <v-card border>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
事件日志 事件日志
<v-spacer/> <v-spacer />
<v-btn <v-btn
color="error" color="error"
size="small" size="small"

View File

@ -6,13 +6,19 @@
请将这个ID复制并私聊给开发者以便进行问题排查 请将这个ID复制并私聊给开发者以便进行问题排查
</v-card-subtitle> </v-card-subtitle>
<v-card-text> <v-card-text>
<div class="text-h6 mb-2">访客 ID</div> <div class="text-h6 mb-2">
访客 ID
</div>
<v-code class="d-block pa-2 bg-grey-lighten-4 rounded mb-4"> <v-code class="d-block pa-2 bg-grey-lighten-4 rounded mb-4">
{{ visitorId || '加载中...' }} {{ visitorId || '加载中...' }}
</v-code> </v-code>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn color="primary" @click="loadData" :loading="loading"> <v-btn
color="primary"
:loading="loading"
@click="loadData"
>
Refresh Refresh
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>

View File

@ -11,7 +11,7 @@
<v-app-bar-title class="text-h6"> <v-app-bar-title class="text-h6">
编辑考试配置 编辑考试配置
</v-app-bar-title> </v-app-bar-title>
<v-spacer/> <v-spacer />
<v-btn <v-btn
:loading="saving" :loading="saving"
color="success" color="success"

View File

@ -11,16 +11,26 @@
{{ error }} {{ error }}
</v-alert> </v-alert>
<v-skeleton-loader v-if="loading" type="article"/> <v-skeleton-loader
v-if="loading"
type="article"
/>
<div v-else-if="!config"> <div v-else-if="!config">
<v-alert border="start" type="warning" variant="tonal"> <v-alert
border="start"
type="warning"
variant="tonal"
>
缺少配置请通过 URL 参数 id url 传入配置 缺少配置请通过 URL 参数 id url 传入配置
</v-alert> </v-alert>
</div> </div>
<div v-else> <div v-else>
<div ref="playerRef" class="player"> <div
ref="playerRef"
class="player"
>
<ExamPlayer <ExamPlayer
v-model:room-number="roomNumberLocal" v-model:room-number="roomNumberLocal"
:config="playerConfigObj" :config="playerConfigObj"

View File

@ -2,9 +2,17 @@
<v-container class="fill-height"> <v-container class="fill-height">
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-card border class="elevation-12"> <v-card
border
class="elevation-12"
>
<v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4"> <v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4">
<v-icon class="mr-2" color="white">mdi-calendar-check</v-icon> <v-icon
class="mr-2"
color="white"
>
mdi-calendar-check
</v-icon>
考试看板 考试看板
</v-card-title> </v-card-title>
<v-card-subtitle> <v-card-subtitle>
@ -22,7 +30,9 @@
@click:close="error = ''" @click:close="error = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-alert-circle</v-icon> <v-icon class="mr-2">
mdi-alert-circle
</v-icon>
{{ error }} {{ error }}
</div> </div>
</v-alert> </v-alert>
@ -38,7 +48,9 @@
@click:close="success = ''" @click:close="success = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-2">mdi-check-circle</v-icon> <v-icon class="mr-2">
mdi-check-circle
</v-icon>
{{ success }} {{ success }}
</div> </div>
</v-alert> </v-alert>
@ -74,19 +86,29 @@
</div> </div>
<!-- 加载状态 --> <!-- 加载状态 -->
<v-card v-if="loading" class="my-4" outlined> <v-card
v-if="loading"
class="my-4"
outlined
>
<v-card-text> <v-card-text>
<v-skeleton-loader <v-skeleton-loader
class="mx-auto" class="mx-auto"
type="list-item-avatar-two-line@3" type="list-item-avatar-two-line@3"
></v-skeleton-loader> />
</v-card-text> </v-card-text>
</v-card> </v-card>
<!-- 配置列表 --> <!-- 配置列表 -->
<v-card v-if="!loading && configs.length > 0" class="my-4" elevation="1"> <v-card
v-if="!loading && configs.length > 0"
class="my-4"
elevation="1"
>
<v-card-title class="d-flex align-center pa-4 bg-primary-lighten-5"> <v-card-title class="d-flex align-center pa-4 bg-primary-lighten-5">
<v-icon class="mr-2">mdi-format-list-bulleted</v-icon> <v-icon class="mr-2">
mdi-format-list-bulleted
</v-icon>
<span class="font-weight-bold">配置列表</span> <span class="font-weight-bold">配置列表</span>
</v-card-title> </v-card-title>
<v-list> <v-list>
@ -98,8 +120,13 @@
@click="showEditDialog(config)" @click="showEditDialog(config)"
> >
<template #prepend> <template #prepend>
<v-avatar class="mr-2" color="primary"> <v-avatar
<v-icon color="white">mdi-calendar-text</v-icon> class="mr-2"
color="primary"
>
<v-icon color="white">
mdi-calendar-text
</v-icon>
</v-avatar> </v-avatar>
</template> </template>
@ -108,11 +135,21 @@
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle class="text-caption mt-1"> <v-list-item-subtitle class="text-caption mt-1">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon class="mr-1" size="small">mdi-information-outline</v-icon> <v-icon
class="mr-1"
size="small"
>
mdi-information-outline
</v-icon>
{{ config.message || '无描述' }} {{ config.message || '无描述' }}
</div> </div>
<div class="d-flex align-center mt-1"> <div class="d-flex align-center mt-1">
<v-icon class="mr-1" size="small">mdi-book-multiple</v-icon> <v-icon
class="mr-1"
size="small"
>
mdi-book-multiple
</v-icon>
{{ config.examInfos ? config.examInfos.length : 0 }} 堂考试 {{ config.examInfos ? config.examInfos.length : 0 }} 堂考试
</div> </div>
</v-list-item-subtitle> </v-list-item-subtitle>
@ -140,8 +177,6 @@
> >
<v-icon>mdi-eye</v-icon> <v-icon>mdi-eye</v-icon>
</v-btn> </v-btn>
</div> </div>
</template> </template>
</v-list-item> </v-list-item>
@ -149,12 +184,22 @@
</v-card> </v-card>
<!-- 空状态 --> <!-- 空状态 -->
<v-card v-if="!loading && configs.length === 0" class="my-4" elevation="1"> <v-card
v-if="!loading && configs.length === 0"
class="my-4"
elevation="1"
>
<v-card-text class="text-center py-8"> <v-card-text class="text-center py-8">
<v-icon class="mb-4" color="grey-lighten-1" size="64"> <v-icon
class="mb-4"
color="grey-lighten-1"
size="64"
>
mdi-calendar-blank mdi-calendar-blank
</v-icon> </v-icon>
<h3 class="text-h6 mb-2 text-grey-darken-1">暂无配置</h3> <h3 class="text-h6 mb-2 text-grey-darken-1">
暂无配置
</h3>
<p class="text-body-2 text-grey-darken-1 mb-4"> <p class="text-body-2 text-grey-darken-1 mb-4">
点击"新建配置"按钮创建您的第一个考试配置 点击"新建配置"按钮创建您的第一个考试配置
</p> </p>
@ -173,10 +218,18 @@
</v-row> </v-row>
<!-- 重命名对话框 --> <!-- 重命名对话框 -->
<v-dialog v-model="renameDialog" max-width="500"> <v-dialog
v-model="renameDialog"
max-width="500"
>
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" color="primary">mdi-rename-box</v-icon> <v-icon
class="mr-2"
color="primary"
>
mdi-rename-box
</v-icon>
重命名配置 重命名配置
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
@ -187,10 +240,10 @@
prepend-inner-icon="mdi-calendar-text" prepend-inner-icon="mdi-calendar-text"
variant="outlined" variant="outlined"
@keyup.enter="renameConfig" @keyup.enter="renameConfig"
></v-text-field> />
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="grey" color="grey"
variant="text" variant="text"
@ -212,12 +265,21 @@
</v-dialog> </v-dialog>
<!-- 编辑配置弹框 --> <!-- 编辑配置弹框 -->
<v-dialog v-model="editDialog" max-width="1200" persistent> <v-dialog
v-model="editDialog"
max-width="1200"
persistent
>
<v-card> <v-card>
<v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4"> <v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4">
<v-icon class="mr-2" color="white">mdi-pencil</v-icon> <v-icon
class="mr-2"
color="white"
>
mdi-pencil
</v-icon>
编辑考试配置 编辑考试配置
<v-spacer></v-spacer> <v-spacer />
<v-chip <v-chip
v-if="editingConfig" v-if="editingConfig"
class="mr-2" class="mr-2"
@ -237,8 +299,10 @@
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-card-text class="pa-4" <v-card-text
style="max-height: 70vh; overflow-y: auto;"> class="pa-4"
style="max-height: 70vh; overflow-y: auto;"
>
<ExamConfigEditor <ExamConfigEditor
v-if="editingConfig" v-if="editingConfig"
ref="configEditor" ref="configEditor"
@ -259,7 +323,7 @@
> >
关闭 关闭
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
:loading="saving" :loading="saving"
color="success" color="success"

View File

@ -34,11 +34,16 @@
<v-btn <v-btn
v-if="shouldShowUrgentTestButton" v-if="shouldShowUrgentTestButton"
prepend-icon="mdi-chat" prepend-icon="mdi-chat"
@click="urgentTestDialog = true"
variant="tonal" variant="tonal"
>发送通知</v-btn @click="urgentTestDialog = true"
> >
<v-btn icon="mdi-chat" variant="text" @click="isChatOpen = true" /> 发送通知
</v-btn>
<v-btn
icon="mdi-chat"
variant="text"
@click="isChatOpen = true"
/>
<v-btn <v-btn
:badge="unreadCount || undefined" :badge="unreadCount || undefined"
:badge-color="unreadCount ? 'error' : undefined" :badge-color="unreadCount ? 'error' : undefined"
@ -46,7 +51,11 @@
variant="text" variant="text"
@click="$refs.messageLog.drawer = true" @click="$refs.messageLog.drawer = true"
/> />
<v-btn icon="mdi-cog" variant="text" @click="$router.push('/settings')" /> <v-btn
icon="mdi-cog"
variant="text"
@click="$router.push('/settings')"
/>
</template> </template>
</v-app-bar> </v-app-bar>
<!-- 初始化选择卡片仅在首页且需要授权时显示不影响顶栏 --> <!-- 初始化选择卡片仅在首页且需要授权时显示不影响顶栏 -->
@ -63,11 +72,20 @@
@token-info-updated="updateTokenDisplayInfo" @token-info-updated="updateTokenDisplayInfo"
/> />
<div v-if="!shouldShowInit" class="d-flex"> <div
v-if="!shouldShowInit"
class="d-flex"
>
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<v-container class="main-window flex-grow-1 no-select bloom-container" fluid> <v-container
class="main-window flex-grow-1 no-select bloom-container"
fluid
>
<!-- 常驻通知区域 --> <!-- 常驻通知区域 -->
<v-row v-if="persistentNotifications.length > 0" class="mb-4"> <v-row
v-if="persistentNotifications.length > 0"
class="mb-4"
>
<v-col cols="12"> <v-col cols="12">
<v-card <v-card
v-for="notification in persistentNotifications" v-for="notification in persistentNotifications"
@ -78,31 +96,49 @@
@click="showNotificationDetail(notification)" @click="showNotificationDetail(notification)"
> >
<v-card-text class="d-flex align-center py-3"> <v-card-text class="d-flex align-center py-3">
<span class="text-h6 text-truncate font-weight-bold">{{ notification.message }}</span> <span class="text-h6 text-truncate font-weight-bold">{{ notification.message }}</span>
<v-spacer></v-spacer> <v-spacer />
<v-btn icon="mdi-chevron-right" variant="text"></v-btn> <v-btn
icon="mdi-chevron-right"
variant="text"
/>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
</v-row> </v-row>
<!-- 通知详情对话框 --> <!-- 通知详情对话框 -->
<v-dialog v-model="notificationDetailDialog" max-width="700" scrollable> <v-dialog
<v-card v-if="currentNotification" class="rounded-xl"> v-model="notificationDetailDialog"
max-width="700"
scrollable
>
<v-card
v-if="currentNotification"
class="rounded-xl"
>
<v-card-title class="d-flex align-center pa-4 text-h5"> <v-card-title class="d-flex align-center pa-4 text-h5">
<span
<span :class="currentNotification.isUrgent ? 'text-error' : ''" class="font-weight-bold"> :class="currentNotification.isUrgent ? 'text-error' : ''"
class="font-weight-bold"
>
{{ currentNotification.isUrgent ? '强调通知' : '通知详情' }} {{ currentNotification.isUrgent ? '强调通知' : '通知详情' }}
</span> </span>
<v-spacer></v-spacer> <v-spacer />
<v-btn icon="mdi-close" variant="text" @click="notificationDetailDialog = false"></v-btn> <v-btn
icon="mdi-close"
variant="text"
@click="notificationDetailDialog = false"
/>
</v-card-title> </v-card-title>
<v-divider></v-divider> <v-divider />
<v-card-text class="pa-6"> <v-card-text class="pa-6">
<div class="text-h4 font-weight-medium mb-4" style="line-height: 1.5;"> <div
class="text-h4 font-weight-medium mb-4"
style="line-height: 1.5;"
>
{{ currentNotification.message }} {{ currentNotification.message }}
</div> </div>
<div class="text-subtitle-1 text-grey"> <div class="text-subtitle-1 text-grey">
@ -110,7 +146,7 @@
</div> </div>
</v-card-text> </v-card-text>
<v-divider></v-divider> <v-divider />
<v-card-actions class="pa-4"> <v-card-actions class="pa-4">
<v-btn <v-btn
@ -123,7 +159,7 @@
> >
删除通知 删除通知
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer />
<v-btn <v-btn
color="primary" color="primary"
size="x-large" size="x-large"
@ -188,7 +224,10 @@
@save="handleHomeworkSave" @save="handleHomeworkSave"
/> />
<v-snackbar v-model="state.snackbar" :timeout="2000"> <v-snackbar
v-model="state.snackbar"
:timeout="2000"
>
{{ state.snackbarText }} {{ state.snackbarText }}
</v-snackbar> </v-snackbar>
@ -224,24 +263,41 @@
<FloatingICP /> <FloatingICP />
<!-- 设备聊天室右下角浮窗 --> <!-- 设备聊天室右下角浮窗 -->
<ChatWidget v-model="isChatOpen" :show-button="false" /> <ChatWidget
v-model="isChatOpen"
:show-button="false"
/>
<!-- 紧急通知测试对话框 --> <!-- 紧急通知测试对话框 -->
<UrgentTestDialog v-model="urgentTestDialog" /> <UrgentTestDialog v-model="urgentTestDialog" />
<!-- 添加确认对话框 --> <!-- 添加确认对话框 -->
<v-dialog v-model="confirmDialog.show" max-width="400"> <v-dialog
v-model="confirmDialog.show"
max-width="400"
>
<v-card> <v-card>
<v-card-title class="text-h6"> 确认保存</v-card-title> <v-card-title class="text-h6">
确认保存
</v-card-title>
<v-card-text> <v-card-text>
您正在修改 {{ state.dateString }} 的数据确定要保存吗 您正在修改 {{ state.dateString }} 的数据确定要保存吗
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer />
<v-btn color="grey" variant="text" @click="confirmDialog.reject"> <v-btn
color="grey"
variant="text"
@click="confirmDialog.reject"
>
取消 取消
</v-btn> </v-btn>
<v-btn color="primary" @click="confirmDialog.resolve"> 确认保存</v-btn> <v-btn
color="primary"
@click="confirmDialog.resolve"
>
确认保存
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
@ -254,9 +310,14 @@
/> />
<!-- 添加URL配置确认对话框 --> <!-- 添加URL配置确认对话框 -->
<v-dialog v-model="urlConfigDialog.show" max-width="500"> <v-dialog
v-model="urlConfigDialog.show"
max-width="500"
>
<v-card> <v-card>
<v-card-title class="text-h6"> 确认应用URL配置</v-card-title> <v-card-title class="text-h6">
确认应用URL配置
</v-card-title>
<v-card-text> <v-card-text>
<p>以下配置将应用于当前班级</p> <p>以下配置将应用于当前班级</p>
<v-list density="compact"> <v-list density="compact">
@ -265,17 +326,28 @@
:key="change.key" :key="change.key"
> >
<template #prepend> <template #prepend>
<v-icon :icon="change.icon" class="mr-2" size="small" /> <v-icon
:icon="change.icon"
class="mr-2"
size="small"
/>
</template> </template>
<v-list-item-title class="d-flex align-center"> <v-list-item-title class="d-flex align-center">
<span class="text-subtitle-1">{{ change.name }}</span> <span class="text-subtitle-1">{{ change.name }}</span>
<v-tooltip activator="parent" location="top" <v-tooltip
>{{ change.description || change.key }} activator="parent"
location="top"
>
{{ change.description || change.key }}
</v-tooltip> </v-tooltip>
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle> <v-list-item-subtitle>
<span class="text-grey-darken-1">{{ change.oldValue }}</span> <span class="text-grey-darken-1">{{ change.oldValue }}</span>
<v-icon class="mx-1" icon="mdi-arrow-right" size="small" /> <v-icon
class="mx-1"
icon="mdi-arrow-right"
size="small"
/>
<span class="text-primary font-weight-medium">{{ <span class="text-primary font-weight-medium">{{
change.newValue change.newValue
}}</span> }}</span>
@ -292,29 +364,49 @@
> >
取消 取消
</v-btn> </v-btn>
<v-btn color="primary" @click="urlConfigDialog.confirmHandler"> <v-btn
color="primary"
@click="urlConfigDialog.confirmHandler"
>
确认应用 确认应用
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
<!-- 通知详情对话框 --> <!-- 通知详情对话框 -->
<v-dialog v-model="notificationDetailDialog" max-width="600"> <v-dialog
v-model="notificationDetailDialog"
max-width="600"
>
<v-card v-if="currentNotification"> <v-card v-if="currentNotification">
<v-card-title class="headline" :class="currentNotification.isUrgent ? 'text-error' : 'text-primary'"> <v-card-title
class="headline"
:class="currentNotification.isUrgent ? 'text-error' : 'text-primary'"
>
{{ currentNotification.isUrgent ? '强调通知' : '通知详情' }} {{ currentNotification.isUrgent ? '强调通知' : '通知详情' }}
</v-card-title> </v-card-title>
<v-card-text class="text-h5 py-4"> <v-card-text class="text-h5 py-4">
{{ currentNotification.message }} {{ currentNotification.message }}
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn color="error" variant="text" @click="removePersistentNotification(currentNotification.id)">删除</v-btn> <v-btn
<v-spacer></v-spacer> color="error"
<v-btn color="primary" @click="notificationDetailDialog = false">关闭</v-btn> variant="text"
@click="removePersistentNotification(currentNotification.id)"
>
删除
</v-btn>
<v-spacer />
<v-btn
color="primary"
@click="notificationDetailDialog = false"
>
关闭
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
<br /><br /><br /> <br><br><br>
</template> </template>
<script> <script>
@ -2066,7 +2158,9 @@ export default {
}, },
async removePersistentNotification(id) { async removePersistentNotification(id) {
this.persistentNotifications = this.persistentNotifications.filter(n => n.id !== id); this.persistentNotifications = this.persistentNotifications.filter(n => n.id !== id);
await dataProvider.saveData('notification-list', this.persistentNotifications); // {} []
const dataToSave = this.persistentNotifications.length > 0 ? this.persistentNotifications : {};
await dataProvider.saveData('notification-list', dataToSave);
this.notificationDetailDialog = false; this.notificationDetailDialog = false;
}, },
}, },

View File

@ -7,11 +7,20 @@
@click="$router.push('/')" @click="$router.push('/')"
/> />
</template> </template>
<v-app-bar-title v-if="list && !isRenaming" class="text-h6">{{ list.name }}</v-app-bar-title> <v-app-bar-title
<v-app-bar-title v-else class="text-h6">列表</v-app-bar-title> v-if="list && !isRenaming"
class="text-h6"
>
{{ list.name }}
</v-app-bar-title>
<v-app-bar-title
v-else
class="text-h6"
>
列表
</v-app-bar-title>
</v-app-bar> </v-app-bar>
<v-container> <v-container>
<div class="d-flex align-center mb-4"> <div class="d-flex align-center mb-4">
<v-btn <v-btn
border border
@ -23,11 +32,19 @@
</v-btn> </v-btn>
<h1 v-if="list && !isRenaming"> <h1 v-if="list && !isRenaming">
{{ list.name }} {{ list.name }}
<v-btn border icon size="small" @click="startRenaming"> <v-btn
border
icon
size="small"
@click="startRenaming"
>
<v-icon>mdi-pencil</v-icon> <v-icon>mdi-pencil</v-icon>
</v-btn> </v-btn>
</h1> </h1>
<div v-else-if="list && isRenaming" class="d-flex align-center"> <div
v-else-if="list && isRenaming"
class="d-flex align-center"
>
<v-text-field <v-text-field
v-model="newListName" v-model="newListName"
autofocus autofocus
@ -37,11 +54,20 @@
label="列表名称" label="列表名称"
style="min-width: 200px;" style="min-width: 200px;"
@keyup.enter="saveListName" @keyup.enter="saveListName"
></v-text-field> />
<v-btn class="mr-2" color="primary" size="small" @click="saveListName"> <v-btn
class="mr-2"
color="primary"
size="small"
@click="saveListName"
>
<v-icon>mdi-check</v-icon> <v-icon>mdi-check</v-icon>
</v-btn> </v-btn>
<v-btn color="error" size="small" @click="cancelRenaming"> <v-btn
color="error"
size="small"
@click="cancelRenaming"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
</div> </div>
@ -51,10 +77,14 @@
</div> </div>
<v-card border class="mb-5" rounded="xl"> <v-card
border
class="mb-5"
rounded="xl"
>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
项目列表 项目列表
<v-spacer/> <v-spacer />
<v-btn-toggle <v-btn-toggle
v-model="sortType" v-model="sortType"
mandatory mandatory
@ -97,7 +127,7 @@
</v-list-item> </v-list-item>
</v-list> </v-list>
<v-card-actions v-if="sortedItems.length > 0"> <v-card-actions v-if="sortedItems.length > 0">
<v-spacer/> <v-spacer />
<v-btn <v-btn
:disabled="!hasCompletedItems" :disabled="!hasCompletedItems"
color="error" color="error"
@ -108,7 +138,11 @@
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
<v-card border class="mb-5" rounded="xl"> <v-card
border
class="mb-5"
rounded="xl"
>
<v-card-title>添加新项目</v-card-title> <v-card-title>添加新项目</v-card-title>
<v-card-text> <v-card-text>
<v-text-field <v-text-field
@ -126,7 +160,11 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-card border class="mb-5" rounded="xl"> <v-card
border
class="mb-5"
rounded="xl"
>
<v-card-title>列表排序</v-card-title> <v-card-title>列表排序</v-card-title>
<v-card-text> <v-card-text>
<v-text-field <v-text-field
@ -153,16 +191,30 @@
</v-card> </v-card>
<!-- 确认删除对话框 --> <!-- 确认删除对话框 -->
<v-dialog v-model="deleteDialog.show" max-width="500"> <v-dialog
<v-card border rounded="xl"> v-model="deleteDialog.show"
max-width="500"
>
<v-card
border
rounded="xl"
>
<v-card-title>{{ deleteDialog.title }}</v-card-title> <v-card-title>{{ deleteDialog.title }}</v-card-title>
<v-card-text>{{ deleteDialog.text }}</v-card-text> <v-card-text>{{ deleteDialog.text }}</v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn color="primary" variant="text" @click="deleteDialog.show = false"> <v-btn
color="primary"
variant="text"
@click="deleteDialog.show = false"
>
取消 取消
</v-btn> </v-btn>
<v-btn color="error" variant="text" @click="confirmDelete"> <v-btn
color="error"
variant="text"
@click="confirmDelete"
>
确认删除 确认删除
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -170,8 +222,14 @@
</v-dialog> </v-dialog>
<!-- 项目详情对话框 --> <!-- 项目详情对话框 -->
<v-dialog v-model="itemDialog.show" max-width="600"> <v-dialog
<v-card border rounded="xl"> v-model="itemDialog.show"
max-width="600"
>
<v-card
border
rounded="xl"
>
<v-card-title> <v-card-title>
<span v-if="!itemDialog.isEditing">项目详情</span> <span v-if="!itemDialog.isEditing">项目详情</span>
<span v-else>编辑项目</span> <span v-else>编辑项目</span>
@ -181,13 +239,16 @@
<div v-if="!itemDialog.isEditing && itemDialog.item"> <div v-if="!itemDialog.isEditing && itemDialog.item">
<v-list> <v-list>
<v-list-item> <v-list-item>
<v-list-item-title class="text-subtitle-1 font-weight-bold">{{ itemDialog.item.name }} <v-list-item-title class="text-subtitle-1 font-weight-bold">
{{ itemDialog.item.name }}
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle>{{ itemDialog.item.id }}</v-list-item-subtitle> <v-list-item-subtitle>{{ itemDialog.item.id }}</v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<v-list-item-title class="text-subtitle-1 font-weight-bold">状态</v-list-item-title> <v-list-item-title class="text-subtitle-1 font-weight-bold">
状态
</v-list-item-title>
<v-list-item-subtitle> <v-list-item-subtitle>
<v-chip <v-chip
:color="itemDialog.item.completed ? 'success' : 'warning'" :color="itemDialog.item.completed ? 'success' : 'warning'"
@ -199,20 +260,24 @@
</v-list-item> </v-list-item>
<v-list-item v-if="itemDialog.item.description"> <v-list-item v-if="itemDialog.item.description">
<v-list-item-title class="text-subtitle-1 font-weight-bold">描述</v-list-item-title> <v-list-item-title class="text-subtitle-1 font-weight-bold">
描述
</v-list-item-title>
<v-list-item-subtitle>{{ itemDialog.item.description }}</v-list-item-subtitle> <v-list-item-subtitle>{{ itemDialog.item.description }}</v-list-item-subtitle>
</v-list-item> </v-list-item>
</v-list> </v-list>
</div> </div>
<div v-else-if="itemDialog.isEditing && itemDialog.item" class="pa-2"> <div
v-else-if="itemDialog.isEditing && itemDialog.item"
class="pa-2"
>
<v-text-field <v-text-field
v-model="itemDialog.editedItem.name" v-model="itemDialog.editedItem.name"
class="mb-3" class="mb-3"
label="名称" label="名称"
variant="outlined" variant="outlined"
></v-text-field> />
<v-textarea <v-textarea
v-model="itemDialog.editedItem.description" v-model="itemDialog.editedItem.description"
@ -220,7 +285,7 @@
label="描述" label="描述"
rows="3" rows="3"
variant="outlined" variant="outlined"
></v-textarea> />
<v-switch <v-switch
@ -228,37 +293,56 @@
color="success" color="success"
hide-details hide-details
label="已完成" label="已完成"
></v-switch> />
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<template v-if="!itemDialog.isEditing"> <template v-if="!itemDialog.isEditing">
<v-btn color="primary" variant="text" @click="startEditingItem"> <v-btn
color="primary"
variant="text"
@click="startEditingItem"
>
编辑 编辑
</v-btn> </v-btn>
<v-btn color="error" variant="text" @click="confirmDeleteItem(itemDialog.item?.id)"> <v-btn
color="error"
variant="text"
@click="confirmDeleteItem(itemDialog.item?.id)"
>
删除 删除
</v-btn> </v-btn>
<v-btn color="secondary" variant="text" @click="itemDialog.show = false"> <v-btn
color="secondary"
variant="text"
@click="itemDialog.show = false"
>
关闭 关闭
</v-btn> </v-btn>
</template> </template>
<template v-else> <template v-else>
<v-btn color="success" variant="text" @click="saveItemChanges"> <v-btn
color="success"
variant="text"
@click="saveItemChanges"
>
保存 保存
</v-btn> </v-btn>
<v-btn color="secondary" variant="text" @click="cancelEditingItem"> <v-btn
color="secondary"
variant="text"
@click="cancelEditingItem"
>
取消 取消
</v-btn> </v-btn>
</template> </template>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
</v-container> </v-container>
</template> </template>

View File

@ -7,12 +7,16 @@
@click="$router.push('/')" @click="$router.push('/')"
/> />
</template> </template>
<v-app-bar-title class="text-h6">列表</v-app-bar-title> <v-app-bar-title class="text-h6">
列表
</v-app-bar-title>
</v-app-bar> </v-app-bar>
<v-container> <v-container>
<v-card
border
<v-card border class="mb-5" rounded="xl"> class="mb-5"
rounded="xl"
>
<v-card-title>现有列表</v-card-title> <v-card-title>现有列表</v-card-title>
<v-card-text v-if="lists.length === 0"> <v-card-text v-if="lists.length === 0">
暂无列表请创建新列表 暂无列表请创建新列表
@ -27,7 +31,10 @@
<div v-if="list.id !== editingListId"> <div v-if="list.id !== editingListId">
<v-list-item-title>{{ list.name }}</v-list-item-title> <v-list-item-title>{{ list.name }}</v-list-item-title>
</div> </div>
<div v-else class="d-flex align-center w-100"> <div
v-else
class="d-flex align-center w-100"
>
<v-text-field <v-text-field
v-model="editListName" v-model="editListName"
autofocus autofocus
@ -36,21 +43,41 @@
hide-details hide-details
label="列表名称" label="列表名称"
@keyup.enter="saveListName" @keyup.enter="saveListName"
></v-text-field> />
<v-btn border class="mr-2" color="primary" icon @click.stop.prevent="saveListName"> <v-btn
border
class="mr-2"
color="primary"
icon
@click.stop.prevent="saveListName"
>
<v-icon>mdi-check</v-icon> <v-icon>mdi-check</v-icon>
</v-btn> </v-btn>
<v-btn border color="error" icon @click.stop.prevent="cancelEditing"> <v-btn
border
color="error"
icon
@click.stop.prevent="cancelEditing"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
</div> </div>
<template #append> <template #append>
<div v-if="list.id !== editingListId"> <div v-if="list.id !== editingListId">
<v-btn border class="mr-2" icon @click.stop.prevent="startEditing(list.id)"> <v-btn
border
class="mr-2"
icon
@click.stop.prevent="startEditing(list.id)"
>
<v-icon>mdi-pencil</v-icon> <v-icon>mdi-pencil</v-icon>
</v-btn> </v-btn>
<v-btn border icon @click.stop.prevent="confirmDeleteList(list.id)"> <v-btn
border
icon
@click.stop.prevent="confirmDeleteList(list.id)"
>
<v-icon>mdi-delete</v-icon> <v-icon>mdi-delete</v-icon>
</v-btn> </v-btn>
</div> </div>
@ -58,30 +85,49 @@
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-card> </v-card>
<v-card border class="mb-5" rounded="xl"> <v-card
border
class="mb-5"
rounded="xl"
>
<v-card-title>创建新列表</v-card-title> <v-card-title>创建新列表</v-card-title>
<v-card-text> <v-card-text>
<v-text-field <v-text-field
v-model="newListName" v-model="newListName"
:rules="[v => !!v || '名称不能为空']" :rules="[v => !!v || '名称不能为空']"
label="列表名称" label="列表名称"
></v-text-field> />
<v-btn :disabled="!newListName" color="primary" @click="createNewList"> <v-btn
:disabled="!newListName"
color="primary"
@click="createNewList"
>
创建列表 创建列表
</v-btn> </v-btn>
</v-card-text> </v-card-text>
</v-card> </v-card>
<!-- 确认删除对话框 --> <!-- 确认删除对话框 -->
<v-dialog v-model="deleteDialog.show" max-width="500"> <v-dialog
v-model="deleteDialog.show"
max-width="500"
>
<v-card border> <v-card border>
<v-card-title>删除列表</v-card-title> <v-card-title>删除列表</v-card-title>
<v-card-text>{{ deleteDialog.text }}</v-card-text> <v-card-text>{{ deleteDialog.text }}</v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer />
<v-btn color="primary" variant="text" @click="deleteDialog.show = false"> <v-btn
color="primary"
variant="text"
@click="deleteDialog.show = false"
>
取消 取消
</v-btn> </v-btn>
<v-btn color="error" variant="text" @click="confirmDelete"> <v-btn
color="error"
variant="text"
@click="confirmDelete"
>
确认删除 确认删除
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>

View File

@ -11,10 +11,11 @@
icon="mdi-menu" icon="mdi-menu"
variant="text" variant="text"
@click="drawer = !drawer" @click="drawer = !drawer"
/> />
</template> </template>
<v-app-bar-title class="text-h6">设置</v-app-bar-title> <v-app-bar-title class="text-h6">
设置
</v-app-bar-title>
</v-app-bar> </v-app-bar>
<v-container fluid> <v-container fluid>
@ -43,14 +44,23 @@
direction="vertical" direction="vertical"
style="width: 100%" style="width: 100%"
> >
<v-tabs-window-item value="index" <v-tabs-window-item value="index">
> <v-card
<v-card border class="service-card gradient-right clickable mb-4" color="primary" elevation="8" hover border
rounded="xl" variant="tonal" @click="openClassworksKV"> class="service-card gradient-right clickable mb-4"
color="primary"
elevation="8"
hover
rounded="xl"
variant="tonal"
@click="openClassworksKV"
>
<v-card-item> <v-card-item>
<div class="card-title"> <div class="card-title">
<div> <div>
<div class="text-h6">在寻找 Classworks KV </div> <div class="text-h6">
在寻找 Classworks KV
</div>
<div class="text-caption text-medium-emphasis"> <div class="text-caption text-medium-emphasis">
文档形键值数据库 文档形键值数据库
</div> </div>
@ -71,50 +81,53 @@
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-card border class="rounded-xl mb-4" subtitle="设置" title="Classworks"> <v-card
border
class="rounded-xl mb-4"
subtitle="设置"
title="Classworks"
>
<v-card-text> <v-card-text>
<v-alert <v-alert
class="rounded-xl" class="rounded-xl"
color="error" color="error"
icon="mdi-alert-circle" icon="mdi-alert-circle"
variant="tonal" variant="tonal"
>Classworks >
Classworks
是开源免费的软件官方没有提供任何形式的付费支持服务源代码仓库地址在 是开源免费的软件官方没有提供任何形式的付费支持服务源代码仓库地址在
<a <a
href="https://github.com/ZeroCatDev/Classworks" href="https://github.com/ZeroCatDev/Classworks"
target="_blank" target="_blank"
>https://github.com/ZeroCatDev/Classworks</a >https://github.com/ZeroCatDev/Classworks</a>退
>如果您通过有偿协助等付费方式取得本应用在遇到问题时请在与卖家约定的服务框架下优先向卖家求助如果卖家没有提供您预期的服务请退款或通过其它形式积极维护您的合法权益 </v-alert>
</v-alert
>
<v-alert <v-alert
class="mt-4 rounded-xl" class="mt-4 rounded-xl"
color="info" color="info"
icon="mdi-information" icon="mdi-information"
variant="tonal" variant="tonal"
>请不要使用浏览器清除缓存功能否则会导致配置丢失
<del
>恶意的操作可能导致您受到贵校教师的处理
</del
>
</v-alert
> >
请不要使用浏览器清除缓存功能否则会导致配置丢失
<del>恶意的操作可能导致您受到贵校教师的处理
</del>
</v-alert>
<v-alert <v-alert
class="mt-4 rounded-xl" class="mt-4 rounded-xl"
color="warning" color="warning"
icon="mdi-information" icon="mdi-information"
variant="tonal" variant="tonal"
><p> >
请不要使用包括但不限于360极速浏览器360安全浏览器夸克浏览器QQ浏览器等浏览器使用 <p>
Classworks 请不要使用包括但不限于360极速浏览器360安全浏览器夸克浏览器QQ浏览器等浏览器使用
这些浏览器过时且存在严重的一致性问题在Windows上使用新版 Classworks
Microsoft Edge 浏览器是最推荐的选择 这些浏览器过时且存在严重的一致性问题在Windows上使用新版
</p> Microsoft Edge 浏览器是最推荐的选择
</p>
<p style="color: #666"> <p style="color: #666">
上述浏览器商标为其所属公司所有Classworks 上述浏览器商标为其所属公司所有Classworks
与上述浏览器所属公司无竞争关系 与上述浏览器所属公司无竞争关系
</p> </p>
<br/> <br>
<v-btn <v-btn
append-icon="mdi-open-in-new" append-icon="mdi-open-in-new"
class="text-none rounded-xl" class="text-none rounded-xl"
@ -122,14 +135,13 @@
href="https://www.microsoft.com/zh-cn/windows/microsoft-edge" href="https://www.microsoft.com/zh-cn/windows/microsoft-edge"
target="_blank" target="_blank"
variant="tonal" variant="tonal"
>下载 Microsoft Edge微软边缘浏览器
</v-btn
> >
</v-alert 下载 Microsoft Edge微软边缘浏览器
> </v-btn>
</v-alert>
</v-card-text> </v-card-text>
</v-card> </v-card>
<about-card/> <about-card />
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="server"> <v-tabs-window-item value="server">
@ -138,15 +150,27 @@
border border
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
<data-provider-settings-card border class="mt-4"/> <data-provider-settings-card
<kv-database-card border class="mt-4"/> border
class="mt-4"
/>
<kv-database-card
border
class="mt-4"
/>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="student"> <v-tabs-window-item value="student">
<student-list-card :is-mobile="isMobile" border/> <student-list-card
:is-mobile="isMobile"
border
/>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="share"> <v-tabs-window-item value="share">
<settings-link-generator border class="mt-4"/> <settings-link-generator
border
class="mt-4"
/>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="refresh"> <v-tabs-window-item value="refresh">
@ -182,27 +206,35 @@
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="randomPicker"> <v-tabs-window-item value="randomPicker">
<random-picker-card :is-mobile="isMobile" border/> <random-picker-card
:is-mobile="isMobile"
border
/>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="subject"> <v-tabs-window-item value="subject">
<subject-management-card border/> <subject-management-card border />
<br/> <br>
<homework-template-card border/> <homework-template-card border />
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="developer" <v-tabs-window-item value="developer">
> <settings-card
<settings-card border icon="mdi-developer-board" title="开发者选项"> border
icon="mdi-developer-board"
title="开发者选项"
>
<v-list> <v-list>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon class="mr-3" icon="mdi-code-tags"/> <v-icon
class="mr-3"
icon="mdi-code-tags"
/>
</template> </template>
<v-list-item-title>启用开发者选项</v-list-item-title> <v-list-item-title>启用开发者选项</v-list-item-title>
<v-list-item-subtitle <v-list-item-subtitle>
>启用后可以查看和修改开发者设置 启用后可以查看和修改开发者设置
</v-list-item-subtitle </v-list-item-subtitle>
>
<template #append> <template #append>
<v-switch <v-switch
v-model="settings.developer.enabled" v-model="settings.developer.enabled"
@ -220,29 +252,41 @@
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
<template v-if="settings.developer.enabled"> <template v-if="settings.developer.enabled">
<v-card border class="mt-4 rounded-lg"> <v-card
border
class="mt-4 rounded-lg"
>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2" icon="mdi-cog-outline"/> <v-icon
class="mr-2"
icon="mdi-cog-outline"
/>
所有设置 所有设置
</v-card-title> </v-card-title>
<v-card-subtitle> 浏览和修改所有可用设置</v-card-subtitle> <v-card-subtitle> 浏览和修改所有可用设置</v-card-subtitle>
<v-card-text> <v-card-text>
<settings-explorer @update="onSettingUpdate"/> <settings-explorer @update="onSettingUpdate" />
</v-card-text> </v-card-text>
</v-card> </v-card>
</template> </template>
<v-col v-if="settings.developer.enabled" cols="12"></v-col> <v-col
v-if="settings.developer.enabled"
cols="12"
/>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="about"> <v-tabs-window-item value="about">
<about-card/> <about-card />
<echo-chamber-card border class="mt-4"/> <echo-chamber-card
border
class="mt-4"
/>
</v-tabs-window-item> </v-tabs-window-item>
</v-tabs-window> </v-tabs-window>
</v-container> </v-container>
<!-- 消息记录组件 --> <!-- 消息记录组件 -->
<message-log ref="messageLog"/> <message-log ref="messageLog" />
</div> </div>
</template> </template>

246
typed-router.d.ts vendored Normal file
View File

@ -0,0 +1,246 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection ES6UnusedImports
// Generated by unplugin-vue-router. !! DO NOT MODIFY THIS FILE !!
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
declare module 'vue-router/auto-resolver' {
export type ParamParserCustom = never
}
declare module 'vue-router/auto-routes' {
import type {
RouteRecordInfo,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'vue-router'
/**
* Route name map generated by unplugin-vue-router
*/
export interface RouteNamedMap {
'/': RouteRecordInfo<
'/',
'/',
Record<never, never>,
Record<never, never>,
| never
>,
'/404': RouteRecordInfo<
'/404',
'/404',
Record<never, never>,
Record<never, never>,
| never
>,
'/authorize': RouteRecordInfo<
'/authorize',
'/authorize',
Record<never, never>,
Record<never, never>,
| never
>,
'/CacheManagement': RouteRecordInfo<
'/CacheManagement',
'/CacheManagement',
Record<never, never>,
Record<never, never>,
| never
>,
'/cses2wakeup': RouteRecordInfo<
'/cses2wakeup',
'/cses2wakeup',
Record<never, never>,
Record<never, never>,
| never
>,
'/DataMigration': RouteRecordInfo<
'/DataMigration',
'/DataMigration',
Record<never, never>,
Record<never, never>,
| never
>,
'/debug': RouteRecordInfo<
'/debug',
'/debug',
Record<never, never>,
Record<never, never>,
| never
>,
'/debug-init': RouteRecordInfo<
'/debug-init',
'/debug-init',
Record<never, never>,
Record<never, never>,
| never
>,
'/debug-socket': RouteRecordInfo<
'/debug-socket',
'/debug-socket',
Record<never, never>,
Record<never, never>,
| never
>,
'/exam-editor/[id]': RouteRecordInfo<
'/exam-editor/[id]',
'/exam-editor/:id',
{ id: ParamValue<true> },
{ id: ParamValue<false> },
| never
>,
'/exam-player': RouteRecordInfo<
'/exam-player',
'/exam-player',
Record<never, never>,
Record<never, never>,
| never
>,
'/examschedule': RouteRecordInfo<
'/examschedule',
'/examschedule',
Record<never, never>,
Record<never, never>,
| never
>,
'/list/': RouteRecordInfo<
'/list/',
'/list',
Record<never, never>,
Record<never, never>,
| never
>,
'/list/[id]': RouteRecordInfo<
'/list/[id]',
'/list/:id',
{ id: ParamValue<true> },
{ id: ParamValue<false> },
| never
>,
'/settings': RouteRecordInfo<
'/settings',
'/settings',
Record<never, never>,
Record<never, never>,
| never
>,
}
/**
* Route file to route info map by unplugin-vue-router.
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
* - views: names of nested views (can be passed to <RouterView name="...">)
*
* @internal
*/
export interface _RouteFileInfoMap {
'src/pages/index.vue': {
routes:
| '/'
views:
| never
}
'src/pages/404.vue': {
routes:
| '/404'
views:
| never
}
'src/pages/authorize.vue': {
routes:
| '/authorize'
views:
| never
}
'src/pages/CacheManagement.vue': {
routes:
| '/CacheManagement'
views:
| never
}
'src/pages/cses2wakeup.vue': {
routes:
| '/cses2wakeup'
views:
| never
}
'src/pages/DataMigration.vue': {
routes:
| '/DataMigration'
views:
| never
}
'src/pages/debug.vue': {
routes:
| '/debug'
views:
| never
}
'src/pages/debug-init.vue': {
routes:
| '/debug-init'
views:
| never
}
'src/pages/debug-socket.vue': {
routes:
| '/debug-socket'
views:
| never
}
'src/pages/exam-editor/[id].vue': {
routes:
| '/exam-editor/[id]'
views:
| never
}
'src/pages/exam-player.vue': {
routes:
| '/exam-player'
views:
| never
}
'src/pages/examschedule.vue': {
routes:
| '/examschedule'
views:
| never
}
'src/pages/list/index.vue': {
routes:
| '/list/'
views:
| never
}
'src/pages/list/[id].vue': {
routes:
| '/list/[id]'
views:
| never
}
'src/pages/settings.vue': {
routes:
| '/settings'
views:
| never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* @internal
*/
export type _RouteNamesForFilePath<FilePath extends string> =
_RouteFileInfoMap extends Record<FilePath, infer Info>
? Info['routes']
: keyof RouteNamedMap
}