1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-12-07 21:13:11 +00:00

规范代码格式

This commit is contained in:
Sunwuyuan 2025-11-16 15:14:17 +08:00
parent 0af2c4cc63
commit 76c2dba502
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
87 changed files with 3403 additions and 3247 deletions

View File

@ -3,8 +3,8 @@
<!-- 正常路由 --> <!-- 正常路由 -->
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition <transition
name="md3"
mode="out-in" mode="out-in"
name="md3"
> >
<component <component
:is="Component" :is="Component"
@ -23,6 +23,7 @@ import { useTheme } from "vuetify";
import {getSetting} from "@/utils/settings"; import {getSetting} from "@/utils/settings";
import RateLimitModal from "@/components/RateLimitModal.vue"; import RateLimitModal from "@/components/RateLimitModal.vue";
import Clarity from "@microsoft/clarity"; import Clarity from "@microsoft/clarity";
const theme = useTheme(); const theme = useTheme();
onMounted(() => { onMounted(() => {

View File

@ -1,4 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" viewBox="0 0 256 256" fill="none"> <svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="256" height="256"
viewBox="0 0 256 256" fill="none">
<g clip-path="url(#clip-path-74_1)"> <g clip-path="url(#clip-path-74_1)">
<path fill="#FFFFFF" d="M0 256L256 256L256 0L0 0L0 256Z"> <path fill="#FFFFFF" d="M0 256L256 256L256 0L0 0L0 256Z">
</path> </path>
@ -11,7 +12,8 @@
<path d="M28 128L128 28L228 28L128 128L28 128Z" fill-rule="evenodd" fill="#52452A"> <path d="M28 128L128 28L228 28L128 128L28 128Z" fill-rule="evenodd" fill="#52452A">
</path> </path>
<g> <g>
<path fill="#000000" d="M-3049.01 2467.94L-3043.48 2467.94L-3043.48 2466.99L-3045.92 2466.99C-3046.36 2466.99 -3046.9 2467.04 -3047.36 2467.08C-3045.29 2465.12 -3043.9 2463.33 -3043.9 2461.57C-3043.9 2460.01 -3044.9 2458.99 -3046.47 2458.99C-3047.58 2458.99 -3048.35 2459.49 -3049.06 2460.27L-3048.43 2460.9C-3047.93 2460.31 -3047.32 2459.88 -3046.6 2459.88C-3045.51 2459.88 -3044.98 2460.61 -3044.98 2461.62C-3044.98 2463.13 -3046.25 2464.88 -3049.01 2467.29L-3049.01 2467.94ZM-3039.27 2468.1C-3037.9 2468.1 -3036.74 2466.95 -3036.74 2465.24C-3036.74 2463.39 -3037.7 2462.48 -3039.19 2462.48C-3039.87 2462.48 -3040.64 2462.88 -3041.18 2463.54C-3041.13 2460.81 -3040.13 2459.89 -3038.91 2459.89C-3038.38 2459.89 -3037.85 2460.15 -3037.52 2460.56L-3036.89 2459.89C-3037.39 2459.36 -3038.04 2458.99 -3038.96 2458.99C-3040.66 2458.99 -3042.21 2460.3 -3042.21 2463.74C-3042.21 2466.65 -3040.95 2468.1 -3039.27 2468.1ZM-3041.15 2464.41C-3040.58 2463.6 -3039.91 2463.3 -3039.36 2463.3C-3038.3 2463.3 -3037.78 2464.05 -3037.78 2465.24C-3037.78 2466.44 -3038.43 2467.23 -3039.27 2467.23C-3040.37 2467.23 -3041.03 2466.24 -3041.15 2464.41ZM-3035.17 2467.94L-3030.34 2467.94L-3030.34 2467.03L-3032.1 2467.03L-3032.1 2459.15L-3032.95 2459.15C-3033.43 2459.42 -3033.99 2459.62 -3034.77 2459.77L-3034.77 2460.47L-3033.2 2460.47L-3033.2 2467.03L-3035.17 2467.03L-3035.17 2467.94ZM-3029.51 2467.94L-3028.4 2467.94L-3027.54 2465.25L-3024.33 2465.25L-3023.49 2467.94L-3022.31 2467.94L-3025.3 2459.15L-3026.54 2459.15L-3029.51 2467.94ZM-3027.27 2464.38L-3026.84 2463.02C-3026.52 2462.02 -3026.24 2461.08 -3025.96 2460.04L-3025.91 2460.04C-3025.62 2461.06 -3025.35 2462.02 -3025.02 2463.02L-3024.6 2464.38L-3027.27 2464.38ZM-3018.93 2468.1C-3017.26 2468.1 -3016.19 2466.58 -3016.19 2463.51C-3016.19 2460.47 -3017.26 2458.99 -3018.93 2458.99C-3020.61 2458.99 -3021.67 2460.47 -3021.67 2463.51C-3021.67 2466.58 -3020.61 2468.1 -3018.93 2468.1ZM-3018.93 2467.21C-3019.93 2467.21 -3020.61 2466.09 -3020.61 2463.51C-3020.61 2460.95 -3019.93 2459.85 -3018.93 2459.85C-3017.93 2459.85 -3017.25 2460.95 -3017.25 2463.51C-3017.25 2466.09 -3017.93 2467.21 -3018.93 2467.21ZM-3012.27 2468.1C-3010.6 2468.1 -3009.53 2466.58 -3009.53 2463.51C-3009.53 2460.47 -3010.6 2458.99 -3012.27 2458.99C-3013.95 2458.99 -3015.01 2460.47 -3015.01 2463.51C-3015.01 2466.58 -3013.95 2468.1 -3012.27 2468.1ZM-3012.27 2467.21C-3013.27 2467.21 -3013.95 2466.09 -3013.95 2463.51C-3013.95 2460.95 -3013.27 2459.85 -3012.27 2459.85C-3011.27 2459.85 -3010.59 2460.95 -3010.59 2463.51C-3010.59 2466.09 -3011.27 2467.21 -3012.27 2467.21Z"> <path fill="#000000"
d="M-3049.01 2467.94L-3043.48 2467.94L-3043.48 2466.99L-3045.92 2466.99C-3046.36 2466.99 -3046.9 2467.04 -3047.36 2467.08C-3045.29 2465.12 -3043.9 2463.33 -3043.9 2461.57C-3043.9 2460.01 -3044.9 2458.99 -3046.47 2458.99C-3047.58 2458.99 -3048.35 2459.49 -3049.06 2460.27L-3048.43 2460.9C-3047.93 2460.31 -3047.32 2459.88 -3046.6 2459.88C-3045.51 2459.88 -3044.98 2460.61 -3044.98 2461.62C-3044.98 2463.13 -3046.25 2464.88 -3049.01 2467.29L-3049.01 2467.94ZM-3039.27 2468.1C-3037.9 2468.1 -3036.74 2466.95 -3036.74 2465.24C-3036.74 2463.39 -3037.7 2462.48 -3039.19 2462.48C-3039.87 2462.48 -3040.64 2462.88 -3041.18 2463.54C-3041.13 2460.81 -3040.13 2459.89 -3038.91 2459.89C-3038.38 2459.89 -3037.85 2460.15 -3037.52 2460.56L-3036.89 2459.89C-3037.39 2459.36 -3038.04 2458.99 -3038.96 2458.99C-3040.66 2458.99 -3042.21 2460.3 -3042.21 2463.74C-3042.21 2466.65 -3040.95 2468.1 -3039.27 2468.1ZM-3041.15 2464.41C-3040.58 2463.6 -3039.91 2463.3 -3039.36 2463.3C-3038.3 2463.3 -3037.78 2464.05 -3037.78 2465.24C-3037.78 2466.44 -3038.43 2467.23 -3039.27 2467.23C-3040.37 2467.23 -3041.03 2466.24 -3041.15 2464.41ZM-3035.17 2467.94L-3030.34 2467.94L-3030.34 2467.03L-3032.1 2467.03L-3032.1 2459.15L-3032.95 2459.15C-3033.43 2459.42 -3033.99 2459.62 -3034.77 2459.77L-3034.77 2460.47L-3033.2 2460.47L-3033.2 2467.03L-3035.17 2467.03L-3035.17 2467.94ZM-3029.51 2467.94L-3028.4 2467.94L-3027.54 2465.25L-3024.33 2465.25L-3023.49 2467.94L-3022.31 2467.94L-3025.3 2459.15L-3026.54 2459.15L-3029.51 2467.94ZM-3027.27 2464.38L-3026.84 2463.02C-3026.52 2462.02 -3026.24 2461.08 -3025.96 2460.04L-3025.91 2460.04C-3025.62 2461.06 -3025.35 2462.02 -3025.02 2463.02L-3024.6 2464.38L-3027.27 2464.38ZM-3018.93 2468.1C-3017.26 2468.1 -3016.19 2466.58 -3016.19 2463.51C-3016.19 2460.47 -3017.26 2458.99 -3018.93 2458.99C-3020.61 2458.99 -3021.67 2460.47 -3021.67 2463.51C-3021.67 2466.58 -3020.61 2468.1 -3018.93 2468.1ZM-3018.93 2467.21C-3019.93 2467.21 -3020.61 2466.09 -3020.61 2463.51C-3020.61 2460.95 -3019.93 2459.85 -3018.93 2459.85C-3017.93 2459.85 -3017.25 2460.95 -3017.25 2463.51C-3017.25 2466.09 -3017.93 2467.21 -3018.93 2467.21ZM-3012.27 2468.1C-3010.6 2468.1 -3009.53 2466.58 -3009.53 2463.51C-3009.53 2460.47 -3010.6 2458.99 -3012.27 2458.99C-3013.95 2458.99 -3015.01 2460.47 -3015.01 2463.51C-3015.01 2466.58 -3013.95 2468.1 -3012.27 2468.1ZM-3012.27 2467.21C-3013.27 2467.21 -3013.95 2466.09 -3013.95 2463.51C-3013.95 2460.95 -3013.27 2459.85 -3012.27 2459.85C-3011.27 2459.85 -3010.59 2460.95 -3010.59 2463.51C-3010.59 2466.09 -3011.27 2467.21 -3012.27 2467.21Z">
</path> </path>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -3,16 +3,16 @@
<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-spacer>
<v-btn color="error" @click="clearAllCaches" :loading="loading"> <v-btn :loading="loading" color="error" @click="clearAllCaches">
清除所有缓存 清除所有缓存
</v-btn> </v-btn>
<v-btn icon class="ml-2" @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" type="warning" class="mb-4"> <v-alert v-if="!serviceWorkerActive" class="mb-4" type="warning">
Service Worker 未激活缓存管理功能不可用 Service Worker 未激活缓存管理功能不可用
</v-alert> </v-alert>
@ -30,7 +30,7 @@
</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 color="error" size="small" @click="clearCache(cache.name)" :loading="loading"> <v-btn :loading="loading" color="error" size="small" @click="clearCache(cache.name)">
清除此缓存 清除此缓存
</v-btn> </v-btn>
</div> </div>
@ -43,7 +43,7 @@
{{ url }} {{ url }}
</v-list-item-subtitle> </v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn icon size="small" color="error" @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>

View File

@ -2,12 +2,12 @@
<!-- Floating toggle button --> <!-- Floating toggle button -->
<div <div
v-if="showToggleButton" v-if="showToggleButton"
class="chat-toggle"
:style="toggleStyle" :style="toggleStyle"
class="chat-toggle"
> >
<v-btn <v-btn
icon
color="primary" color="primary"
icon
variant="flat" variant="flat"
@click="open()" @click="open()"
> >
@ -27,13 +27,13 @@
<!-- Chat panel --> <!-- Chat panel -->
<div <div
v-show="visible" v-show="visible"
class="chat-panel"
:style="panelStyle" :style="panelStyle"
class="chat-panel"
> >
<v-card <v-card
border border
elevation="8"
class="chat-card" class="chat-card"
elevation="8"
> >
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon class="mr-2"> <v-icon class="mr-2">
@ -44,9 +44,9 @@
<v-tooltip location="top"> <v-tooltip location="top">
<template #activator="{ props }"> <template #activator="{ props }">
<v-chip <v-chip
v-bind="props"
size="x-small"
:color="connected ? 'success' : 'grey'" :color="connected ? 'success' : 'grey'"
size="x-small"
v-bind="props"
variant="tonal" variant="tonal"
> >
{{ connected ? '已连接' : '未连接' }} {{ connected ? '已连接' : '未连接' }}
@ -86,13 +86,13 @@
</div> </div>
<div <div
v-else v-else
class="message-row"
:class="{ self: msg.self }" :class="{ self: msg.self }"
class="message-row"
> >
<div class="avatar"> <div class="avatar">
<v-avatar <v-avatar
size="24"
:color="msg.self ? 'primary' : 'grey'" :color="msg.self ? 'primary' : 'grey'"
size="24"
> >
<v-icon size="small"> <v-icon size="small">
{{ msg.self ? 'mdi-account' : 'mdi-account-outline' }} {{ msg.self ? 'mdi-account' : 'mdi-account-outline' }}
@ -116,9 +116,9 @@
<v-card-actions class="chat-input"> <v-card-actions class="chat-input">
<v-btn <v-btn
class="mr-1"
icon icon
variant="text" variant="text"
class="mr-1"
@click="insertEmoji('😄')" @click="insertEmoji('😄')"
> >
<v-icon>mdi-emoticon-outline</v-icon> <v-icon>mdi-emoticon-outline</v-icon>
@ -126,19 +126,19 @@
<v-textarea <v-textarea
ref="inputRef" ref="inputRef"
v-model="text" v-model="text"
class="flex-grow-1"
rows="1"
auto-grow auto-grow
variant="solo" class="flex-grow-1"
hide-details hide-details
placeholder="输入消息" placeholder="输入消息"
rows="1"
variant="solo"
@keydown.enter.prevent="handleEnter" @keydown.enter.prevent="handleEnter"
@keydown.shift.enter.stop @keydown.shift.enter.stop
/> />
<v-btn <v-btn
color="primary"
:disabled="!canSend" :disabled="!canSend"
class="ml-2" class="ml-2"
color="primary"
@click="send" @click="send"
> >
<v-icon start> <v-icon start>
@ -239,7 +239,9 @@ export default {
try { try {
const stored = localStorage.getItem('chat.lastVisit') const stored = localStorage.getItem('chat.lastVisit')
if (stored) this.lastVisit = stored if (stored) this.lastVisit = stored
} catch (e) { void e } } catch (e) {
void e
}
// Prepare socket // Prepare socket
const s = getSocket() const s = getSocket()
@ -280,7 +282,9 @@ export default {
this.$emit('update:modelValue', false) this.$emit('update:modelValue', false)
try { try {
localStorage.setItem('chat.lastVisit', new Date().toISOString()) localStorage.setItem('chat.lastVisit', new Date().toISOString())
} catch (e) { void e } } catch (e) {
void e
}
this.unreadCount = 0 this.unreadCount = 0
}, },
onOpen() { onOpen() {
@ -340,7 +344,9 @@ export default {
if (!el) return if (!el) return
try { try {
el.scrollTop = el.scrollHeight el.scrollTop = el.scrollHeight
} catch (e) { void e } } catch (e) {
void e
}
}, },
}, },
} }
@ -351,33 +357,45 @@ export default {
position: fixed; position: fixed;
z-index: 1100; z-index: 1100;
} }
.chat-panel { .chat-panel {
position: fixed; position: fixed;
z-index: 1101; z-index: 1101;
} }
.chat-card { .chat-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.chat-body { .chat-body {
padding: 8px 12px; padding: 8px 12px;
height: calc(100% - 120px); height: calc(100% - 120px);
} }
.messages { .messages {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
} }
.message-row { .message-row {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
margin: 8px 0; margin: 8px 0;
} }
.message-row.self { .message-row.self {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
.message-row .avatar { width: 28px; display: flex; justify-content: center; }
.message-row .avatar {
width: 28px;
display: flex;
justify-content: center;
}
.message-row .bubble { .message-row .bubble {
max-width: 70%; max-width: 70%;
background: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.06);
@ -385,12 +403,34 @@ export default {
padding: 6px 10px; padding: 6px 10px;
margin: 0 8px; margin: 0 8px;
} }
.message-row.self .bubble { .message-row.self .bubble {
background: rgba(33, 150, 243, 0.15); background: rgba(33, 150, 243, 0.15);
} }
.bubble .text { white-space: pre-wrap; word-break: break-word; }
.bubble .meta { font-size: 12px; opacity: 0.6; margin-top: 2px; text-align: right; } .bubble .text {
.divider-row { text-align: center; color: rgba(255,255,255,0.6); font-size: 12px; } white-space: pre-wrap;
.divider-text { margin: 4px 0; } word-break: break-word;
.chat-input { padding: 8px; } }
.bubble .meta {
font-size: 12px;
opacity: 0.6;
margin-top: 2px;
text-align: right;
}
.divider-row {
text-align: center;
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
}
.divider-text {
margin: 4px 0;
}
.chat-input {
padding: 8px;
}
</style> </style>

View File

@ -3,11 +3,11 @@
<!-- 错误提示 --> <!-- 错误提示 -->
<v-alert <v-alert
v-if="error" v-if="error"
type="error"
class="mb-4 mt-3 mx-2"
variant="tonal"
border="start" border="start"
class="mb-4 mt-3 mx-2"
closable closable
type="error"
variant="tonal"
@click:close="error = ''" @click:close="error = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
@ -19,11 +19,11 @@
<!-- 成功提示 --> <!-- 成功提示 -->
<v-alert <v-alert
v-if="success" v-if="success"
type="success"
class="mb-4 mt-3 mx-2"
variant="tonal"
border="start" border="start"
class="mb-4 mt-3 mx-2"
closable closable
type="success"
variant="tonal"
@click:close="success = ''" @click:close="success = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
@ -35,22 +35,22 @@
<!-- 验证错误提示 --> <!-- 验证错误提示 -->
<v-alert <v-alert
v-if="hasValidationErrors && !loading" v-if="hasValidationErrors && !loading"
type="warning"
class="mb-4 mt-3 mx-2"
variant="tonal"
border="start" border="start"
class="mb-4 mt-3 mx-2"
type="warning"
variant="tonal"
> >
<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 density="compact" class="bg-transparent"> <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 v-slot:prepend>
<v-icon size="small" color="warning">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>
@ -60,7 +60,7 @@
<!-- 加载状态 --> <!-- 加载状态 -->
<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 type="article" class="mx-auto"></v-skeleton-loader> <v-skeleton-loader class="mx-auto" type="article"></v-skeleton-loader>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -68,12 +68,12 @@
<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
color="success"
variant="elevated"
prepend-icon="mdi-open-in-new"
@click="openConfig"
class="mr-2 text-none"
:disabled="!isValidConfig" :disabled="!isValidConfig"
class="mr-2 text-none"
color="success"
prepend-icon="mdi-open-in-new"
variant="elevated"
@click="openConfig"
> >
打开 ExamSchedule 打开 ExamSchedule
</v-btn> </v-btn>
@ -89,9 +89,10 @@
<v-btn-toggle <v-btn-toggle
v-model="isEditMode" v-model="isEditMode"
color="primary" color="primary"
variant="outlined"
divided divided
> <v-btn variant="outlined"
>
<v-btn
class="text-error" class="text-error"
prepend-icon="mdi-delete" prepend-icon="mdi-delete"
@click="confirmDelete" @click="confirmDelete"
@ -116,7 +117,7 @@
> >
{{ localConfig.message || "未设置考试提示" }} {{ localConfig.message || "未设置考试提示" }}
</div> </div>
<v-chip v-if="localConfig.room" size="large" class="px-4 py-2"> <v-chip v-if="localConfig.room" class="px-4 py-2" size="large">
<v-icon start>mdi-home</v-icon> <v-icon start>mdi-home</v-icon>
考场{{ localConfig.room }} 考场{{ localConfig.room }}
</v-chip> </v-chip>
@ -130,10 +131,10 @@
v-for="(examInfo, index) in localConfig.examInfos" v-for="(examInfo, index) in localConfig.examInfos"
:key="index" :key="index"
cols="12" cols="12"
md="6"
lg="4" lg="4"
md="6"
> >
<v-card variant="tonal" class="h-100" hover> <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>
@ -143,8 +144,9 @@
<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 size="small" class="mr-2" color="success" <v-icon class="mr-2" color="success" size="small"
>mdi-clock-start</v-icon >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>
@ -154,8 +156,9 @@
</div> </div>
<div> <div>
<div class="d-flex align-center mb-1"> <div class="d-flex align-center mb-1">
<v-icon size="small" class="mr-2" color="error" <v-icon class="mr-2" color="error" size="small"
>mdi-clock-end</v-icon >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>
@ -169,7 +172,7 @@
</v-row> </v-row>
</div> </div>
<div v-else class="text-center py-12"> <div v-else class="text-center py-12">
<v-icon size="80" color="grey-lighten-2" class="mb-4"> <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>
@ -183,7 +186,7 @@
</div> </div>
<!-- JSON预览 --> <!-- JSON预览 -->
<v-card class="mb-4" elevation="2" border> <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"
@ -193,25 +196,25 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn
color="white" color="white"
variant="outlined"
prepend-icon="mdi-content-copy" prepend-icon="mdi-content-copy"
size="small" size="small"
variant="outlined"
@click.stop="copyToClipboard" @click.stop="copyToClipboard"
> >
复制 复制
</v-btn> </v-btn>
<v-btn <v-btn
color="white"
variant="text"
size="small"
:icon="showJsonPreview ? 'mdi-chevron-up' : 'mdi-chevron-down'" :icon="showJsonPreview ? 'mdi-chevron-up' : 'mdi-chevron-down'"
class="ml-2" class="ml-2"
color="white"
size="small"
variant="text"
> >
</v-btn> </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-show="showJsonPreview" class="pa-4">
<v-card variant="tonal" 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>
@ -222,7 +225,7 @@
<!-- 编辑模式 --> <!-- 编辑模式 -->
<div v-if="!loading && isEditMode"> <div v-if="!loading && isEditMode">
<!-- 基本信息 --> <!-- 基本信息 -->
<v-card class="mb-4" elevation="1" border> <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>
基本信息 基本信息
@ -232,11 +235,11 @@
<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 || '考试名称不能为空']"
label="考试名称" label="考试名称"
prepend-inner-icon="mdi-calendar-text" prepend-inner-icon="mdi-calendar-text"
variant="outlined"
:rules="[(v) => !!v || '考试名称不能为空']"
required required
variant="outlined"
></v-text-field> ></v-text-field>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
@ -251,10 +254,10 @@
<v-textarea <v-textarea
v-model="localConfig.message" v-model="localConfig.message"
label="考试提示" label="考试提示"
prepend-inner-icon="mdi-message-text"
variant="outlined"
rows="3"
placeholder="输入考试相关的提示信息..." placeholder="输入考试相关的提示信息..."
prepend-inner-icon="mdi-message-text"
rows="3"
variant="outlined"
></v-textarea> ></v-textarea>
<!-- 默认提示选项 --> <!-- 默认提示选项 -->
@ -266,25 +269,25 @@
<v-chip <v-chip
v-for="(tip, index) in defaultExamTips" v-for="(tip, index) in defaultExamTips"
:key="index" :key="index"
color="primary"
variant="outlined"
size="small"
@click="selectDefaultTip(tip)"
class="ma-1" class="ma-1"
color="primary"
size="small"
variant="outlined"
@click="selectDefaultTip(tip)"
> >
<v-icon start size="small">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 size="small" class="mr-1">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 class="mb-4" elevation="1" border> <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>
考试科目安排 考试科目安排
@ -312,31 +315,31 @@
<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 || '科目名称不能为空']"
density="comfortable"
label="科目名称" label="科目名称"
prepend-inner-icon="mdi-book" prepend-inner-icon="mdi-book"
variant="outlined" variant="outlined"
density="comfortable"
:rules="[(v) => !!v || '科目名称不能为空']"
></v-text-field> ></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"
transition="scale-transition"
offset-y
min-width="auto" min-width="auto"
offset-y
transition="scale-transition"
> >
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-text-field <v-text-field
v-model="examInfo.startFormatted" v-model="examInfo.startFormatted"
:rules="[(v) => !!v || '开始时间不能为空']"
density="comfortable"
label="开始时间" label="开始时间"
prepend-inner-icon="mdi-clock-start" prepend-inner-icon="mdi-clock-start"
variant="outlined"
density="comfortable"
readonly readonly
v-bind="props" v-bind="props"
:rules="[(v) => !!v || '开始时间不能为空']" variant="outlined"
></v-text-field> ></v-text-field>
</template> </template>
<v-card min-width="600"> <v-card min-width="600">
@ -345,24 +348,24 @@
</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 cols="6" class="border-e"> <v-col class="border-e" cols="6">
<v-date-picker <v-date-picker
v-model="examInfo.startDate" v-model="examInfo.startDate"
@update:model-value="updateStartDateTime(index)"
color="primary" color="primary"
elevation="0"
locale="zh-cn" locale="zh-cn"
show-adjacent-months show-adjacent-months
elevation="0" @update:model-value="updateStartDateTime(index)"
></v-date-picker> ></v-date-picker>
</v-col> </v-col>
<v-col cols="6"> <v-col cols="6">
<v-time-picker <v-time-picker
v-model="examInfo.startTime" v-model="examInfo.startTime"
@update:model-value="updateStartDateTime(index)"
color="primary" color="primary"
elevation="0"
format="24hr" format="24hr"
scrollable scrollable
elevation="0" @update:model-value="updateStartDateTime(index)"
></v-time-picker> ></v-time-picker>
</v-col> </v-col>
</v-row> </v-row>
@ -391,20 +394,20 @@
<v-menu <v-menu
v-model="examInfo.endDateMenu" v-model="examInfo.endDateMenu"
:close-on-content-click="false" :close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="auto" min-width="auto"
offset-y
transition="scale-transition"
> >
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-text-field <v-text-field
v-model="examInfo.endFormatted" v-model="examInfo.endFormatted"
:rules="[(v) => !!v || '结束时间不能为空']"
density="comfortable"
label="结束时间" label="结束时间"
prepend-inner-icon="mdi-clock-end" prepend-inner-icon="mdi-clock-end"
variant="outlined"
density="comfortable"
readonly readonly
v-bind="props" v-bind="props"
:rules="[(v) => !!v || '结束时间不能为空']" variant="outlined"
></v-text-field> ></v-text-field>
</template> </template>
<v-card min-width="600"> <v-card min-width="600">
@ -413,24 +416,24 @@
</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 cols="6" class="border-e"> <v-col class="border-e" cols="6">
<v-date-picker <v-date-picker
v-model="examInfo.endDate" v-model="examInfo.endDate"
@update:model-value="updateEndDateTime(index)"
color="primary" color="primary"
elevation="0"
locale="zh-cn" locale="zh-cn"
show-adjacent-months show-adjacent-months
elevation="0" @update:model-value="updateEndDateTime(index)"
></v-date-picker> ></v-date-picker>
</v-col> </v-col>
<v-col cols="6"> <v-col cols="6">
<v-time-picker <v-time-picker
v-model="examInfo.endTime" v-model="examInfo.endTime"
@update:model-value="updateEndDateTime(index)"
color="primary" color="primary"
elevation="0"
format="24hr" format="24hr"
scrollable scrollable
elevation="0" @update:model-value="updateEndDateTime(index)"
></v-time-picker> ></v-time-picker>
</v-col> </v-col>
</v-row> </v-row>
@ -455,32 +458,32 @@
</v-card> </v-card>
</v-menu> </v-menu>
</v-col> </v-col>
<v-col cols="12" md="2" class="d-flex align-center"> <v-col class="d-flex align-center" cols="12" md="2">
<v-btn <v-btn
icon="mdi-delete"
color="error" color="error"
variant="text" icon="mdi-delete"
size="small" size="small"
variant="text"
@click="removeExamInfo(index)" @click="removeExamInfo(index)"
> >
<v-icon>mdi-delete</v-icon> <v-icon>mdi-delete</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn
v-if="index > 0" v-if="index > 0"
icon="mdi-arrow-up"
color="primary" color="primary"
variant="text" icon="mdi-arrow-up"
size="small" size="small"
variant="text"
@click="moveExamInfo(index, -1)" @click="moveExamInfo(index, -1)"
> >
<v-icon>mdi-arrow-up</v-icon> <v-icon>mdi-arrow-up</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn
v-if="index < localConfig.examInfos.length - 1" v-if="index < localConfig.examInfos.length - 1"
icon="mdi-arrow-down"
color="primary" color="primary"
variant="text" icon="mdi-arrow-down"
size="small" size="small"
variant="text"
@click="moveExamInfo(index, 1)" @click="moveExamInfo(index, 1)"
> >
<v-icon>mdi-arrow-down</v-icon> <v-icon>mdi-arrow-down</v-icon>
@ -491,7 +494,7 @@
</v-list-item> </v-list-item>
</v-list> </v-list>
<div v-else class="text-center py-8"> <div v-else class="text-center py-8">
<v-icon size="48" color="grey-lighten-1" class="mb-2"> <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">
@ -509,7 +512,7 @@
<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 color="error" class="mr-2">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>
@ -526,10 +529,10 @@
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
:loading="deleting"
color="error" color="error"
variant="outlined" variant="outlined"
@click="deleteConfig" @click="deleteConfig"
:loading="deleting"
> >
删除 删除
</v-btn> </v-btn>
@ -1074,7 +1077,6 @@ export default {
}, },
/** /**
* 确认删除配置 * 确认删除配置
*/ */

View File

@ -1,10 +1,10 @@
<template> <template>
<a <a
aria-label="浙ICP备2024068645号"
class="floating-icp-link" class="floating-icp-link"
href="https://beian.miit.gov.cn/" href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="浙ICP备2024068645号" target="_blank"
> >
浙ICP备2024068645号 浙ICP备2024068645号
</a> </a>

View File

@ -1,47 +1,47 @@
<template> <template>
<v-slide-y-transition> <v-slide-y-transition>
<v-card <v-card
:class="{ 'toolbar-expanded': isExpanded }"
class="floating-toolbar" class="floating-toolbar"
elevation="4" elevation="4"
rounded="xl" rounded="xl"
:class="{ 'toolbar-expanded': isExpanded }"
> >
<v-btn-group variant="text" class="toolbar-buttons"> <v-btn-group class="toolbar-buttons" variant="text">
<v-btn <v-btn
v-ripple
:title="'查看昨天'"
class="toolbar-btn"
icon="mdi-chevron-left" icon="mdi-chevron-left"
variant="text" variant="text"
@click="$emit('prev-day')" @click="$emit('prev-day')"
:title="'查看昨天'"
class="toolbar-btn"
v-ripple
/> />
<v-btn <v-btn
v-ripple
:title="'缩小字体'"
class="toolbar-btn"
icon="mdi-format-font-size-decrease" icon="mdi-format-font-size-decrease"
variant="text" variant="text"
@click="$emit('zoom', 'out')" @click="$emit('zoom', 'out')"
:title="'缩小字体'"
class="toolbar-btn"
v-ripple
/> />
<v-btn <v-btn
v-ripple
:title="'放大字体'"
class="toolbar-btn"
icon="mdi-format-font-size-increase" icon="mdi-format-font-size-increase"
variant="text" variant="text"
@click="$emit('zoom', 'up')" @click="$emit('zoom', 'up')"
:title="'放大字体'"
class="toolbar-btn"
v-ripple
/> />
<v-menu location="top" :close-on-content-click="false"> <v-menu :close-on-content-click="false" location="top">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
icon="mdi-calendar" v-ripple
variant="text"
v-bind="props"
:title="'选择日期'" :title="'选择日期'"
class="toolbar-btn" class="toolbar-btn"
v-ripple icon="mdi-calendar"
v-bind="props"
variant="text"
/> />
</template> </template>
<v-card border class="date-picker-card"> <v-card border class="date-picker-card">
@ -53,23 +53,23 @@
</v-card> </v-card>
</v-menu> </v-menu>
<v-btn <v-btn
icon="mdi-refresh" v-ripple
variant="text"
:loading="loading" :loading="loading"
@click="$emit('refresh')"
:title="'刷新数据'" :title="'刷新数据'"
class="toolbar-btn" class="toolbar-btn"
v-ripple icon="mdi-refresh"
variant="text"
@click="$emit('refresh')"
/> />
<v-btn <v-btn
v-if="!isToday" v-if="!isToday"
v-ripple
:title="'查看明天'"
class="toolbar-btn"
icon="mdi-chevron-right" icon="mdi-chevron-right"
variant="text" variant="text"
@click="$emit('next-day')" @click="$emit('next-day')"
:title="'查看明天'"
class="toolbar-btn"
v-ripple
/> />
</v-btn-group> </v-btn-group>

View File

@ -15,7 +15,7 @@
</div> </div>
</div> </div>
<template #actions> <template #actions>
<v-btn variant="text" icon="mdi-close" @click="snackbar = false" /> <v-btn icon="mdi-close" variant="text" @click="snackbar = false"/>
</template> </template>
</v-snackbar> </v-snackbar>
</template> </template>

View File

@ -38,16 +38,23 @@
<template #subtitle> <template #subtitle>
<div class="text-subtitle-1"> <div class="text-subtitle-1">
Replace this page by removing <v-kbd>{{ `<HelloWorld />` }}</v-kbd> in <v-kbd>pages/index.vue</v-kbd>. Replace this page by removing
<v-kbd>{{ `
<HelloWorld/>
` }}
</v-kbd>
in
<v-kbd>pages/index.vue</v-kbd>
.
</div> </div>
</template> </template>
<v-overlay <v-overlay
opacity=".12"
scrim="primary"
contained contained
model-value model-value
opacity=".12"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -67,11 +74,11 @@
variant="text" variant="text"
> >
<v-overlay <v-overlay
opacity=".06"
scrim="primary"
contained contained
model-value model-value
opacity=".06"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -91,11 +98,11 @@
variant="text" variant="text"
> >
<v-overlay <v-overlay
opacity=".06"
scrim="primary"
contained contained
model-value model-value
opacity=".06"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -115,11 +122,11 @@
variant="text" variant="text"
> >
<v-overlay <v-overlay
opacity=".06"
scrim="primary"
contained contained
model-value model-value
opacity=".06"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -139,11 +146,11 @@
variant="text" variant="text"
> >
<v-overlay <v-overlay
opacity=".06"
scrim="primary"
contained contained
model-value model-value
opacity=".06"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>

View File

@ -1,6 +1,6 @@
# 创建新的作业编辑对话框组件 # 创建新的作业编辑对话框组件
<template> <template>
<v-dialog v-model="dialogVisible" width="auto" max-width="900" @click:outside="handleClose"> <v-dialog v-model="dialogVisible" max-width="900" width="auto" @click:outside="handleClose">
<v-card border> <v-card border>
<v-card-title>{{ title }}</v-card-title> <v-card-title>{{ title }}</v-card-title>
<v-card-subtitle> <v-card-subtitle>
@ -15,9 +15,9 @@
auto-grow auto-grow
placeholder="使用换行表示分条" placeholder="使用换行表示分条"
rows="5" rows="5"
width="480"
@click="updateCurrentLine" @click="updateCurrentLine"
@keyup="updateCurrentLine" @keyup="updateCurrentLine"
width="480"
/> />
<!-- Template Buttons Section --> <!-- Template Buttons Section -->
@ -29,9 +29,9 @@
<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
class="ma-1 book-chip"
:color="isBookSelected(book) ? 'success' : 'default'" :color="isBookSelected(book) ? 'success' : 'default'"
:variant="isBookSelected(book) ? 'elevated' : 'flat'" :variant="isBookSelected(book) ? 'elevated' : 'flat'"
class="ma-1 book-chip"
@click="handleBookClick(book)" @click="handleBookClick(book)"
> >
{{ book }} {{ book }}
@ -42,9 +42,9 @@
<v-chip <v-chip
v-for="page in pages" v-for="page in pages"
:key="page" :key="page"
class="ma-1"
:color="isPageSelected(book, page) ? 'info' : 'default'" :color="isPageSelected(book, page) ? 'info' : 'default'"
:variant="isPageSelected(book, page) ? 'elevated' : 'flat'" :variant="isPageSelected(book, page) ? 'elevated' : 'flat'"
class="ma-1"
@click="handlePageClick(book, page)" @click="handlePageClick(book, page)"
> >
{{ page }} {{ page }}
@ -57,9 +57,9 @@
<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
class="ma-1 book-chip"
:color="isBookSelected(book) ? 'success' : 'default'" :color="isBookSelected(book) ? 'success' : 'default'"
:variant="isBookSelected(book) ? 'elevated' : 'flat'" :variant="isBookSelected(book) ? 'elevated' : 'flat'"
class="ma-1 book-chip"
@click="handleBookClick(book)" @click="handleBookClick(book)"
> >
{{ book }} {{ book }}
@ -70,9 +70,9 @@
<v-chip <v-chip
v-for="page in pages" v-for="page in pages"
:key="page" :key="page"
class="ma-1"
:color="isPageSelected(book, page) ? 'info' : 'default'" :color="isPageSelected(book, page) ? 'info' : 'default'"
:variant="isPageSelected(book, page) ? 'elevated' : 'flat'" :variant="isPageSelected(book, page) ? 'elevated' : 'flat'"
class="ma-1"
@click="handlePageClick(book, page)" @click="handlePageClick(book, page)"
> >
{{ page }} {{ page }}
@ -102,16 +102,16 @@
</div> </div>
<!-- Quick Tools Section --> <!-- Quick Tools Section -->
<div class="quick-tools ml-4" style="min-width: 180px;" v-if="showQuickTools"> <div v-if="showQuickTools" 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">
<v-btn <v-btn
v-for="n in 3" v-for="n in 3"
:key="n" :key="n"
class="keypad-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn"
@click="insertAtCursor(String(n))" @click="insertAtCursor(String(n))"
> >
{{ n }} {{ n }}
@ -121,9 +121,9 @@
<v-btn <v-btn
v-for="n in 3" v-for="n in 3"
:key="n" :key="n"
class="keypad-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn"
@click="insertAtCursor(String(n + 3))" @click="insertAtCursor(String(n + 3))"
> >
{{ n + 3 }} {{ n + 3 }}
@ -133,9 +133,9 @@
<v-btn <v-btn
v-for="n in 3" v-for="n in 3"
:key="n" :key="n"
class="keypad-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn"
@click="insertAtCursor(String(n + 6))" @click="insertAtCursor(String(n + 6))"
> >
{{ n + 6 }} {{ n + 6 }}
@ -143,26 +143,26 @@
</div> </div>
<div class="keypad-row"> <div class="keypad-row">
<v-btn <v-btn
class="keypad-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn"
@click="insertAtCursor('-')" @click="insertAtCursor('-')"
> >
- -
</v-btn> </v-btn>
<v-btn <v-btn
class="keypad-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn"
@click="insertAtCursor('0')" @click="insertAtCursor('0')"
> >
0 0
</v-btn> </v-btn>
<v-btn <v-btn
size="small"
variant="tonal"
class="keypad-btn" class="keypad-btn"
color="error" color="error"
size="small"
variant="tonal"
@click="deleteLastChar" @click="deleteLastChar"
> >
@ -170,16 +170,17 @@
</div> </div>
<div class="keypad-row"> <div class="keypad-row">
<v-btn <v-btn
class="keypad-btn space-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn space-btn"
@click="insertAtCursor(' ')" @click="insertAtCursor(' ')"
> >
空格 空格
</v-btn><v-btn </v-btn>
<v-btn
class="keypad-btn space-btn"
size="small" size="small"
variant="tonal" variant="tonal"
class="keypad-btn space-btn"
@click="insertAtCursor('\n')" @click="insertAtCursor('\n')"
> >
换行 换行
@ -492,7 +493,6 @@ export default {
} }
.book-chip { .book-chip {
align-self: flex-start; align-self: flex-start;
} }

View File

@ -25,8 +25,8 @@
<div class="card-horizontal-layout"> <div class="card-horizontal-layout">
<div class="card-icon-wrapper"> <div class="card-icon-wrapper">
<v-icon <v-icon
size="48"
color="primary" color="primary"
size="48"
> >
mdi-new-box mdi-new-box
</v-icon> </v-icon>
@ -53,8 +53,8 @@
<div class="card-horizontal-layout"> <div class="card-horizontal-layout">
<div class="card-icon-wrapper"> <div class="card-icon-wrapper">
<v-icon <v-icon
size="48"
color="success" color="success"
size="48"
> >
mdi-account-check mdi-account-check
</v-icon> </v-icon>
@ -81,8 +81,8 @@
<div class="card-horizontal-layout"> <div class="card-horizontal-layout">
<div class="card-icon-wrapper"> <div class="card-icon-wrapper">
<v-icon <v-icon
size="48"
color="info" color="info"
size="48"
> >
mdi-database-cog mdi-database-cog
</v-icon> </v-icon>
@ -102,33 +102,33 @@
<div class="options-buttons"> <div class="options-buttons">
<v-btn <v-btn
variant="tonal"
prepend-icon="mdi-laptop" prepend-icon="mdi-laptop"
size="small" size="small"
variant="tonal"
@click="useLocalMode" @click="useLocalMode"
> >
使用本地模式 使用本地模式
</v-btn> </v-btn>
<v-btn <v-btn
variant="tonal"
prepend-icon="mdi-flash" prepend-icon="mdi-flash"
size="small" size="small"
variant="tonal"
@click="handleAutoAuthorize" @click="handleAutoAuthorize"
> >
授权码式授权弃用 授权码式授权弃用
</v-btn> </v-btn>
<v-btn <v-btn
variant="tonal"
prepend-icon="mdi-key" prepend-icon="mdi-key"
size="small" size="small"
variant="tonal"
@click="showTokenDialog = true" @click="showTokenDialog = true"
> >
输入 Token 输入 Token
</v-btn> </v-btn>
<v-btn <v-btn
variant="tonal"
prepend-icon="mdi-code-tags" prepend-icon="mdi-code-tags"
size="small" size="small"
variant="tonal"
@click="showAlternativeCodeDialog = true" @click="showAlternativeCodeDialog = true"
> >
输入替代代码 输入替代代码
@ -158,10 +158,10 @@
> >
<DeviceAuthDialog <DeviceAuthDialog
ref="deviceAuthDialog" ref="deviceAuthDialog"
:show-cancel="true"
:preconfig="deviceAuthPreconfig" :preconfig="deviceAuthPreconfig"
@success="handleAuthSuccess" :show-cancel="true"
@cancel="showDeviceAuthDialog = false" @cancel="showDeviceAuthDialog = false"
@success="handleAuthSuccess"
/> />
</v-dialog> </v-dialog>
@ -171,8 +171,8 @@
> >
<TokenInputDialog <TokenInputDialog
:show-cancel="true" :show-cancel="true"
@success="handleTokenSuccess"
@cancel="showTokenDialog = false" @cancel="showTokenDialog = false"
@success="handleTokenSuccess"
/> />
</v-dialog> </v-dialog>
@ -182,8 +182,8 @@
> >
<AlternativeCodeDialog <AlternativeCodeDialog
:show-cancel="true" :show-cancel="true"
@submit="handleAlternativeCodeSubmit"
@cancel="showAlternativeCodeDialog = false" @cancel="showAlternativeCodeDialog = false"
@submit="handleAlternativeCodeSubmit"
/> />
</v-dialog> </v-dialog>
</div> </div>

View File

@ -8,22 +8,22 @@
<v-card <v-card
class="kvinit-card" class="kvinit-card"
elevation="8" elevation="8"
title="初始化云端存储授权"
subtitle="请完成授权以启用云端存储功能"
prepend-icon="mdi-cloud-lock" prepend-icon="mdi-cloud-lock"
subtitle="请完成授权以启用云端存储功能"
title="初始化云端存储授权"
> >
<v-card-actions class="justify-end"> <v-card-actions class="justify-end">
<v-btn <v-btn
text
class="me-3" class="me-3"
text
@click="useLocalMode" @click="useLocalMode"
> >
使用本地模式 使用本地模式
</v-btn> </v-btn>
<v-btn <v-btn
:loading="loading"
color="primary" color="primary"
variant="flat" variant="flat"
:loading="loading"
@click="goToAuthorize" @click="goToAuthorize"
> >
前往授权 前往授权
@ -36,10 +36,10 @@
class="d-flex align-center" class="d-flex align-center"
> >
<v-progress-circular <v-progress-circular
class="me-2"
indeterminate indeterminate
size="20" size="20"
width="2" width="2"
class="me-2"
/> />
<span class="body-2"> 正在检查授权状态 </span> <span class="body-2"> 正在检查授权状态 </span>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<v-navigation-drawer v-model="drawer" location="right" temporary width="400" v-if="drawer"> <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>
@ -8,13 +8,14 @@
<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 :icon="icons[msg.type]" :color="colors[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>
@ -24,7 +25,7 @@
<v-list-item v-if="!messages.length"> <v-list-item v-if="!messages.length">
<template #prepend> <template #prepend>
<v-icon icon="mdi-inbox" color="grey" /> <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>

View File

@ -7,8 +7,8 @@
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field <v-text-field
v-model="classNumber" v-model="classNumber"
label="班级编号"
hint="请输入需要迁移的班级编号" hint="请输入需要迁移的班级编号"
label="班级编号"
persistent-hint persistent-hint
prepend-icon="mdi-account-group" prepend-icon="mdi-account-group"
></v-text-field> ></v-text-field>
@ -17,8 +17,8 @@
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field <v-text-field
v-model="machineId" v-model="machineId"
label="设备标识 (UUID)"
hint="系统已自动填充设备标识,通常无需修改" hint="系统已自动填充设备标识,通常无需修改"
label="设备标识 (UUID)"
persistent-hint persistent-hint
prepend-icon="mdi-identifier" prepend-icon="mdi-identifier"
readonly readonly
@ -27,24 +27,24 @@
</v-row> </v-row>
<v-radio-group v-model="migrationType" class="mt-2"> <v-radio-group v-model="migrationType" class="mt-2">
<v-radio value="local" label="本地数据迁移"></v-radio> <v-radio label="本地数据迁移" value="local"></v-radio>
<v-radio value="server" label="服务器数据迁移"></v-radio> <v-radio label="服务器数据迁移" value="server"></v-radio>
</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"
label="服务器地址"
hint="输入服务器域名例如https://example.com" hint="输入服务器域名例如https://example.com"
label="服务器地址"
persistent-hint persistent-hint
prepend-icon="mdi-server" prepend-icon="mdi-server"
></v-text-field> ></v-text-field>
<v-alert <v-alert
class="mt-2"
density="compact" density="compact"
type="info" type="info"
variant="outlined" variant="outlined"
class="mt-2"
> >
服务器接口格式<br/> 服务器接口格式<br/>
- 配置接口域名/班号/config<br/> - 配置接口域名/班号/config<br/>
@ -52,7 +52,7 @@
</v-alert> </v-alert>
<div class="d-flex align-center mt-4"> <div class="d-flex align-center mt-4">
<v-icon color="warning" class="mr-2">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>
@ -61,8 +61,8 @@
<v-text-field <v-text-field
v-model="startDate" v-model="startDate"
label="开始日期" label="开始日期"
type="date"
prepend-icon="mdi-calendar-start" prepend-icon="mdi-calendar-start"
type="date"
></v-text-field> ></v-text-field>
</v-col> </v-col>
@ -70,8 +70,8 @@
<v-text-field <v-text-field
v-model="endDate" v-model="endDate"
label="结束日期" label="结束日期"
type="date"
prepend-icon="mdi-calendar-end" prepend-icon="mdi-calendar-end"
type="date"
></v-text-field> ></v-text-field>
</v-col> </v-col>
</v-row> </v-row>
@ -87,13 +87,13 @@
}}</span> }}</span>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn
:loading="loading || scanning"
color="primary" color="primary"
@click=" @click="
migrationType === 'local' migrationType === 'local'
? scanLocalDatabase() ? scanLocalDatabase()
: previewServerData() : previewServerData()
" "
:loading="loading || scanning"
> >
{{ migrationType === "local" ? "扫描数据" : "加载数据" }} {{ migrationType === "local" ? "扫描数据" : "加载数据" }}
</v-btn> </v-btn>
@ -115,8 +115,8 @@
:headers="headers" :headers="headers"
:items="displayItems" :items="displayItems"
:items-per-page="10" :items-per-page="10"
item-value="key"
class="elevation-1" class="elevation-1"
item-value="key"
> >
<template #[`item.type`]="{ item }"> <template #[`item.type`]="{ item }">
<v-chip <v-chip
@ -134,9 +134,9 @@
<v-alert <v-alert
v-if="displayItems.length > 0" v-if="displayItems.length > 0"
type="info"
density="compact"
class="mt-2" class="mt-2"
density="compact"
type="info"
> >
系统将迁移表格中显示的所有数据项迁移前请确认数据完整性 系统将迁移表格中显示的所有数据项迁移前请确认数据完整性
</v-alert> </v-alert>
@ -152,15 +152,15 @@
<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 value="kv-local" label="本地 KV 存储"></v-radio> <v-radio label="本地 KV 存储" value="kv-local"></v-radio>
<v-radio value="kv-server" label="服务器 KV 存储"></v-radio> <v-radio label="服务器 KV 存储" value="kv-server"></v-radio>
</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"
label="目标服务器地址"
hint="输入KV服务器地址例如https://example.com/kv-api" hint="输入KV服务器地址例如https://example.com/kv-api"
label="目标服务器地址"
persistent-hint persistent-hint
prepend-icon="mdi-server-network" prepend-icon="mdi-server-network"
></v-text-field> ></v-text-field>
@ -170,10 +170,10 @@
<div class="d-flex justify-end mb-6"> <div class="d-flex justify-end mb-6">
<v-btn <v-btn
:disabled="!canMigrate"
:loading="migrating"
color="success" color="success"
@click="startMigration" @click="startMigration"
:loading="migrating"
:disabled="!canMigrate"
> >
开始迁移 开始迁移
</v-btn> </v-btn>
@ -188,7 +188,7 @@
<span>{{ migrationSuccess ? "迁移成功" : "迁移失败" }}</span> <span>{{ migrationSuccess ? "迁移成功" : "迁移失败" }}</span>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-alert v-if="migrationError" type="error" class="mb-4"> <v-alert v-if="migrationError" class="mb-4" type="error">
{{ migrationError }} {{ migrationError }}
</v-alert> </v-alert>

View File

@ -4,7 +4,9 @@ Vue template files in this folder are automatically imported.
## 🚀 Usage ## 🚀 Usage
Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it. Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin
automatically imports `.vue` files created in the `src/components` directory, and registers them as global components.
This means that you can use any component in your application without having to manually import it.
The following example assumes a component located at `src/components/MyComponent.vue`: The following example assumes a component located at `src/components/MyComponent.vue`:

View File

@ -1,13 +1,13 @@
<template> <template>
<v-dialog <v-dialog
v-model="dialog" v-model="dialog"
max-width="600"
fullscreen-breakpoint="sm" fullscreen-breakpoint="sm"
max-width="600"
persistent persistent
> >
<v-card class="random-picker-card" rounded="xl" border> <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 icon="mdi-account-question" class="mr-2" /> <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"/>
@ -18,13 +18,13 @@
<div class="d-flex justify-center align-center counter-container"> <div class="d-flex justify-center align-center counter-container">
<v-btn <v-btn
size="x-large"
icon="mdi-minus"
variant="tonal"
color="primary"
:disabled="count <= 1" :disabled="count <= 1"
@click="decrementCount"
class="counter-btn" class="counter-btn"
color="primary"
icon="mdi-minus"
size="x-large"
variant="tonal"
@click="decrementCount"
/> />
<div class="count-display mx-8"> <div class="count-display mx-8">
@ -33,13 +33,13 @@
</div> </div>
<v-btn <v-btn
size="x-large"
icon="mdi-plus"
variant="tonal"
color="primary"
:disabled="count >= maxAllowedCount" :disabled="count >= maxAllowedCount"
@click="incrementCount"
class="counter-btn" class="counter-btn"
color="primary"
icon="mdi-plus"
size="x-large"
variant="tonal"
@click="incrementCount"
/> />
</div> </div>
@ -47,13 +47,13 @@
<div class="mode-switch-container mt-6"> <div class="mode-switch-container mt-6">
<v-btn-toggle <v-btn-toggle
v-model="pickerMode" v-model="pickerMode"
color="primary"
rounded="pill"
mandatory
class="mode-toggle" class="mode-toggle"
color="primary"
mandatory
rounded="pill"
> >
<v-btn value="name" prepend-icon="mdi-account">姓名模式</v-btn> <v-btn prepend-icon="mdi-account" value="name">姓名模式</v-btn>
<v-btn value="number" prepend-icon="mdi-numeric">学号模式</v-btn> <v-btn prepend-icon="mdi-numeric" value="number">学号模式</v-btn>
</v-btn-toggle> </v-btn-toggle>
</div> </div>
@ -63,36 +63,36 @@
<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"
label="最小值"
type="number"
min="1"
max="100"
hide-details
class="number-input" class="number-input"
density="compact" density="compact"
hide-details
label="最小值"
max="100"
min="1"
type="number"
/> />
<span class="mx-2"></span> <span class="mx-2"></span>
<v-text-field <v-text-field
v-model.number="maxNumber" v-model.number="maxNumber"
label="最大值"
type="number"
min="1"
max="100"
hide-details
class="number-input" class="number-input"
density="compact" density="compact"
hide-details
label="最大值"
max="100"
min="1"
type="number"
/> />
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<v-btn <v-btn
size="x-large"
color="primary"
prepend-icon="mdi-dice-multiple"
@click="startPicking"
:disabled="filteredStudents.length === 0" :disabled="filteredStudents.length === 0"
class="start-btn" class="start-btn"
color="primary"
prepend-icon="mdi-dice-multiple"
size="x-large"
@click="startPicking"
> >
开始抽取 开始抽取
</v-btn> </v-btn>
@ -112,10 +112,10 @@
<v-tooltip v-if="pickerMode === 'name'" location="bottom"> <v-tooltip v-if="pickerMode === 'name'" location="bottom">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-icon <v-icon
v-bind="props" class="ml-1"
icon="mdi-information-outline" icon="mdi-information-outline"
size="small" size="small"
class="ml-1" v-bind="props"
/> />
</template> </template>
<div class="pa-2"> <div class="pa-2">
@ -136,18 +136,18 @@
<v-chip <v-chip
:color="tempFilters.excludeLate ? 'warning' : 'default'" :color="tempFilters.excludeLate ? 'warning' : 'default'"
:variant="tempFilters.excludeLate ? 'elevated' : 'text'" :variant="tempFilters.excludeLate ? 'elevated' : 'text'"
@click="tempFilters.excludeLate = !tempFilters.excludeLate"
prepend-icon="mdi-clock-alert"
class="filter-chip" class="filter-chip"
prepend-icon="mdi-clock-alert"
@click="tempFilters.excludeLate = !tempFilters.excludeLate"
> >
{{ tempFilters.excludeLate ? "排除" : "包含" }}迟到学生 {{ tempFilters.excludeLate ? "排除" : "包含" }}迟到学生
</v-chip> </v-chip>
<v-chip <v-chip
:color="tempFilters.excludeAbsent ? 'error' : 'default'" :color="tempFilters.excludeAbsent ? 'error' : 'default'"
:variant="tempFilters.excludeAbsent ? 'elevated' : 'text'" :variant="tempFilters.excludeAbsent ? 'elevated' : 'text'"
@click="tempFilters.excludeAbsent = !tempFilters.excludeAbsent"
prepend-icon="mdi-account-off"
class="filter-chip" class="filter-chip"
prepend-icon="mdi-account-off"
@click="tempFilters.excludeAbsent = !tempFilters.excludeAbsent"
> >
{{ tempFilters.excludeAbsent ? "排除" : "包含" }}请假学生 {{ tempFilters.excludeAbsent ? "排除" : "包含" }}请假学生
</v-chip> </v-chip>
@ -155,9 +155,9 @@
<v-chip <v-chip
:color="tempFilters.excludeExcluded ? 'grey' : 'default'" :color="tempFilters.excludeExcluded ? 'grey' : 'default'"
:variant="tempFilters.excludeExcluded ? 'elevated' : 'text'" :variant="tempFilters.excludeExcluded ? 'elevated' : 'text'"
@click="tempFilters.excludeExcluded = !tempFilters.excludeExcluded"
prepend-icon="mdi-account-cancel"
class="filter-chip" class="filter-chip"
prepend-icon="mdi-account-cancel"
@click="tempFilters.excludeExcluded = !tempFilters.excludeExcluded"
> >
{{ tempFilters.excludeExcluded ? "排除" : "包含" }}不参与学生 {{ tempFilters.excludeExcluded ? "排除" : "包含" }}不参与学生
</v-chip> </v-chip>
@ -169,15 +169,15 @@
<div v-if="isAnimating" class="animation-container"> <div v-if="isAnimating" class="animation-container">
<div class="animation-wrapper"> <div class="animation-wrapper">
<transition-group <transition-group
class="shuffle-container"
name="shuffle" name="shuffle"
tag="div" tag="div"
class="shuffle-container"
> >
<div <div
v-for="(student, index) in animationStudents" v-for="(student, index) in animationStudents"
:key="student.id" :key="student.id"
class="student-item"
:class="{ highlighted: highlightedIndices.includes(index) }" :class="{ highlighted: highlightedIndices.includes(index) }"
class="student-item"
> >
{{ student.name }} {{ student.name }}
</div> </div>
@ -190,46 +190,46 @@
<v-card <v-card
v-for="(student, index) in pickedStudents" v-for="(student, index) in pickedStudents"
:key="index" :key="index"
variant="outlined"
color="primary"
class="mb-2 result-card" class="mb-2 result-card"
color="primary"
variant="outlined"
> >
<v-card-text <v-card-text
class="text-h4 text-center py-4 d-flex align-center justify-center" class="text-h4 text-center py-4 d-flex align-center justify-center"
> >
{{ student }} {{ student }}
<v-btn <v-btn
icon="mdi-refresh"
variant="text"
size="small"
class="ml-2 refresh-btn"
@click="refreshSingleStudent(index)"
:disabled="remainingStudents.length === 0" :disabled="remainingStudents.length === 0"
:title=" :title="
remainingStudents.length === 0 remainingStudents.length === 0
? '没有更多可用学生' ? '没有更多可用学生'
: '重新抽取此学生' : '重新抽取此学生'
" "
class="ml-2 refresh-btn"
icon="mdi-refresh"
size="small"
variant="text"
@click="refreshSingleStudent(index)"
/> />
</v-card-text> </v-card-text>
</v-card> </v-card>
<div class="mt-8 d-flex justify-center"> <div class="mt-8 d-flex justify-center">
<v-btn <v-btn
class="mx-2"
color="primary" color="primary"
prepend-icon="mdi-refresh" prepend-icon="mdi-refresh"
@click="resetPicker"
size="large" size="large"
class="mx-2" @click="resetPicker"
> >
重新抽取 重新抽取
</v-btn> </v-btn>
<v-btn <v-btn
class="mx-2"
color="grey" color="grey"
size="large"
variant="outlined" variant="outlined"
@click="dialog = false" @click="dialog = false"
size="large"
class="mx-2"
> >
关闭 关闭
</v-btn> </v-btn>

View File

@ -2,20 +2,21 @@
<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 icon="mdi-clock-alert-outline" size="large" class="mr-2" /> <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 flat class="mb-4" v-if="activeRequests.length > 0"> <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 prepend-icon="mdi-web" color="primary"> >
<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">{{
@ -25,7 +26,8 @@
<v-list-item-subtitle> <v-list-item-subtitle>
{{ request.method }} {{ request.path }} {{ request.method }} {{ request.path }}
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item></v-list </v-list-item>
</v-list
> >
<v-divider <v-divider
v-if="index < activeRequests.length - 1" v-if="index < activeRequests.length - 1"

View File

@ -1,11 +1,11 @@
<template> <template>
<v-alert <v-alert
v-if="isReadOnly" v-if="isReadOnly"
class="readonly-warning"
closable
prominent
type="warning" type="warning"
variant="tonal" variant="tonal"
prominent
closable
class="readonly-warning"
@click:close="dismissed = true" @click:close="dismissed = true"
> >
<template #prepend> <template #prepend>

View File

@ -1,11 +1,11 @@
<template> <template>
<v-card elevation="2" class="settings-card rounded-lg"> <v-card class="settings-card rounded-lg" elevation="2">
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon <v-icon
:icon="icon" :icon="icon"
size="large"
class="mr-2" class="mr-2"
size="large"
/> />
</template> </template>
<v-card-title class="text-h6">{{ title }}</v-card-title> <v-card-title class="text-h6">{{ title }}</v-card-title>
@ -14,9 +14,9 @@
<v-card-text> <v-card-text>
<v-progress-linear <v-progress-linear
v-if="loading" v-if="loading"
indeterminate
color="primary"
class="mb-4" class="mb-4"
color="primary"
indeterminate
/> />
<slot/> <slot/>
</v-card-text> </v-card-text>

View File

@ -3,7 +3,7 @@
<!-- 统一链接生成器卡片 --> <!-- 统一链接生成器卡片 -->
<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 start icon="mdi-link-variant" class="mr-2" /> <v-icon class="mr-2" icon="mdi-link-variant" start/>
统一链接生成器 统一链接生成器
</v-card-title> </v-card-title>
@ -13,7 +13,7 @@
</div> </div>
<!-- 预配置认证信息部分 --> <!-- 预配置认证信息部分 -->
<v-card variant="tonal" class="mb-4"> <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>
预配置认证信息 预配置认证信息
@ -24,23 +24,23 @@
<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"
label="命名空间"
variant="outlined"
prepend-inner-icon="mdi-identifier"
placeholder="例如: classroom-001"
hint="设备的命名空间标识符" hint="设备的命名空间标识符"
label="命名空间"
persistent-hint persistent-hint
placeholder="例如: classroom-001"
prepend-inner-icon="mdi-identifier"
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"
label="认证码"
variant="outlined"
prepend-inner-icon="mdi-lock-outline"
placeholder="设备认证码(可选)"
hint="留空则需要用户手动输入" hint="留空则需要用户手动输入"
label="认证码"
persistent-hint persistent-hint
placeholder="设备认证码(可选)"
prepend-inner-icon="mdi-lock-outline"
variant="outlined"
/> />
</v-col> </v-col>
</v-row> </v-row>
@ -49,10 +49,10 @@
<v-col cols="12"> <v-col cols="12">
<v-checkbox <v-checkbox
v-model="preconfigForm.autoExecute" v-model="preconfigForm.autoExecute"
label="自动执行认证"
hint="启用后会自动尝试认证,即使没有认证码也会尝试"
persistent-hint
density="compact" density="compact"
hint="启用后会自动尝试认证,即使没有认证码也会尝试"
label="自动执行认证"
persistent-hint
/> />
</v-col> </v-col>
</v-row> </v-row>
@ -60,36 +60,38 @@
<!-- 预配置信息预览 --> <!-- 预配置信息预览 -->
<v-alert <v-alert
v-if="preconfigForm.namespace" v-if="preconfigForm.namespace"
class="mt-3"
type="info" type="info"
variant="tonal" variant="tonal"
class="mt-3"
> >
<div class="text-subtitle-2 mb-2">预配置信息</div> <div class="text-subtitle-2 mb-2">预配置信息</div>
<v-chip size="small" class="mr-2 mb-1"> <v-chip class="mr-2 mb-1" size="small">
<v-icon start size="small">mdi-identifier</v-icon> <v-icon size="small" start>mdi-identifier</v-icon>
命名空间: {{ preconfigForm.namespace }} 命名空间: {{ preconfigForm.namespace }}
</v-chip> </v-chip>
<v-chip <v-chip
v-if="preconfigForm.authCode" v-if="preconfigForm.authCode"
size="small"
class="mr-2 mb-1" class="mr-2 mb-1"
color="warning" color="warning"
size="small"
> >
<v-icon start size="small">mdi-lock</v-icon> <v-icon size="small" start>mdi-lock</v-icon>
认证码: {{ preconfigForm.authCode.length > 8 ? preconfigForm.authCode.substring(0, 8) + "..." : preconfigForm.authCode }} 认证码: {{ preconfigForm.authCode.length > 8 ? preconfigForm.authCode.substring(0, 8) + "..." :
preconfigForm.authCode }}
</v-chip> </v-chip>
<v-chip v-else size="small" class="mr-2 mb-1" color="grey"> <v-chip v-else class="mr-2 mb-1" color="grey" size="small">
<v-icon start size="small">mdi-lock-open</v-icon> <v-icon size="small" start>mdi-lock-open</v-icon>
无认证码 无认证码
</v-chip> </v-chip>
<v-chip <v-chip
size="small"
class="mr-2 mb-1"
:color="preconfigForm.autoExecute ? 'success' : 'orange'" :color="preconfigForm.autoExecute ? 'success' : 'orange'"
class="mr-2 mb-1"
size="small"
> >
<v-icon start size="small">{{ <v-icon size="small" start>{{
preconfigForm.autoExecute ? "mdi-play-circle" : "mdi-hand-back-left" preconfigForm.autoExecute ? "mdi-play-circle" : "mdi-hand-back-left"
}}</v-icon> }}
</v-icon>
{{ preconfigForm.autoExecute ? "自动认证" : "手动认证" }} {{ preconfigForm.autoExecute ? "自动认证" : "手动认证" }}
</v-chip> </v-chip>
</v-alert> </v-alert>
@ -97,7 +99,7 @@
</v-card> </v-card>
<!-- 设置分享部分 --> <!-- 设置分享部分 -->
<v-card variant="tonal" class="mb-4"> <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>
设置分享可选 设置分享可选
@ -111,37 +113,37 @@
<!-- 设置快速选择按钮 --> <!-- 设置快速选择按钮 -->
<div class="d-flex mb-3 gap-2 flex-wrap"> <div class="d-flex mb-3 gap-2 flex-wrap">
<v-btn <v-btn
size="small"
variant="tonal"
color="primary" color="primary"
prepend-icon="mdi-server-network" prepend-icon="mdi-server-network"
size="small"
variant="tonal"
@click="selectDataSourceSettings" @click="selectDataSourceSettings"
> >
数据源设置 数据源设置
</v-btn> </v-btn>
<v-btn <v-btn
size="small"
variant="tonal"
color="primary" color="primary"
prepend-icon="mdi-compare" prepend-icon="mdi-compare"
size="small"
variant="tonal"
@click="selectChangedSettings" @click="selectChangedSettings"
> >
已变更设置 已变更设置
</v-btn> </v-btn>
<v-btn <v-btn
size="small"
variant="tonal"
color="success" color="success"
prepend-icon="mdi-select-all" prepend-icon="mdi-select-all"
size="small"
variant="tonal"
@click="selectAll" @click="selectAll"
> >
全选 全选
</v-btn> </v-btn>
<v-btn <v-btn
size="small"
variant="tonal"
color="error" color="error"
prepend-icon="mdi-select-remove" prepend-icon="mdi-select-remove"
size="small"
variant="tonal"
@click="resetSelection" @click="resetSelection"
> >
清除选择 清除选择
@ -150,7 +152,7 @@
<!-- 选择摘要 --> <!-- 选择摘要 -->
<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 color="primary" class="mr-2"> <v-chip class="mr-2" color="primary">
已选 {{ selectedItems.length }} 项设置 已选 {{ selectedItems.length }} 项设置
</v-chip> </v-chip>
@ -158,17 +160,17 @@
<v-chip <v-chip
v-for="item in selectedItems.slice(0, 3)" v-for="item in selectedItems.slice(0, 3)"
:key="item" :key="item"
size="small"
class="mr-1" class="mr-1"
size="small"
variant="text" variant="text"
> >
{{ getSettingDescription(item) }} {{ getSettingDescription(item) }}
</v-chip> </v-chip>
<v-chip <v-chip
v-if="selectedItems.length > 3" v-if="selectedItems.length > 3"
color="grey"
size="small" size="small"
variant="text" variant="text"
color="grey"
> >
+{{ selectedItems.length - 3 }} 更多 +{{ selectedItems.length - 3 }} 更多
</v-chip> </v-chip>
@ -190,39 +192,39 @@
<v-expansion-panel-text> <v-expansion-panel-text>
<v-text-field <v-text-field
v-model="search" v-model="search"
class="mb-4"
clearable
hide-details
label="搜索设置" label="搜索设置"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
single-line single-line
hide-details
class="mb-4"
clearable
/> />
<v-data-table <v-data-table
:items-per-page="settingItems.length" v-model="selectedItems"
:headers="headers" :headers="headers"
:items="filteredItems" :items="filteredItems"
item-value="key" :items-per-page="settingItems.length"
v-model="selectedItems"
show-select
density="compact"
class="rounded setting-table"
@update:selected="handleSelectionChange"
:sort-by="[{ key: 'isChanged', order: 'desc' }]" :sort-by="[{ key: 'isChanged', order: 'desc' }]"
class="rounded setting-table"
density="compact"
item-value="key"
show-select
@update:selected="handleSelectionChange"
> >
<template #[`item.description`]="{ item }"> <template #[`item.description`]="{ item }">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon <v-icon
size="small"
:icon="item.icon" :icon="item.icon"
class="mr-2" class="mr-2"
size="small"
/> />
{{ item.description }} {{ item.description }}
<v-chip <v-chip
v-if="item.key === 'server.kvToken'" v-if="item.key === 'server.kvToken'"
size="x-small"
color="error"
class="ml-2" class="ml-2"
color="error"
size="x-small"
> >
敏感 敏感
</v-chip> </v-chip>
@ -245,10 +247,10 @@
<template #[`item.isChanged`]="{ item }"> <template #[`item.isChanged`]="{ item }">
<v-chip <v-chip
size="x-small"
:color="item.isChanged ? 'warning' : 'success'" :color="item.isChanged ? 'warning' : 'success'"
:text="item.isChanged ? '已修改' : '默认'" :text="item.isChanged ? '已修改' : '默认'"
density="compact" density="compact"
size="x-small"
/> />
</template> </template>
</v-data-table> </v-data-table>
@ -259,7 +261,7 @@
</v-card> </v-card>
<!-- 链接生成和操作部分 --> <!-- 链接生成和操作部分 -->
<v-card variant="outlined" class="mb-4"> <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>
生成的统一链接 生成的统一链接
@ -269,27 +271,27 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="d-flex mb-3 gap-2 flex-wrap"> <div class="d-flex mb-3 gap-2 flex-wrap">
<v-btn <v-btn
variant="flat" :disabled="!preconfigForm.namespace.trim()"
color="primary" color="primary"
prepend-icon="mdi-auto-fix" prepend-icon="mdi-auto-fix"
variant="flat"
@click="generateUnifiedLink" @click="generateUnifiedLink"
:disabled="!preconfigForm.namespace.trim()"
> >
生成统一链接 生成统一链接
</v-btn> </v-btn>
<v-btn <v-btn
variant="tonal" :disabled="!unifiedLink"
color="success" color="success"
prepend-icon="mdi-test-tube" prepend-icon="mdi-test-tube"
variant="tonal"
@click="openTestLink" @click="openTestLink"
:disabled="!unifiedLink"
> >
测试链接 测试链接
</v-btn> </v-btn>
<v-btn <v-btn
variant="tonal"
color="error" color="error"
prepend-icon="mdi-delete" prepend-icon="mdi-delete"
variant="tonal"
@click="clearAll" @click="clearAll"
> >
清空所有 清空所有
@ -299,38 +301,38 @@
<!-- 生成的链接 --> <!-- 生成的链接 -->
<v-text-field <v-text-field
v-model="unifiedLink" v-model="unifiedLink"
:append-inner-icon="linkCopied ? 'mdi-check' : 'mdi-content-copy'"
:placeholder="preconfigForm.namespace ? '点击「生成统一链接」按钮' : '请先输入命名空间'"
class="mb-3"
label="统一链接" label="统一链接"
readonly readonly
variant="outlined" variant="outlined"
class="mb-3"
:append-inner-icon="linkCopied ? 'mdi-check' : 'mdi-content-copy'"
@click:append-inner="copyUnifiedLink" @click:append-inner="copyUnifiedLink"
:placeholder="preconfigForm.namespace ? '点击「生成统一链接」按钮' : '请先输入命名空间'"
/> />
<!-- 链接内容预览 --> <!-- 链接内容预览 -->
<v-alert <v-alert
v-if="unifiedLink" v-if="unifiedLink"
class="mb-3"
type="success" type="success"
variant="tonal" variant="tonal"
class="mb-3"
> >
<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 size="small" color="primary"> <v-chip color="primary" size="small">
<v-icon start size="small">mdi-account-key</v-icon> <v-icon size="small" start>mdi-account-key</v-icon>
预配置认证 预配置认证
</v-chip> </v-chip>
<v-chip <v-chip
v-if="selectedItems.length > 0" v-if="selectedItems.length > 0"
size="small"
color="secondary" color="secondary"
size="small"
> >
<v-icon start size="small">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 size="small" color="grey"> <v-chip v-else color="grey" size="small">
<v-icon start size="small">mdi-cog-off</v-icon> <v-icon size="small" start>mdi-cog-off</v-icon>
无额外设置 无额外设置
</v-chip> </v-chip>
</div> </div>

View File

@ -14,12 +14,12 @@
<v-autocomplete <v-autocomplete
v-model="selectedName" v-model="selectedName"
:items="studentList" :items="studentList"
clearable
hide-details
item-title="name" item-title="name"
item-value="name" item-value="name"
label="学生姓名" label="学生姓名"
placeholder="选择您的姓名" placeholder="选择您的姓名"
clearable
hide-details
/> />
<div <div
v-if="studentList.length > 0" v-if="studentList.length > 0"
@ -29,9 +29,9 @@
</div> </div>
<v-alert <v-alert
v-if="error" v-if="error"
class="mt-3"
type="error" type="error"
variant="tonal" variant="tonal"
class="mt-3"
> >
{{ error }} {{ error }}
</v-alert> </v-alert>
@ -58,10 +58,10 @@
<!-- 顶栏学生姓名显示通过插槽暴露给父组件 --> <!-- 顶栏学生姓名显示通过插槽暴露给父组件 -->
<slot <slot
name="header-display"
:student-name="currentStudentName"
:is-student="isStudentToken" :is-student="isStudentToken"
:open-dialog="openDialog" :open-dialog="openDialog"
:student-name="currentStudentName"
name="header-display"
/> />
</template> </template>
@ -70,6 +70,7 @@ import { ref, computed, watch, onMounted } from 'vue'
import {getSetting, watchSettings} from '@/utils/settings' import {getSetting, watchSettings} from '@/utils/settings'
import axios from '@/axios/axios' import axios from '@/axios/axios'
import dataProvider from '@/utils/dataProvider' import dataProvider from '@/utils/dataProvider'
const emit = defineEmits(['token-info-updated']) const emit = defineEmits(['token-info-updated'])
const showDialog = ref(false) const showDialog = ref(false)

View File

@ -4,17 +4,17 @@
<v-card-text> <v-card-text>
<v-textarea <v-textarea
v-model="code" v-model="code"
density="comfortable"
hide-details="auto"
label="替代代码" label="替代代码"
placeholder="请输入替代代码" placeholder="请输入替代代码"
variant="outlined"
density="comfortable"
rows="5" rows="5"
hide-details="auto" variant="outlined"
/> />
<v-alert <v-alert
class="mt-3"
type="info" type="info"
variant="tonal" variant="tonal"
class="mt-3"
> >
替代代码功能暂未实现敬请期待 替代代码功能暂未实现敬请期待
</v-alert> </v-alert>

View File

@ -3,9 +3,9 @@
<v-card-text class="pa-8"> <v-card-text class="pa-8">
<div class="text-center mb-6"> <div class="text-center mb-6">
<v-icon <v-icon
size="80"
color="success"
class="mb-4" class="mb-4"
color="success"
size="80"
> >
mdi-account-key mdi-account-key
</v-icon> </v-icon>
@ -18,14 +18,14 @@
</div> </div>
<v-card <v-card
variant="tonal"
color="info"
class="pa-4 mb-6" class="pa-4 mb-6"
color="info"
variant="tonal"
> >
<div class="text-body-2"> <div class="text-body-2">
<v-icon <v-icon
size="20"
class="mr-2" class="mr-2"
size="20"
> >
mdi-information mdi-information
</v-icon> </v-icon>
@ -36,11 +36,11 @@
<div class="form-section"> <div class="form-section">
<v-text-field <v-text-field
v-model="form.namespace" v-model="form.namespace"
label="命名空间"
class="mb-4" class="mb-4"
variant="outlined"
hide-details="auto" hide-details="auto"
label="命名空间"
prepend-inner-icon="mdi-identifier" prepend-inner-icon="mdi-identifier"
variant="outlined"
> >
</v-text-field> </v-text-field>
@ -48,19 +48,19 @@
<v-text-field <v-text-field
v-model="form.password" v-model="form.password"
label="认证码" label="认证码"
prepend-inner-icon="mdi-lock-outline"
type="text" type="text"
variant="outlined" variant="outlined"
prepend-inner-icon="mdi-lock-outline"
> >
</v-text-field> </v-text-field>
<v-alert <v-alert
v-if="error" v-if="error"
type="error"
variant="tonal"
class="mt-4" class="mt-4"
closable closable
type="error"
variant="tonal"
@click:close="error = ''" @click:close="error = ''"
> >
{{ error }} {{ error }}
@ -81,15 +81,15 @@
<v-btn <v-btn
:disabled="!form.namespace || authenticating" :disabled="!form.namespace || authenticating"
:loading="authenticating" :loading="authenticating"
size="x-large"
color="primary"
variant="elevated"
class="px-8" class="px-8"
color="primary"
size="x-large"
variant="elevated"
@click="authenticate" @click="authenticate"
> >
<v-icon <v-icon
start
size="24" size="24"
start
> >
mdi-login mdi-login
</v-icon> </v-icon>

View File

@ -15,9 +15,9 @@
> >
<div class="text-center mb-6"> <div class="text-center mb-6">
<v-icon <v-icon
size="80"
color="primary"
class="mb-4" class="mb-4"
color="primary"
size="80"
> >
mdi-hand-wave mdi-hand-wave
</v-icon> </v-icon>
@ -40,22 +40,22 @@
</h3> </h3>
<v-card <v-card
variant="tonal"
color="primary"
class="pa-6 mb-6" class="pa-6 mb-6"
color="primary"
variant="tonal"
> >
<div class="relationship-diagram"> <div class="relationship-diagram">
<!-- Classworks 应用 --> <!-- Classworks 应用 -->
<div class="diagram-item"> <div class="diagram-item">
<v-card <v-card
elevation="8"
color="blue-darken-1"
class="pa-4" class="pa-4"
color="blue-darken-1"
elevation="8"
> >
<div class="text-center"> <div class="text-center">
<v-icon <v-icon
size="60"
color="white" color="white"
size="60"
> >
mdi-laptop mdi-laptop
</v-icon> </v-icon>
@ -70,10 +70,10 @@
<div class="diagram-description mt-3"> <div class="diagram-description mt-3">
<v-chip <v-chip
color="blue"
variant="flat"
size="small"
class="mb-2" class="mb-2"
color="blue"
size="small"
variant="flat"
> >
前端应用 前端应用
</v-chip> </v-chip>
@ -88,8 +88,8 @@
<!-- 连接线 --> <!-- 连接线 -->
<div class="diagram-connector"> <div class="diagram-connector">
<v-icon <v-icon
size="40"
color="primary" color="primary"
size="40"
> >
mdi-swap-horizontal mdi-swap-horizontal
</v-icon> </v-icon>
@ -101,14 +101,14 @@
<!-- Classworks KV --> <!-- Classworks KV -->
<div class="diagram-item"> <div class="diagram-item">
<v-card <v-card
elevation="8"
color="green-darken-1"
class="pa-4" class="pa-4"
color="green-darken-1"
elevation="8"
> >
<div class="text-center"> <div class="text-center">
<v-icon <v-icon
size="60"
color="white" color="white"
size="60"
> >
mdi-cloud-sync mdi-cloud-sync
</v-icon> </v-icon>
@ -123,10 +123,10 @@
<div class="diagram-description mt-3"> <div class="diagram-description mt-3">
<v-chip <v-chip
color="green"
variant="flat"
size="small"
class="mb-2" class="mb-2"
color="green"
size="small"
variant="flat"
> >
后端服务 后端服务
</v-chip> </v-chip>
@ -153,9 +153,9 @@
</h3> </h3>
<v-card <v-card
variant="tonal"
color="info"
class="mb-6 pa-4" class="mb-6 pa-4"
color="info"
variant="tonal"
> >
<div class="text-body-2"> <div class="text-body-2">
比如在家里电脑手机上查看或者多个教室设备共享数据 比如在家里电脑手机上查看或者多个教室设备共享数据
@ -164,17 +164,17 @@
<div class="button-group"> <div class="button-group">
<v-btn <v-btn
size="x-large"
block block
variant="elevated"
color="primary"
class="mb-4 py-6" class="mb-4 py-6"
color="primary"
size="x-large"
variant="elevated"
@click="selectStorageType('cloud')" @click="selectStorageType('cloud')"
> >
<div class="d-flex flex-column align-center py-2"> <div class="d-flex flex-column align-center py-2">
<v-icon <v-icon
size="40"
class="mb-2" class="mb-2"
size="40"
> >
mdi-cloud-check mdi-cloud-check
</v-icon> </v-icon>
@ -184,16 +184,16 @@
</v-btn> </v-btn>
<v-btn <v-btn
size="x-large"
block block
variant="outlined"
class="py-6" class="py-6"
size="x-large"
variant="outlined"
@click="selectStorageType('local')" @click="selectStorageType('local')"
> >
<div class="d-flex flex-column align-center py-2"> <div class="d-flex flex-column align-center py-2">
<v-icon <v-icon
size="40"
class="mb-2" class="mb-2"
size="40"
> >
mdi-laptop mdi-laptop
</v-icon> </v-icon>
@ -211,9 +211,9 @@
> >
<div class="text-center mb-6"> <div class="text-center mb-6">
<v-icon <v-icon
size="80"
color="success"
class="mb-4" class="mb-4"
color="success"
size="80"
> >
mdi-check-circle mdi-check-circle
</v-icon> </v-icon>
@ -221,8 +221,8 @@
您可以使用本地模式 您可以使用本地模式
</h3> </h3>
<v-card <v-card
variant="tonal"
class="pa-4 text-left" class="pa-4 text-left"
variant="tonal"
> >
<div class="text-body-1 mb-2"> <div class="text-body-1 mb-2">
此数据将存储在您的浏览器中如果您的浏览器不支持IndexedDB可能会出现问题如果您经常清除浏览器数据请谨慎使用本地模式 此数据将存储在您的浏览器中如果您的浏览器不支持IndexedDB可能会出现问题如果您经常清除浏览器数据请谨慎使用本地模式
@ -241,9 +241,9 @@
> >
<div class="text-center mb-6"> <div class="text-center mb-6">
<v-icon <v-icon
size="80"
color="primary"
class="mb-4" class="mb-4"
color="primary"
size="80"
> >
mdi-cloud-cog mdi-cloud-cog
</v-icon> </v-icon>
@ -253,8 +253,8 @@
</div> </div>
<v-card <v-card
variant="tonal"
class="pa-6 mb-6" class="pa-6 mb-6"
variant="tonal"
> >
<div class="d-flex flex-column flex-sm-row align-center"> <div class="d-flex flex-column flex-sm-row align-center">
<div class="flex-grow-1"> <div class="flex-grow-1">
@ -266,9 +266,9 @@
</p> </p>
<v-btn <v-btn
color="primary" color="primary"
prepend-icon="mdi-flash"
size="large" size="large"
variant="elevated" variant="elevated"
prepend-icon="mdi-flash"
@click="goToProgressiveStep" @click="goToProgressiveStep"
> >
自动注册 自动注册
@ -277,16 +277,17 @@
</div> </div>
</v-card> </v-card>
<div class="mb-6"> <div class="mb-6">
也可以手动前往 Classworks KV 控制台获取认证信息</div> 也可以手动前往 Classworks KV 控制台获取认证信息
</div>
<v-card <v-card
:variant="kvserverurl=='https://kv.houlang.cloud'? 'elevated' : 'outlined'"
:color=" kvserverurl=='https://kv.houlang.cloud'? 'primary' : 'error' " :color=" kvserverurl=='https://kv.houlang.cloud'? 'primary' : 'error' "
:variant="kvserverurl=='https://kv.houlang.cloud'? 'elevated' : 'outlined'"
class="pa-6 mb-6" class="pa-6 mb-6"
@click="openKVSite" @click="openKVSite"
> >
<v-icon <v-icon
size="48"
class="mb-3" class="mb-3"
size="48"
> >
mdi-open-in-new mdi-open-in-new
</v-icon> </v-icon>
@ -322,12 +323,13 @@
</v-expansion-panel-title> </v-expansion-panel-title>
<v-expansion-panel-text> <v-expansion-panel-text>
<v-card <v-card
variant="tonal"
color="success"
class="pa-4" class="pa-4"
color="success"
variant="tonal"
> >
<div class="text-body-2 mb-2"> <div class="text-body-2 mb-2">
如果您之前已经使用过 Classworks KV可以直接使用您的 <strong>UUID命名空间</strong> <strong>设置的密码</strong> 进行认证 如果您之前已经使用过 Classworks KV可以直接使用您的 <strong>UUID命名空间</strong>
<strong>设置的密码</strong> 进行认证
</div> </div>
<div class="text-body-2"> <div class="text-body-2">
返回上一页点击"已注册"按钮输入您的认证信息即可登录 返回上一页点击"已注册"按钮输入您的认证信息即可登录
@ -350,9 +352,9 @@
</v-expansion-panel-title> </v-expansion-panel-title>
<v-expansion-panel-text> <v-expansion-panel-text>
<v-card <v-card
variant="tonal"
color="info"
class="pa-4" class="pa-4"
color="info"
variant="tonal"
> >
<div class="text-body-2 mb-2"> <div class="text-body-2 mb-2">
不同的密码对应不同的设备类型这将由 <strong>管理员管理</strong> 不同的密码对应不同的设备类型这将由 <strong>管理员管理</strong>
@ -385,10 +387,10 @@
> >
<div class="text-center mb-6"> <div class="text-center mb-6">
<v-avatar <v-avatar
size="80"
color="primary"
variant="tonal"
class="mb-4" class="mb-4"
color="primary"
size="80"
variant="tonal"
> >
<v-icon size="48"> <v-icon size="48">
mdi-rocket-launch mdi-rocket-launch
@ -404,21 +406,20 @@
<v-progress-linear <v-progress-linear
:model-value="progressValue" :model-value="progressValue"
height="8"
color="primary"
rounded
class="mb-6" class="mb-6"
color="primary"
height="8"
rounded
/> />
<v-row> <v-row>
<v-col <v-col
cols="12" cols="12"
> >
<v-card <v-card
variant="tonal"
:color="statusColor" :color="statusColor"
variant="tonal"
> >
<v-card-item> <v-card-item>
<div class="d-flex align-center mb-3"> <div class="d-flex align-center mb-3">
@ -487,8 +488,8 @@
<v-btn <v-btn
v-if="progressiveStatus === 'idle'" v-if="progressiveStatus === 'idle'"
color="primary" color="primary"
size="large"
prepend-icon="mdi-play" prepend-icon="mdi-play"
size="large"
@click="startProgressiveRegister" @click="startProgressiveRegister"
> >
开始创建 开始创建
@ -497,8 +498,8 @@
<v-btn <v-btn
v-if="progressiveStatus === 'error'" v-if="progressiveStatus === 'error'"
color="error" color="error"
variant="outlined"
prepend-icon="mdi-refresh" prepend-icon="mdi-refresh"
variant="outlined"
@click="retryProgressiveRegister" @click="retryProgressiveRegister"
> >
重试 重试
@ -506,10 +507,10 @@
<v-btn <v-btn
v-if="progressiveStatus === 'registering'" v-if="progressiveStatus === 'registering'"
color="primary"
variant="tonal"
:loading="true" :loading="true"
color="primary"
prepend-icon="mdi-progress-clock" prepend-icon="mdi-progress-clock"
variant="tonal"
> >
正在执行 正在执行
</v-btn> </v-btn>
@ -517,9 +518,9 @@
<v-btn <v-btn
v-if="progressiveStatus === 'success'" v-if="progressiveStatus === 'success'"
color="success" color="success"
prepend-icon="mdi-check-circle"
size="large" size="large"
variant="elevated" variant="elevated"
prepend-icon="mdi-check-circle"
@click="applyTokenAndClose" @click="applyTokenAndClose"
> >
应用令牌并关闭 应用令牌并关闭
@ -528,9 +529,9 @@
<v-btn <v-btn
v-if="progressiveStatus === 'success'" v-if="progressiveStatus === 'success'"
color="primary" color="primary"
prepend-icon="mdi-open-in-new"
size="large" size="large"
variant="outlined" variant="outlined"
prepend-icon="mdi-open-in-new"
@click="openAuthPage" @click="openAuthPage"
> >
前往绑定账户 前往绑定账户
@ -556,8 +557,8 @@
<v-btn <v-btn
v-if="currentStep < totalSteps && currentStep !== 4" v-if="currentStep < totalSteps && currentStep !== 4"
:disabled="currentStep === 3 && !storageType" :disabled="currentStep === 3 && !storageType"
size="large"
color="primary" color="primary"
size="large"
variant="elevated" variant="elevated"
@click="nextStep" @click="nextStep"
> >
@ -568,8 +569,8 @@
</v-btn> </v-btn>
<v-btn <v-btn
v-if="currentStep === totalSteps || currentStep === 4" v-if="currentStep === totalSteps || currentStep === 4"
size="large"
color="primary" color="primary"
size="large"
variant="elevated" variant="elevated"
@click="finish" @click="finish"
> >
@ -584,6 +585,7 @@ import { ref, computed } from 'vue'
import {getSetting, setSetting} from '@/utils/settings' import {getSetting, setSetting} from '@/utils/settings'
import axios from '@/axios/axios' import axios from '@/axios/axios'
import {v4 as uuidv4} from 'uuid' import {v4 as uuidv4} from 'uuid'
const emit = defineEmits(['close', 'success']) const emit = defineEmits(['close', 'success'])
const kvserverurl = getSetting('server.authDomain') const kvserverurl = getSetting('server.authDomain')
const currentStep = ref(1) const currentStep = ref(1)

View File

@ -2,8 +2,8 @@
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon <v-icon
icon="mdi-account-plus"
class="mr-2" class="mr-2"
icon="mdi-account-plus"
/> />
渐进式注册 渐进式注册
</v-card-title> </v-card-title>
@ -15,9 +15,9 @@
</p> </p>
<v-alert <v-alert
class="mb-4"
type="info" type="info"
variant="tonal" variant="tonal"
class="mb-4"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-information"/> <v-icon icon="mdi-information"/>
@ -30,10 +30,10 @@
<div v-else-if="isRegistering"> <div v-else-if="isRegistering">
<div class="text-center py-4"> <div class="text-center py-4">
<v-progress-circular <v-progress-circular
class="mb-4"
color="primary"
indeterminate indeterminate
size="48" size="48"
color="primary"
class="mb-4"
/> />
<p class="text-h6 mb-2"> <p class="text-h6 mb-2">
正在注册设备... 正在注册设备...
@ -47,9 +47,9 @@
<!-- 注册成功 --> <!-- 注册成功 -->
<div v-else-if="isRegistered && deviceInfo"> <div v-else-if="isRegistered && deviceInfo">
<v-alert <v-alert
class="mb-4"
type="success" type="success"
variant="tonal" variant="tonal"
class="mb-4"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-check-circle"/> <v-icon icon="mdi-check-circle"/>
@ -78,9 +78,9 @@
</v-list> </v-list>
<v-alert <v-alert
class="mt-4"
type="info" type="info"
variant="tonal" variant="tonal"
class="mt-4"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-information"/> <v-icon icon="mdi-information"/>
@ -92,9 +92,9 @@
<!-- 错误状态 --> <!-- 错误状态 -->
<div v-else-if="errorMessage"> <div v-else-if="errorMessage">
<v-alert <v-alert
class="mb-4"
type="error" type="error"
variant="tonal" variant="tonal"
class="mb-4"
> >
<template #prepend> <template #prepend>
<v-icon icon="mdi-alert-circle"/> <v-icon icon="mdi-alert-circle"/>
@ -110,9 +110,9 @@
<!-- 注册按钮 --> <!-- 注册按钮 -->
<v-btn <v-btn
v-if="!isRegistered && !isRegistering" v-if="!isRegistered && !isRegistering"
:loading="isRegistering"
color="primary" color="primary"
prepend-icon="mdi-plus" prepend-icon="mdi-plus"
:loading="isRegistering"
@click="registerDevice" @click="registerDevice"
> >
注册设备 注册设备

View File

@ -5,19 +5,24 @@
## 组件列表 ## 组件列表
### DeviceAuthDialog.vue ### DeviceAuthDialog.vue
设备认证对话框,用于通过 namespace 和密码进行设备认证。 设备认证对话框,用于通过 namespace 和密码进行设备认证。
**Props:** **Props:**
- `showCancel` (Boolean): 是否显示取消按钮,默认为 `false` - `showCancel` (Boolean): 是否显示取消按钮,默认为 `false`
**Events:** **Events:**
- `@success`: 认证成功时触发,传递认证数据 - `@success`: 认证成功时触发,传递认证数据
- `@cancel`: 点击取消按钮时触发 - `@cancel`: 点击取消按钮时触发
**暴露的方法:** **暴露的方法:**
- `reset()`: 清空表单和错误信息 - `reset()`: 清空表单和错误信息
**使用示例:** **使用示例:**
```vue ```vue
<template> <template>
<v-dialog v-model="dialog"> <v-dialog v-model="dialog">
@ -37,19 +42,24 @@ import DeviceAuthDialog from '@/components/auth/DeviceAuthDialog.vue'
--- ---
### TokenInputDialog.vue ### TokenInputDialog.vue
Token 输入对话框,用于手动输入 KV 授权 Token。 Token 输入对话框,用于手动输入 KV 授权 Token。
**Props:** **Props:**
- `showCancel` (Boolean): 是否显示取消按钮,默认为 `false` - `showCancel` (Boolean): 是否显示取消按钮,默认为 `false`
**Events:** **Events:**
- `@success`: Token 验证成功时触发 - `@success`: Token 验证成功时触发
- `@cancel`: 点击取消按钮时触发 - `@cancel`: 点击取消按钮时触发
**暴露的方法:** **暴露的方法:**
- `reset()`: 清空表单和错误信息 - `reset()`: 清空表单和错误信息
**使用示例:** **使用示例:**
```vue ```vue
<template> <template>
<v-dialog v-model="dialog"> <v-dialog v-model="dialog">
@ -69,19 +79,24 @@ import TokenInputDialog from '@/components/auth/TokenInputDialog.vue'
--- ---
### AlternativeCodeDialog.vue ### AlternativeCodeDialog.vue
替代代码输入对话框(功能暂未实现)。 替代代码输入对话框(功能暂未实现)。
**Props:** **Props:**
- `showCancel` (Boolean): 是否显示取消按钮,默认为 `false` - `showCancel` (Boolean): 是否显示取消按钮,默认为 `false`
**Events:** **Events:**
- `@submit`: 提交代码时触发,传递代码内容 - `@submit`: 提交代码时触发,传递代码内容
- `@cancel`: 点击取消按钮时触发 - `@cancel`: 点击取消按钮时触发
**暴露的方法:** **暴露的方法:**
- `reset()`: 清空表单 - `reset()`: 清空表单
**使用示例:** **使用示例:**
```vue ```vue
<template> <template>
<v-dialog v-model="dialog"> <v-dialog v-model="dialog">
@ -101,12 +116,15 @@ import AlternativeCodeDialog from '@/components/auth/AlternativeCodeDialog.vue'
--- ---
### FirstTimeGuide.vue ### FirstTimeGuide.vue
初次使用指南,介绍 Classworks KV 的功能和使用方式。 初次使用指南,介绍 Classworks KV 的功能和使用方式。
**Events:** **Events:**
- `@close`: 关闭指南时触发 - `@close`: 关闭指南时触发
**使用示例:** **使用示例:**
```vue ```vue
<template> <template>
<v-dialog v-model="dialog"> <v-dialog v-model="dialog">

View File

@ -4,18 +4,18 @@
<v-card-text> <v-card-text>
<v-text-field <v-text-field
v-model="token" v-model="token"
clearable
density="comfortable"
hide-details="auto"
label="KV 授权 Token" label="KV 授权 Token"
placeholder="粘贴从授权页面获取的 Token" placeholder="粘贴从授权页面获取的 Token"
variant="outlined" variant="outlined"
density="comfortable"
hide-details="auto"
clearable
/> />
<v-alert <v-alert
v-if="error" v-if="error"
class="mt-3"
type="error" type="error"
variant="tonal" variant="tonal"
class="mt-3"
> >
{{ error }} {{ error }}
</v-alert> </v-alert>

View File

@ -2,9 +2,9 @@
<div class="warning-container"> <div class="warning-container">
<v-chip <v-chip
v-if="show" v-if="show"
class="warning-chip"
color="warning" color="warning"
size="small" size="small"
class="warning-chip"
> >
{{ message }} {{ message }}
</v-chip> </v-chip>
@ -35,7 +35,13 @@ export default {
} }
@keyframes fade-in { @keyframes fade-in {
from { opacity: 0; transform: translateY(-10px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
</style> </style>

View File

@ -33,11 +33,11 @@
</template> </template>
<v-overlay <v-overlay
opacity=".12"
scrim="primary"
contained contained
model-value model-value
opacity=".12"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -46,18 +46,18 @@
<v-card <v-card
class="py-4" class="py-4"
color="surface-variant" color="surface-variant"
to="/"
prepend-icon="mdi-home" prepend-icon="mdi-home"
rounded="lg" rounded="lg"
title="返回首页" title="返回首页"
to="/"
variant="text" variant="text"
> >
<v-overlay <v-overlay
opacity=".06"
scrim="primary"
contained contained
model-value model-value
opacity=".06"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>
@ -66,18 +66,18 @@
<v-card <v-card
class="py-4" class="py-4"
color="surface-variant" color="surface-variant"
@click="this.$router.back()"
prepend-icon="mdi-arrow-left-drop-circle" prepend-icon="mdi-arrow-left-drop-circle"
rounded="lg" rounded="lg"
title="返回上一页" title="返回上一页"
variant="text" variant="text"
@click="this.$router.back()"
> >
<v-overlay <v-overlay
opacity=".06"
scrim="primary"
contained contained
model-value model-value
opacity=".06"
persistent persistent
scrim="primary"
/> />
</v-card> </v-card>
</v-col> </v-col>

View File

@ -1,20 +1,20 @@
<template> <template>
<v-card border rounded="xl" hover> <v-card border hover rounded="xl">
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-information" size="large" class="mr-2" /> <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 cols="12" md="8" class="mx-auto"> <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 size="120" class="mb-4"> <v-avatar class="mb-4" size="120">
<v-img <v-img
src="../../assets/cslogo.png"
alt="Classworks" alt="Classworks"
src="../../assets/cslogo.png"
/> />
</v-avatar> </v-avatar>
@ -24,35 +24,35 @@
<div class="d-flex gap-2 flex-wrap mb-6"> <div class="d-flex gap-2 flex-wrap mb-6">
<v-btn <v-btn
color="red" color="red"
variant="tonal"
href="https://github.com/ClassworksDev/Classworks/issues" href="https://github.com/ClassworksDev/Classworks/issues"
target="_blank"
prepend-icon="mdi-bug" prepend-icon="mdi-bug"
target="_blank"
variant="tonal"
> >
报告问题 报告问题
</v-btn> </v-btn>
<v-btn <v-btn
color="primary" color="primary"
variant="tonal"
href="https://qm.qq.com/q/qNBX4ZZVeg" href="https://qm.qq.com/q/qNBX4ZZVeg"
target="_blank"
prepend-icon="mdi-qqchat" prepend-icon="mdi-qqchat"
target="_blank"
variant="tonal"
> >
QQ QQ
</v-btn> </v-btn>
<v-btn <v-btn
variant="text"
href="https://github.com/ClassworksDev/Classworks" href="https://github.com/ClassworksDev/Classworks"
target="_blank"
prepend-icon="mdi-github" prepend-icon="mdi-github"
target="_blank"
variant="text"
> >
前端 前端
</v-btn> </v-btn>
<v-btn <v-btn
variant="text"
href="https://github.com/ClassworksDev/ClassworksServer" href="https://github.com/ClassworksDev/ClassworksServer"
target="_blank"
prepend-icon="mdi-github" prepend-icon="mdi-github"
target="_blank"
variant="text"
> >
后端 后端
</v-btn> </v-btn>
@ -63,9 +63,9 @@
<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"
href="https://github.com/EnderWolf006/HomeworkBoard" href="https://github.com/EnderWolf006/HomeworkBoard"
target="_blank" target="_blank"
append-icon="mdi-link"
> >
<v-list-item-title> <v-list-item-title>
本项目受到 HomeworkBoard 的启发而开发 本项目受到 HomeworkBoard 的启发而开发
@ -76,9 +76,9 @@
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item <v-list-item
append-icon="mdi-link"
href="https://hlyun.org" href="https://hlyun.org"
target="_blank" target="_blank"
append-icon="mdi-link"
> >
<v-list-item-title> <v-list-item-title>
Classworks <strong>厚浪云</strong>提供 Classworks <strong>厚浪云</strong>提供
@ -88,9 +88,9 @@
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item <v-list-item
append-icon="mdi-link"
href="https://zerocat.houlangs.com" href="https://zerocat.houlangs.com"
target="_blank" target="_blank"
append-icon="mdi-link"
> >
<v-list-item-title> <v-list-item-title>
感谢 ZeroCat 社区的开发者们 感谢 ZeroCat 社区的开发者们
@ -101,9 +101,9 @@
</v-list-item> </v-list-item>
<v-divider class="ma-1"></v-divider> <v-divider class="ma-1"></v-divider>
<v-list-item <v-list-item
append-icon="mdi-link"
href="https://github.com/HUSX100/IslandCaller" href="https://github.com/HUSX100/IslandCaller"
target="_blank" target="_blank"
append-icon="mdi-link"
> >
<v-list-item-title> <v-list-item-title>
本项目与 IslandCaller 没有从属关系 本项目与 IslandCaller 没有从属关系
@ -114,9 +114,9 @@
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item <v-list-item
append-icon="mdi-link"
href="https://classisland.tech" href="https://classisland.tech"
target="_blank" target="_blank"
append-icon="mdi-link"
> >
<v-list-item-title> <v-list-item-title>
本项目与 ClassIsland 没有从属关系 本项目与 ClassIsland 没有从属关系
@ -129,9 +129,9 @@
</v-list> </v-list>
<v-btn <v-btn
variant="text"
class="mb-4" class="mb-4"
prepend-icon="mdi-package-variant" prepend-icon="mdi-package-variant"
variant="text"
@click="showDeps = true" @click="showDeps = true"
> >
查看使用的第三方库 查看使用的第三方库
@ -139,11 +139,12 @@
<v-dialog <v-dialog
v-model="showDeps" v-model="showDeps"
transition="dialog-bottom-transition"
fullscreen fullscreen
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-btn>
<v-toolbar-title>使用的第三方库</v-toolbar-title> <v-toolbar-title>使用的第三方库</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -154,8 +155,8 @@
v-for="dep in Dependencies" v-for="dep in Dependencies"
:key="dep.name" :key="dep.name"
:href="'https://www.npmjs.com/package/' + dep.name" :href="'https://www.npmjs.com/package/' + dep.name"
target="_blank"
append-icon="mdi-link" append-icon="mdi-link"
target="_blank"
> >
<v-list-item-title> <v-list-item-title>
{{ dep.name }} {{ dep.name }}

View File

@ -1,5 +1,5 @@
<template> <template>
<v-list-item class="setting-item" :disabled="disabled"> <v-list-item :disabled="disabled" class="setting-item">
<template #prepend> <template #prepend>
<v-icon :icon="settingIcon"/> <v-icon :icon="settingIcon"/>
</template> </template>
@ -19,57 +19,57 @@
<v-switch <v-switch
v-if="type === 'boolean'" v-if="type === 'boolean'"
v-model="localValue" v-model="localValue"
:disabled="disabled"
density="comfortable" density="comfortable"
hide-details hide-details
:disabled="disabled"
@update:model-value="updateSetting" @update:model-value="updateSetting"
/> />
<v-select <v-select
v-else-if="type === 'string' && hasOptions" v-else-if="type === 'string' && hasOptions"
v-model="localValue" v-model="localValue"
:disabled="disabled"
:items="selectOptions" :items="selectOptions"
bg-color="surface"
class="setting-select"
density="compact" density="compact"
hide-details hide-details
:disabled="disabled"
class="setting-select"
variant="outlined"
bg-color="surface"
@update:model-value="updateSetting"
item-title="title" item-title="title"
item-value="value" item-value="value"
variant="outlined"
@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"
icon="mdi-minus" icon="mdi-minus"
size="small" size="small"
variant="text" variant="text"
:disabled="disabled || localValue <= minValue"
@click="adjustValue(-stepValue)" @click="adjustValue(-stepValue)"
/> />
<v-text-field <v-text-field
v-model.number="localValue" v-model.number="localValue"
type="number" :disabled="disabled"
:max="maxValue"
:min="minValue"
:step="stepValue"
bg-color="surface"
class="mx-2 setting-number-field"
density="compact" density="compact"
hide-details hide-details
:min="minValue"
:max="maxValue"
:step="stepValue"
:disabled="disabled"
class="mx-2 setting-number-field"
style="width: 80px" style="width: 80px"
type="number"
variant="outlined" variant="outlined"
bg-color="surface"
@update:model-value="updateSetting" @update:model-value="updateSetting"
/> />
<v-btn <v-btn
:disabled="disabled || localValue >= maxValue"
icon="mdi-plus" icon="mdi-plus"
size="small" size="small"
variant="text" variant="text"
:disabled="disabled || localValue >= maxValue"
@click="adjustValue(stepValue)" @click="adjustValue(stepValue)"
/> />
</div> </div>
@ -78,12 +78,12 @@
<v-menu location="bottom"> <v-menu location="bottom">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn <v-btn
:disabled="disabled"
class="ml-2"
icon="mdi-dots-vertical" icon="mdi-dots-vertical"
size="small" size="small"
variant="text"
v-bind="props" v-bind="props"
class="ml-2" variant="text"
:disabled="disabled"
/> />
</template> </template>
<v-list density="compact"> <v-list density="compact">
@ -103,7 +103,7 @@
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item @click="resetToDefault" :disabled="isDefaultValue"> <v-list-item :disabled="isDefaultValue" @click="resetToDefault">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon icon="mdi-restore" size="small"/> <v-icon icon="mdi-restore" size="small"/>
</template> </template>
@ -119,12 +119,12 @@
<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"
bg-color="surface"
class="setting-text-field mt-1"
density="compact" density="compact"
hide-details hide-details
:disabled="disabled"
class="setting-text-field mt-1"
variant="outlined" variant="outlined"
bg-color="surface"
@update:model-value="updateSetting" @update:model-value="updateSetting"
/> />
</div> </div>

View File

@ -3,18 +3,19 @@
<div> <div>
<v-text-field v-model="searchQuery" label="搜索设置" prepend-inner-icon="mdi-magnify" clearable variant="outlined" <v-text-field v-model="searchQuery" class="mb-4" clearable density="comfortable" label="搜索设置"
density="comfortable" class="mb-4" /> prepend-inner-icon="mdi-magnify" variant="outlined"/>
<v-list> <v-list>
<div v-for="setting in allSettings" :key="setting.key"> <div v-for="setting in allSettings" :key="setting.key">
<setting-item :key="setting.key" :setting-key="setting.key" <setting-item :key="setting.key" :disabled="setting.requireDeveloper && !isDeveloperMode"
:disabled="setting.requireDeveloper && !isDeveloperMode" @update="onSettingUpdate" @error="onSettingError" /> :setting-key="setting.key" @error="onSettingError"
@update="onSettingUpdate"/>
<v-divider class="my-2"/> <v-divider class="my-2"/>
</div> </div>
</v-list><v-card border> </v-list>
<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>

View File

@ -1,29 +1,29 @@
<template> <template>
<v-card <v-card
border
:color="unsavedChanges ? 'warning-subtle' : undefined"
:class="{ 'unsaved-changes': unsavedChanges }" :class="{ 'unsaved-changes': unsavedChanges }"
:color="unsavedChanges ? 'warning-subtle' : undefined"
border
> >
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-account-group" size="large" class="mr-2" /> <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"
class="mr-2"
prepend-icon="mdi-sort-alphabetical-variant" prepend-icon="mdi-sort-alphabetical-variant"
variant="text" variant="text"
class="mr-2"
@click="sortStudentsByPinyin" @click="sortStudentsByPinyin"
:disabled="modelValue.list.length === 0"
> >
按姓名首字母排序 按姓名首字母排序
</v-btn> </v-btn>
<v-btn <v-btn
:color="modelValue.advanced ? 'primary' : undefined" :color="modelValue.advanced ? 'primary' : undefined"
variant="text"
prepend-icon="mdi-code-braces" prepend-icon="mdi-code-braces"
variant="text"
@click="toggleAdvanced" @click="toggleAdvanced"
> >
{{ modelValue.advanced ? "返回基础编辑" : "高级编辑" }} {{ modelValue.advanced ? "返回基础编辑" : "高级编辑" }}
@ -34,12 +34,12 @@
<v-card-text> <v-card-text>
<v-progress-linear <v-progress-linear
v-if="loading" v-if="loading"
indeterminate
color="primary"
class="mb-4" class="mb-4"
color="primary"
indeterminate
/> />
<v-alert v-if="error" type="error" variant="tonal" closable class="mb-4"> <v-alert v-if="error" class="mb-4" closable type="error" variant="tonal">
{{ error }} {{ error }}
</v-alert> </v-alert>
@ -47,23 +47,23 @@
<!-- 普通编辑模式 --> <!-- 普通编辑模式 -->
<div v-if="!modelValue.advanced"> <div v-if="!modelValue.advanced">
<v-row class="mb-6"> <v-row class="mb-6">
<v-col cols="12" sm="6" md="4"> <v-col cols="12" md="4" sm="6">
<v-text-field <v-text-field
v-model="newStudentName" v-model="newStudentName"
class="mb-4"
hide-details
label="添加学生" label="添加学生"
placeholder="输入学生姓名后回车添加" placeholder="输入学生姓名后回车添加"
prepend-inner-icon="mdi-account-plus" prepend-inner-icon="mdi-account-plus"
variant="outlined" variant="outlined"
hide-details
class="mb-4"
@keyup.enter="addStudent" @keyup.enter="addStudent"
> >
<template #append> <template #append>
<v-btn <v-btn
:disabled="!newStudentName.trim()"
color="primary"
icon="mdi-plus" icon="mdi-plus"
variant="text" variant="text"
color="primary"
:disabled="!newStudentName.trim()"
@click="addStudent" @click="addStudent"
/> />
</template> </template>
@ -76,25 +76,25 @@
v-for="(student, index) in modelValue.list" v-for="(student, index) in modelValue.list"
:key="index" :key="index"
cols="12" cols="12"
sm="6"
md="4"
lg="3" lg="3"
md="4"
sm="6"
> >
<v-hover v-slot="{ isHovering, props }"> <v-hover v-slot="{ isHovering, props }">
<v-card <v-card
v-bind="props"
:elevation="isMobile ? 1 : isHovering ? 4 : 1" :elevation="isMobile ? 1 : isHovering ? 4 : 1"
class="student-card"
border border
class="student-card"
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 location="bottom" :open-on-hover="!isMobile"> <v-menu :open-on-hover="!isMobile" location="bottom">
<template #activator="{ props: menuProps }"> <template #activator="{ props: menuProps }">
<v-btn <v-btn
variant="tonal"
size="small"
class="mr-3 font-weight-medium" class="mr-3 font-weight-medium"
size="small"
v-bind="menuProps" v-bind="menuProps"
variant="tonal"
> >
{{ index + 1 }} {{ index + 1 }}
</v-btn> </v-btn>
@ -102,23 +102,23 @@
<v-list density="compact" nav> <v-list density="compact" nav>
<v-list-item <v-list-item
prepend-icon="mdi-arrow-up-bold"
:disabled="index === 0" :disabled="index === 0"
prepend-icon="mdi-arrow-up-bold"
@click="moveStudent(index, 'top')" @click="moveStudent(index, 'top')"
> >
置顶 置顶
</v-list-item> </v-list-item>
<v-divider/> <v-divider/>
<v-list-item <v-list-item
prepend-icon="mdi-arrow-up"
:disabled="index === 0" :disabled="index === 0"
prepend-icon="mdi-arrow-up"
@click="moveStudent(index, 'up')" @click="moveStudent(index, 'up')"
> >
上移 上移
</v-list-item> </v-list-item>
<v-list-item <v-list-item
prepend-icon="mdi-arrow-down"
:disabled="index === modelValue.list.length - 1" :disabled="index === modelValue.list.length - 1"
prepend-icon="mdi-arrow-down"
@click="moveStudent(index, 'down')" @click="moveStudent(index, 'down')"
> >
下移 下移
@ -129,13 +129,13 @@
<v-text-field <v-text-field
v-if="editState.index === index" v-if="editState.index === index"
v-model="editState.name" v-model="editState.name"
density="compact"
variant="underlined"
hide-details
class="flex-grow-1"
autofocus autofocus
@keyup.enter="saveEdit" class="flex-grow-1"
density="compact"
hide-details
variant="underlined"
@blur="saveEdit" @blur="saveEdit"
@keyup.enter="saveEdit"
/> />
<span <span
v-else v-else
@ -146,21 +146,21 @@
</span> </span>
<div <div
class="d-flex gap-1 action-buttons"
:class="{ 'opacity-100': isHovering || isMobile }" :class="{ 'opacity-100': isHovering || isMobile }"
class="d-flex gap-1 action-buttons"
> >
<v-btn <v-btn
icon="mdi-pencil"
variant="text"
color="primary" color="primary"
icon="mdi-pencil"
size="small" size="small"
variant="text"
@click="startEdit(index, student)" @click="startEdit(index, student)"
/> />
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click="removeStudent(index)" @click="removeStudent(index)"
/> />
</div> </div>
@ -175,36 +175,36 @@
<div v-else class="pt-2"> <div v-else class="pt-2">
<v-textarea <v-textarea
v-model="modelValue.text" v-model="modelValue.text"
label="批量编辑学生列表"
placeholder="每行输入一个学生姓名"
hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行" hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行"
label="批量编辑学生列表"
persistent-hint persistent-hint
variant="outlined" placeholder="每行输入一个学生姓名"
rows="10" rows="10"
variant="outlined"
@update:model-value="handleTextInput" @update:model-value="handleTextInput"
/> />
</div> </div>
</v-expand-transition> </v-expand-transition>
<v-row class="mt-6"> <v-row class="mt-6">
<v-col cols="12" class="d-flex gap-2"> <v-col class="d-flex gap-2" cols="12">
<v-btn <v-btn
:disabled="loading"
:loading="loading"
color="primary" color="primary"
prepend-icon="mdi-content-save" prepend-icon="mdi-content-save"
size="large" size="large"
:loading="loading"
:disabled="loading"
@click="saveStudents" @click="saveStudents"
> >
保存名单 保存名单
</v-btn> </v-btn>
<v-btn <v-btn
:disabled="loading"
:loading="loading"
color="error" color="error"
variant="outlined"
prepend-icon="mdi-refresh" prepend-icon="mdi-refresh"
size="large" size="large"
:loading="loading" variant="outlined"
:disabled="loading"
@click="loadStudents" @click="loadStudents"
> >
重载名单 重载名单
@ -301,7 +301,7 @@ export default {
this.modelValue.text = this.modelValue.list.map(s => s.name).join("\n"); this.modelValue.text = this.modelValue.list.map(s => s.name).join("\n");
this.lastSavedData = JSON.parse(JSON.stringify(this.modelValue.list)); this.lastSavedData = JSON.parse(JSON.stringify(this.modelValue.list));
this.unsavedChanges = false; this.unsavedChanges = false;
return;
} }
} catch (error) { } catch (error) {
console.warn( console.warn(

View File

@ -1,7 +1,7 @@
<template> <template>
<v-card class="my-4" :loading="loading" :disabled="!hasNamespaceInfo"> <v-card :disabled="!hasNamespaceInfo" :loading="loading" class="my-4">
<template #loader> <template #loader>
<v-progress-linear v-if="loading" indeterminate color="primary" /> <v-progress-linear v-if="loading" color="primary" indeterminate/>
</template> </template>
@ -21,18 +21,18 @@
class="mb-4" class="mb-4"
> >
<v-alert <v-alert
border
type="warning" type="warning"
variant="tonal" variant="tonal"
border
> >
<v-alert-title>设备未绑定账号</v-alert-title> <v-alert-title>设备未绑定账号</v-alert-title>
<div>当前设备尚未绑定账号,部分功能可能受限请前往绑定账号以获得完整体验</div> <div>当前设备尚未绑定账号,部分功能可能受限请前往绑定账号以获得完整体验</div>
<v-btn <v-btn
class="mt-3"
variant="outlined"
:href="getBindAccountUrl()" :href="getBindAccountUrl()"
append-icon="mdi-open-in-new" append-icon="mdi-open-in-new"
class="mt-3"
target="_blank" target="_blank"
variant="outlined"
> >
前往绑定账号 前往绑定账号
</v-btn> </v-btn>
@ -45,13 +45,13 @@
class="d-flex align-center mb-4" class="d-flex align-center mb-4"
> >
<v-card <v-card
border
hover
class="w-100"
variant="tonal"
:prepend-avatar="namespaceInfo.account.avatarUrl" :prepend-avatar="namespaceInfo.account.avatarUrl"
:title="namespaceInfo.account.name || '未命名用户'"
:subtitle="'此设备由贵校管理 管理员账号 ID: ' + namespaceInfo.account.id" :subtitle="'此设备由贵校管理 管理员账号 ID: ' + namespaceInfo.account.id"
:title="namespaceInfo.account.name || '未命名用户'"
border
class="w-100"
hover
variant="tonal"
> >
<v-card-text> <v-card-text>
此设备由贵校或贵单位管理该管理员系此空间所有者如有疑问请咨询他对于恶意绑定滥用行为请反馈 此设备由贵校或贵单位管理该管理员系此空间所有者如有疑问请咨询他对于恶意绑定滥用行为请反馈
@ -62,10 +62,10 @@
<!-- 设备信息卡片 --> <!-- 设备信息卡片 -->
<v-card <v-card
v-if="namespaceInfo.device" v-if="namespaceInfo.device"
variant="tonal"
class="mb-4"
border border
class="mb-4"
hover hover
variant="tonal"
> >
<v-card-title class="pb-1"> <v-card-title class="pb-1">
设备信息 设备信息
@ -74,8 +74,8 @@
<div class="d-flex flex-column gap-1"> <div class="d-flex flex-column gap-1">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon <v-icon
size="small"
class="me-2" class="me-2"
size="small"
> >
mdi-tag mdi-tag
</v-icon> </v-icon>
@ -84,8 +84,8 @@
</div> </div>
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon <v-icon
size="small"
class="me-2" class="me-2"
size="small"
> >
mdi-identifier mdi-identifier
</v-icon> </v-icon>
@ -98,8 +98,8 @@
class="d-flex align-center" class="d-flex align-center"
> >
<v-icon <v-icon
size="small"
class="me-2" class="me-2"
size="small"
> >
mdi-uuid mdi-uuid
</v-icon> </v-icon>
@ -108,8 +108,8 @@
</div> </div>
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon <v-icon
size="small"
class="me-2" class="me-2"
size="small"
> >
mdi-calendar mdi-calendar
</v-icon> </v-icon>
@ -121,8 +121,8 @@
class="d-flex align-center" class="d-flex align-center"
> >
<v-icon <v-icon
size="small"
class="me-2" class="me-2"
size="small"
> >
mdi-calendar-clock mdi-calendar-clock
</v-icon> </v-icon>
@ -134,13 +134,14 @@
</v-card> </v-card>
<v-card <v-card
title="Classworks KV"
subtitle="文档形键值数据库"
border border
hover hover
subtitle="文档形键值数据库"
title="Classworks KV"
> >
<v-card-text> <v-card-text>
Classworks KV 是厚浪云推出的文档形键值数据库其是一个开放的云应用平台为各种应用提供存储服务此设备正在使用其服务如果您希望管理设备信息请前往 Classworks KV 的网站如果您在服务推出前就在使用 Classworks您的数据已被自动迁移 Classworks KV 是厚浪云推出的文档形键值数据库其是一个开放的云应用平台为各种应用提供存储服务此设备正在使用其服务如果您希望管理设备信息请前往
Classworks KV 的网站如果您在服务推出前就在使用 Classworks您的数据已被自动迁移
<br><br> <br><br>
Classworks KV 的全域管理员是 Classworks KV 的全域管理员是
<a <a
@ -153,8 +154,8 @@
<v-card-actions> <v-card-actions>
<v-btn <v-btn
:href="defaultAuthServer" :href="defaultAuthServer"
class="text-none"
append-icon="mdi-open-in-new" append-icon="mdi-open-in-new"
class="text-none"
target="_blank" target="_blank"
> >
前往 Classworks KV 前往 Classworks KV
@ -176,9 +177,9 @@
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer/>
<v-btn <v-btn
:loading="loading"
color="primary" color="primary"
variant="outlined" variant="outlined"
:loading="loading"
@click="reloadInfo" @click="reloadInfo"
> >
刷新设备信息 刷新设备信息
@ -202,9 +203,9 @@
<v-card-title>确认重新初始化</v-card-title> <v-card-title>确认重新初始化</v-card-title>
<v-card-text> <v-card-text>
<v-alert <v-alert
class="mb-3"
type="warning" type="warning"
variant="tonal" variant="tonal"
class="mb-3"
> >
<v-alert-title>警告</v-alert-title> <v-alert-title>警告</v-alert-title>
此操作将清除当前的云端存储配置包括 Token您需要重新进行授权 此操作将清除当前的云端存储配置包括 Token您需要重新进行授权

View File

@ -1,5 +1,5 @@
<template> <template>
<settings-card title="数据源设置" icon="mdi-database-cog"> <settings-card icon="mdi-database-cog" title="数据源设置">
<v-list> <v-list>
<!-- 服务器模式设置 --> <!-- 服务器模式设置 -->
<template <template
@ -10,7 +10,7 @@
> >
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-lan-connect" class="mr-3" /> <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>
@ -21,7 +21,8 @@
> >
测试连接 测试连接
</v-btn> </v-btn>
</template> </v-list-item </template>
</v-list-item
><!-- 数据迁移仅对KV本地存储有效 --> ><!-- 数据迁移仅对KV本地存储有效 -->
</template> </template>
@ -29,11 +30,12 @@
<template v-if="currentProvider === 'kv-local'"> <template v-if="currentProvider === 'kv-local'">
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-database" class="mr-3" /> <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">
@ -43,7 +45,7 @@
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-database-export" class="mr-3" /> <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>
@ -53,35 +55,38 @@
</template> </template>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-database-import" class="mr-3" /> <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存储</v-list-item-subtitle >将旧的存储格式数据转移到新的KV存储
</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> </v-list-item </template>
</v-list-item
><!-- 显示机器ID --> ><!-- 显示机器ID -->
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-identifier" class="mr-3" /> <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 icon="mdi-lan-connect" class="mr-3" /> <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 variant="tonal" to="/cachemanagement"> 查看 </v-btn> <v-btn to="/cachemanagement" variant="tonal"> 查看</v-btn>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>
@ -94,10 +99,12 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" variant="text" @click="confirmDialog = false" <v-btn color="grey" variant="text" @click="confirmDialog = false"
>取消</v-btn >取消
</v-btn
> >
<v-btn color="error" variant="tonal" @click="handleConfirm" <v-btn color="error" variant="tonal" @click="handleConfirm"
>确认</v-btn >确认
</v-btn
> >
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@ -1,5 +1,5 @@
<template> <template>
<settings-card title="显示设置" icon="mdi-monitor" border> <settings-card border icon="mdi-monitor" title="显示设置">
<v-list> <v-list>
<setting-item :setting-key="'display.emptySubjectDisplay'"/> <setting-item :setting-key="'display.emptySubjectDisplay'"/>
@ -35,14 +35,13 @@
<script> <script>
import SettingsCard from '@/components/SettingsCard.vue'; import SettingsCard from '@/components/SettingsCard.vue';
import SettingItem from '@/components/settings/SettingItem.vue'; import SettingItem from '@/components/settings/SettingItem.vue';
export default { export default {
name: 'DisplaySettingsCard', name: 'DisplaySettingsCard',
components: {SettingsCard, SettingItem}, components: {SettingsCard, SettingItem},
data() { data() {
return { return {};
};
}, },

View File

@ -1,8 +1,8 @@
<template> <template>
<settings-card <settings-card
border border
title="回声洞"
icon="mdi-thought-bubble" icon="mdi-thought-bubble"
title="回声洞"
@click="handleClick" @click="handleClick"
> >
<v-card-text> <v-card-text>
@ -107,6 +107,11 @@ export default {
font-size: 0.9em; font-size: 0.9em;
} }
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; } .fade-enter-active, .fade-leave-active {
.fade-enter-from, .fade-leave-to { opacity: 0; } transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<settings-card title="编辑设置" icon="mdi-cog"> <settings-card icon="mdi-cog" title="编辑设置">
<v-list> <v-list>
<setting-item :setting-key="'edit.autoSave'"/> <setting-item :setting-key="'edit.autoSave'"/>
@ -12,7 +12,6 @@
<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'"/>

View File

@ -1,17 +1,17 @@
<template> <template>
<settings-card <settings-card
title="作业模板配置"
icon="mdi-book-edit"
:loading="loading" :loading="loading"
border border
icon="mdi-book-edit"
title="作业模板配置"
> >
<!-- 顶部操作按钮 --> <!-- 顶部操作按钮 -->
<v-alert <v-alert
v-if="error" v-if="error"
class="mb-4"
closable
type="error" type="error"
variant="tonal" variant="tonal"
closable
class="mb-4"
> >
{{ error }} {{ error }}
</v-alert> </v-alert>
@ -19,20 +19,20 @@
<div class="d-flex justify-space-between align-center mb-6"> <div class="d-flex justify-space-between align-center mb-6">
<div> <div>
<v-btn <v-btn
color="primary"
size="large"
prepend-icon="mdi-refresh"
:loading="loading" :loading="loading"
@click="loadConfig"
class="mr-2" class="mr-2"
color="primary"
prepend-icon="mdi-refresh"
size="large"
@click="loadConfig"
> >
重新加载配置 重新加载配置
</v-btn> </v-btn>
<v-btn <v-btn
color="success"
size="large"
prepend-icon="mdi-content-save"
:loading="loading" :loading="loading"
color="success"
prepend-icon="mdi-content-save"
size="large"
@click="saveConfig" @click="saveConfig"
> >
保存所有更改 保存所有更改
@ -49,15 +49,15 @@
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<setting-group title="科目配置" icon="mdi-book" border> <setting-group border icon="mdi-book" title="科目配置">
<v-list> <v-list>
<v-list-item> <v-list-item>
<v-text-field <v-text-field
v-model="newSubject" v-model="newSubject"
append-inner-icon="mdi-plus"
density="comfortable"
label="添加新科目" label="添加新科目"
variant="outlined" variant="outlined"
density="comfortable"
append-inner-icon="mdi-plus"
@click:append-inner="addSubject" @click:append-inner="addSubject"
@keyup.enter="addSubject" @keyup.enter="addSubject"
/> />
@ -70,32 +70,32 @@
v-model="editedSubjects[subject]" v-model="editedSubjects[subject]"
:placeholder="subject" :placeholder="subject"
density="comfortable" density="comfortable"
variant="plain"
hide-details hide-details
variant="plain"
@blur="updateSubject(subject)" @blur="updateSubject(subject)"
/> />
<v-spacer/> <v-spacer/>
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click="deleteSubject(subject)" @click="deleteSubject(subject)"
/> />
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-text-field <v-text-field
v-model="newBookTypes[subject]" v-model="newBookTypes[subject]"
append-inner-icon="mdi-plus"
class="mb-2"
density="comfortable"
label="添加作业本名称" label="添加作业本名称"
variant="outlined" variant="outlined"
density="comfortable"
class="mb-2"
append-inner-icon="mdi-plus"
@click:append-inner="() => addBookType(subject)" @click:append-inner="() => addBookType(subject)"
@keyup.enter="() => addBookType(subject)" @keyup.enter="() => addBookType(subject)"
/> />
<v-list density="compact" border 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"
@ -103,21 +103,21 @@
@click="openSubjectBookDialog(subject, bookType, books)" @click="openSubjectBookDialog(subject, bookType, books)"
> >
<template v-slot:prepend> <template v-slot:prepend>
<v-icon icon="mdi-book-open-variant" class="mr-2" /> <v-icon class="mr-2" icon="mdi-book-open-variant"/>
</template> </template>
<template v-slot:append> <template v-slot:append>
<v-chip <v-chip
size="small"
class="mr-2" class="mr-2"
color="info" color="info"
size="small"
> >
{{ books.length }}个部分 {{ books.length }}个部分
</v-chip> </v-chip>
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click.stop="() => deleteBookType(subject, bookType)" @click.stop="() => deleteBookType(subject, bookType)"
/> />
</template> </template>
@ -131,22 +131,22 @@
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<setting-group title="通用配置" icon="mdi-cog" border> <setting-group border icon="mdi-cog" title="通用配置">
<v-list> <v-list>
<v-list-item> <v-list-item>
<v-text-field <v-text-field
v-model="newCommonBook" v-model="newCommonBook"
append-inner-icon="mdi-plus"
density="comfortable"
label="添加作业本名称" label="添加作业本名称"
variant="outlined" variant="outlined"
density="comfortable"
append-inner-icon="mdi-plus"
@click:append-inner="addCommonBook" @click:append-inner="addCommonBook"
@keyup.enter="addCommonBook" @keyup.enter="addCommonBook"
/> />
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<v-list density="compact" border 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"
@ -154,21 +154,21 @@
@click="openSubjectBookDialog('common', bookType, books)" @click="openSubjectBookDialog('common', bookType, books)"
> >
<template v-slot:prepend> <template v-slot:prepend>
<v-icon icon="mdi-book-multiple" class="mr-2" /> <v-icon class="mr-2" icon="mdi-book-multiple"/>
</template> </template>
<template v-slot:append> <template v-slot:append>
<v-chip <v-chip
size="small"
class="mr-2" class="mr-2"
color="info" color="info"
size="small"
> >
{{ books.length }}个部分 {{ books.length }}个部分
</v-chip> </v-chip>
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click.stop="() => deleteBookType('common', bookType)" @click.stop="() => deleteBookType('common', bookType)"
/> />
</template> </template>
@ -181,17 +181,17 @@
<v-list-item> <v-list-item>
<v-text-field <v-text-field
v-model="newAction" v-model="newAction"
append-inner-icon="mdi-plus"
density="comfortable"
label="添加操作" label="添加操作"
variant="outlined" variant="outlined"
density="comfortable"
append-inner-icon="mdi-plus"
@click:append-inner="addAction" @click:append-inner="addAction"
@keyup.enter="addAction" @keyup.enter="addAction"
/> />
</v-list-item> </v-list-item>
<v-list-item> <v-list-item>
<v-list density="compact" border 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"
@ -200,10 +200,10 @@
> >
<template v-slot:append> <template v-slot:append>
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click.stop="removeAction(action)" @click.stop="removeAction(action)"
/> />
</template> </template>
@ -229,41 +229,41 @@
<v-text-field <v-text-field
v-model="dialog.editedItem.name" v-model="dialog.editedItem.name"
:label="dialog.nameLabel" :label="dialog.nameLabel"
variant="outlined"
density="comfortable"
:rules="[v => !!v || '名称不能为空']" :rules="[v => !!v || '名称不能为空']"
density="comfortable"
variant="outlined"
/> />
</v-col> </v-col>
<v-col cols="12" v-if="dialog.editedItem.type === 'subjectBook'"> <v-col v-if="dialog.editedItem.type === 'subjectBook'" cols="12">
<div class="text-subtitle-2 mb-2">所属科目</div> <div class="text-subtitle-2 mb-2">所属科目</div>
<v-chip color="primary">{{ dialog.editedItem.subject }}</v-chip> <v-chip color="primary">{{ dialog.editedItem.subject }}</v-chip>
</v-col> </v-col>
<v-col cols="12" v-if="['subjectBook', 'commonBook'].includes(dialog.editedItem.type)"> <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 density="compact" border rounded class="mb-2"> <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 v-slot:prepend>
<v-icon icon="mdi-checkbox-blank-circle-outline" size="small" class="mr-2" /> <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]"
variant="plain"
density="compact" density="compact"
hide-details hide-details
variant="plain"
/> />
<template v-slot:append> <template v-slot:append>
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click="removeTask(index)" @click="removeTask(index)"
/> />
</template> </template>
@ -271,13 +271,13 @@
</v-list> </v-list>
<v-text-field <v-text-field
v-model="newTask" v-model="newTask"
append-inner-icon="mdi-plus"
class="mt-2"
density="comfortable"
label="添加需完成部分" label="添加需完成部分"
variant="outlined" variant="outlined"
density="comfortable"
append-inner-icon="mdi-plus"
@click:append-inner="addTask" @click:append-inner="addTask"
@keyup.enter="addTask" @keyup.enter="addTask"
class="mt-2"
/> />
</v-card-text> </v-card-text>
</v-card> </v-card>

View File

@ -1,15 +1,15 @@
<template> <template>
<settings-card title="KV数据库管理" icon="mdi-database-edit" :loading="loading"> <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 :icon="connectionIcon" :color="connectionColor" 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 variant="tonal" @click="refreshConnection" :loading="loading"> <v-btn :loading="loading" variant="tonal" @click="refreshConnection">
刷新 刷新
</v-btn> </v-btn>
</template> </template>
@ -20,17 +20,17 @@
<!-- 数据列表 --> <!-- 数据列表 -->
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-format-list-bulleted" class="mr-3" /> <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 @click="loadKvData" :loading="loadingData"> <v-btn :loading="loadingData" @click="loadKvData">
加载数据 加载数据
</v-btn> </v-btn>
<v-btn @click="createNewItem" :disabled="!isKvProvider"> <v-btn :disabled="!isKvProvider" @click="createNewItem">
<v-icon icon="mdi-plus" class="mr-1" /> <v-icon class="mr-1" icon="mdi-plus"/>
新建 新建
</v-btn> </v-btn>
</v-btn-group> </v-btn-group>
@ -41,60 +41,60 @@
<!-- 数据表格 --> <!-- 数据表格 -->
<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 icon="mdi-table" class="mr-2" /> <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"
label="搜索键名" clearable
prepend-inner-icon="mdi-magnify"
variant="outlined"
density="compact" density="compact"
hide-details hide-details
clearable label="搜索键名"
prepend-inner-icon="mdi-magnify"
style="max-width: 300px;" style="max-width: 300px;"
variant="outlined"
/> />
</v-card-title> </v-card-title>
<v-data-table <v-data-table
:headers="tableHeaders" :headers="tableHeaders"
:items="filteredKvData" :items="filteredKvData"
:loading="loadingData"
item-value="key"
class="elevation-0"
:items-per-page="10" :items-per-page="10"
:loading="loadingData"
class="elevation-0"
item-value="key"
> >
<template #[`item.key`]="{ item }"> <template #[`item.key`]="{ item }">
<code class="text-primary">{{ item.key }}</code> <code class="text-primary">{{ item.key }}</code>
</template> </template>
<template #[`item.actions`]="{ item }"> <template #[`item.actions`]="{ item }">
<v-btn-group variant="text" density="compact"> <v-btn-group density="compact" variant="text">
<v-btn <v-btn
icon="mdi-eye" icon="mdi-eye"
size="small" size="small"
@click="viewItem(item)"
title="查看" title="查看"
@click="viewItem(item)"
/> />
<v-btn <v-btn
icon="mdi-pencil" icon="mdi-pencil"
size="small" size="small"
@click="editItem(item)"
title="编辑" title="编辑"
@click="editItem(item)"
/> />
<v-btn <v-btn
color="primary"
icon="mdi-cloud-download" icon="mdi-cloud-download"
size="small" size="small"
color="primary"
@click="getCloudUrl(item)"
title="获取云端地址" title="获取云端地址"
@click="getCloudUrl(item)"
/> />
<v-btn <v-btn
color="error"
icon="mdi-delete" icon="mdi-delete"
size="small" size="small"
color="error"
@click="confirmDelete(item)"
title="删除" title="删除"
@click="confirmDelete(item)"
/> />
</v-btn-group> </v-btn-group>
</template> </template>
@ -105,7 +105,7 @@
<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 icon="mdi-eye" class="mr-2" /> <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"/>
@ -119,21 +119,21 @@
<v-textarea <v-textarea
v-if="selectedItem" v-if="selectedItem"
:model-value="formatJsonData(selectedItem.value)" :model-value="formatJsonData(selectedItem.value)"
class="font-monospace"
label="数据内容" label="数据内容"
variant="outlined"
readonly readonly
rows="15" rows="15"
class="font-monospace" variant="outlined"
/> />
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer/>
<v-btn @click="copyToClipboard(selectedItem?.value)" variant="tonal"> <v-btn variant="tonal" @click="copyToClipboard(selectedItem?.value)">
<v-icon icon="mdi-content-copy" class="mr-1" /> <v-icon class="mr-1" icon="mdi-content-copy"/>
复制数据 复制数据
</v-btn> </v-btn>
<v-btn @click="viewDialog = false" variant="text"> <v-btn variant="text" @click="viewDialog = false">
关闭 关闭
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -144,7 +144,7 @@
<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 icon="mdi-pencil" class="mr-2" /> <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"/>
@ -157,26 +157,26 @@
<v-card-text> <v-card-text>
<v-textarea <v-textarea
v-model="editingData" v-model="editingData"
label="数据内容 (JSON格式)"
variant="outlined"
rows="15"
class="font-monospace"
:error="!isValidJson" :error="!isValidJson"
:error-messages="isValidJson ? [] : ['请输入有效的JSON格式']" :error-messages="isValidJson ? [] : ['请输入有效的JSON格式']"
class="font-monospace"
label="数据内容 (JSON格式)"
rows="15"
variant="outlined"
/> />
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer/>
<v-btn @click="closeEditDialog" variant="text"> <v-btn variant="text" @click="closeEditDialog">
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
@click="saveEditedData"
variant="tonal"
color="primary"
:disabled="!isValidJson" :disabled="!isValidJson"
:loading="savingData" :loading="savingData"
color="primary"
variant="tonal"
@click="saveEditedData"
> >
保存 保存
</v-btn> </v-btn>
@ -188,7 +188,7 @@
<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 icon="mdi-plus" class="mr-2" /> <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"/>
@ -197,37 +197,37 @@
<v-card-text> <v-card-text>
<v-text-field <v-text-field
v-model="newKey" v-model="newKey"
label="键名"
variant="outlined"
class="mb-4"
:error="!isValidKey" :error="!isValidKey"
:error-messages="isValidKey ? [] : ['键名不能为空且不能与现有键重复']" :error-messages="isValidKey ? [] : ['键名不能为空且不能与现有键重复']"
class="mb-4"
label="键名"
placeholder="请输入键名my-config" placeholder="请输入键名my-config"
variant="outlined"
/> />
<v-textarea <v-textarea
v-model="newData" v-model="newData"
label="数据内容 (JSON格式)"
variant="outlined"
rows="15"
class="font-monospace"
:error="!isValidNewJson" :error="!isValidNewJson"
:error-messages="isValidNewJson ? [] : ['请输入有效的JSON格式']" :error-messages="isValidNewJson ? [] : ['请输入有效的JSON格式']"
class="font-monospace"
label="数据内容 (JSON格式)"
placeholder='请输入JSON数据{"name": "value"}' placeholder='请输入JSON数据{"name": "value"}'
rows="15"
variant="outlined"
/> />
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer/>
<v-btn @click="closeCreateDialog" variant="text"> <v-btn variant="text" @click="closeCreateDialog">
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
@click="saveNewData"
variant="tonal"
color="primary"
:disabled="!isValidKey || !isValidNewJson" :disabled="!isValidKey || !isValidNewJson"
:loading="savingData" :loading="savingData"
color="primary"
variant="tonal"
@click="saveNewData"
> >
创建 创建
</v-btn> </v-btn>
@ -239,7 +239,7 @@
<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 icon="mdi-cloud-download" class="mr-2" /> <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"/>
@ -250,19 +250,19 @@
</v-card-subtitle> </v-card-subtitle>
<v-card-text> <v-card-text>
<v-alert v-if="cloudUrlError" type="error" variant="tonal" class="mb-4"> <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" type="success" variant="tonal" class="mb-4"> <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-if="cloudUrlResult.migrated" class="mb-2">
<v-icon icon="mdi-database-arrow-up" class="mr-1" color="success" /> <v-icon class="mr-1" color="success" icon="mdi-database-arrow-up"/>
数据已从本地迁移到云端 数据已从本地迁移到云端
</div> </div>
<div v-if="cloudUrlResult.configured" class="mb-2"> <div v-if="cloudUrlResult.configured" class="mb-2">
<v-icon icon="mdi-cog" class="mr-1" color="info" /> <v-icon class="mr-1" color="info" icon="mdi-cog"/>
云端配置已自动设置 云端配置已自动设置
</div> </div>
</div> </div>
@ -271,39 +271,39 @@
<v-text-field <v-text-field
v-if="cloudUrlResult && cloudUrlResult.url" v-if="cloudUrlResult && cloudUrlResult.url"
:model-value="cloudUrlResult.url" :model-value="cloudUrlResult.url"
label="云端访问地址"
variant="outlined"
readonly
class="font-monospace"
append-inner-icon="mdi-content-copy" append-inner-icon="mdi-content-copy"
class="font-monospace"
label="云端访问地址"
readonly
variant="outlined"
@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 icon="mdi-cog" class="mr-2" /> <v-icon class="mr-2" icon="mdi-cog"/>
高级选项 高级选项
</v-expansion-panel-title> </v-expansion-panel-title>
<v-expansion-panel-text> <v-expansion-panel-text>
<v-checkbox <v-checkbox
v-model="cloudUrlOptions.migrateFromLocal" v-model="cloudUrlOptions.migrateFromLocal"
label="从本地迁移数据到云端"
density="compact" density="compact"
label="从本地迁移数据到云端"
/> />
<v-checkbox <v-checkbox
v-model="cloudUrlOptions.autoConfigureCloud" v-model="cloudUrlOptions.autoConfigureCloud"
label="自动配置云端默认设置"
density="compact" density="compact"
label="自动配置云端默认设置"
/> />
<v-btn <v-btn
@click="refreshCloudUrl"
variant="tonal"
color="primary"
:loading="gettingCloudUrl" :loading="gettingCloudUrl"
class="mt-2" class="mt-2"
color="primary"
variant="tonal"
@click="refreshCloudUrl"
> >
<v-icon icon="mdi-refresh" class="mr-1" /> <v-icon class="mr-1" icon="mdi-refresh"/>
重新获取 重新获取
</v-btn> </v-btn>
</v-expansion-panel-text> </v-expansion-panel-text>
@ -313,16 +313,16 @@
<v-card-actions> <v-card-actions>
<v-spacer/> <v-spacer/>
<v-btn @click="cloudUrlDialog = false" variant="text"> <v-btn variant="text" @click="cloudUrlDialog = false">
关闭 关闭
</v-btn> </v-btn>
<v-btn <v-btn
v-if="cloudUrlResult && cloudUrlResult.url" v-if="cloudUrlResult && cloudUrlResult.url"
@click="openCloudUrl"
variant="tonal"
color="primary" color="primary"
variant="tonal"
@click="openCloudUrl"
> >
<v-icon icon="mdi-open-in-new" class="mr-1" /> <v-icon class="mr-1" icon="mdi-open-in-new"/>
在新窗口打开 在新窗口打开
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -333,28 +333,28 @@
<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 icon="mdi-alert" class="mr-2" /> <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 type="warning" variant="tonal" class="mt-2"> <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 @click="deleteDialog = false" variant="text"> <v-btn variant="text" @click="deleteDialog = false">
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
@click="deleteItem"
variant="tonal"
color="error"
:loading="deletingData" :loading="deletingData"
color="error"
variant="tonal"
@click="deleteItem"
> >
删除 删除
</v-btn> </v-btn>
@ -537,11 +537,6 @@ export default {
}, },
async viewItem(item) { async viewItem(item) {
this.selectedItem = item; this.selectedItem = item;
this.viewDialog = true; this.viewDialog = true;

View File

@ -1,5 +1,5 @@
<template> <template>
<settings-card title="编辑设置" icon="mdi-cog"> <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"/>
@ -17,8 +17,6 @@
<setting-item setting-key="randomPicker.animation"/> <setting-item setting-key="randomPicker.animation"/>
</v-list> </v-list>
</settings-card> </settings-card>
</template> </template>

View File

@ -1,8 +1,9 @@
<template> <template>
<settings-card title="刷新设置" icon="mdi-refresh-circle"> <settings-card icon="mdi-refresh-circle" title="刷新设置">
<v-form> <v-form>
<v-list> <v-list>
<setting-item setting-key="refresh.auto" title="自动刷新" /> <v-divider class="my-2" /> <setting-item 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>
@ -14,15 +15,14 @@
<script> <script>
import SettingsCard from '@/components/SettingsCard.vue'; import SettingsCard from '@/components/SettingsCard.vue';
import SettingItem from '@/components/settings/SettingItem.vue'; import SettingItem from '@/components/settings/SettingItem.vue';
export default { export default {
name: 'RefreshSettingsCard', name: 'RefreshSettingsCard',
components: {SettingsCard}, components: {SettingsCard},
data() { data() {
return { return {};
};
}, },
}; };
</script> </script>

View File

@ -1,8 +1,8 @@
<template> <template>
<settings-card <settings-card
title="数据源设置"
icon="mdi-database"
:loading="loading" :loading="loading"
icon="mdi-database"
title="数据源设置"
> >
<v-form> <v-form>
<!-- 使用双向绑定来替代 setting-key --> <!-- 使用双向绑定来替代 setting-key -->
@ -13,20 +13,20 @@
{ title: 'KV本地存储', value: 'kv-local' }, { title: 'KV本地存储', value: 'kv-local' },
{ title: 'KV远程服务器', value: 'kv-server' } { title: 'KV远程服务器', value: 'kv-server' }
]" ]"
label="数据提供者" class="mb-3"
variant="outlined"
density="comfortable" density="comfortable"
item-title="title" item-title="title"
item-value="value" item-value="value"
label="数据提供者"
prepend-icon="mdi-database" prepend-icon="mdi-database"
class="mb-3" variant="outlined"
/> />
<v-alert <v-alert
v-if="isKvProvider" v-if="isKvProvider"
class="my-2"
type="info" type="info"
variant="tonal" variant="tonal"
class="my-2"
> >
<v-alert-title>KV 存储系统</v-alert-title> <v-alert-title>KV 存储系统</v-alert-title>
<p>KV存储系统使用本机唯一标识符(UUID)来区分不同设备的数据</p> <p>KV存储系统使用本机唯一标识符(UUID)来区分不同设备的数据</p>
@ -38,10 +38,10 @@
<v-alert <v-alert
v-if="isClassworksCloud" v-if="isClassworksCloud"
type="info"
color="success"
variant="tonal"
class="my-2" class="my-2"
color="success"
type="info"
variant="tonal"
> >
<v-alert-title>Classworks云端存储</v-alert-title> <v-alert-title>Classworks云端存储</v-alert-title>
<p>Classworks云端存储是官方提供的存储解决方案自动配置了最优的访问设置</p> <p>Classworks云端存储是官方提供的存储解决方案自动配置了最优的访问设置</p>
@ -56,17 +56,16 @@
<div v-if="isClassworksCloud"> <div v-if="isClassworksCloud">
<v-text-field <v-text-field
v-model="serverSettings.kvToken" v-model="serverSettings.kvToken"
label="KV 授权令牌"
variant="outlined"
density="comfortable"
prepend-icon="mdi-shield-key"
class="mb-2" class="mb-2"
density="comfortable"
hint="令牌用于云端存储授权" hint="令牌用于云端存储授权"
label="KV 授权令牌"
persistent-hint persistent-hint
prepend-icon="mdi-shield-key"
variant="outlined"
/> />
<cloud-namespace-info-card <cloud-namespace-info-card
:visible="isClassworksCloud" :visible="isClassworksCloud"
class="mt-4" class="mt-4"
@ -77,24 +76,24 @@
<div v-else-if="currentProvider === 'kv-server'"> <div v-else-if="currentProvider === 'kv-server'">
<v-text-field <v-text-field
v-model="serverSettings.domain" v-model="serverSettings.domain"
label="服务器域名"
variant="outlined"
density="comfortable"
prepend-icon="mdi-web"
class="mb-2" class="mb-2"
density="comfortable"
hint="例如: https://example.com (不需要路径)" hint="例如: https://example.com (不需要路径)"
label="服务器域名"
persistent-hint persistent-hint
prepend-icon="mdi-web"
variant="outlined"
/> />
<v-text-field <v-text-field
v-model="serverSettings.kvToken" v-model="serverSettings.kvToken"
label="KV 授权令牌"
variant="outlined"
density="comfortable"
prepend-icon="mdi-shield-key"
class="mb-2" class="mb-2"
density="comfortable"
hint="令牌用于服务器验证" hint="令牌用于服务器验证"
label="KV 授权令牌"
persistent-hint persistent-hint
prepend-icon="mdi-shield-key"
variant="outlined"
/> />
</div> </div>
@ -102,13 +101,13 @@
<div v-else-if="currentProvider === 'kv-local'"> <div v-else-if="currentProvider === 'kv-local'">
<v-text-field <v-text-field
v-model="serverSettings.classNumber" v-model="serverSettings.classNumber"
label="班级编号"
variant="outlined"
density="comfortable"
prepend-icon="mdi-account-group"
class="mb-2" class="mb-2"
density="comfortable"
hint="例如: 高三八班" hint="例如: 高三八班"
label="班级编号"
persistent-hint persistent-hint
prepend-icon="mdi-account-group"
variant="outlined"
/> />
</div> </div>
</v-form> </v-form>

View File

@ -1,16 +1,16 @@
<template> <template>
<settings-card <settings-card
title="科目管理"
icon="mdi-book-multiple"
:loading="loading" :loading="loading"
border border
icon="mdi-book-multiple"
title="科目管理"
> >
<v-alert <v-alert
v-if="error" v-if="error"
class="mb-4"
closable
type="error" type="error"
variant="tonal" variant="tonal"
closable
class="mb-4"
> >
{{ error }} {{ error }}
</v-alert> </v-alert>
@ -18,32 +18,32 @@
<div class="d-flex justify-space-between align-center mb-6"> <div class="d-flex justify-space-between align-center mb-6">
<div> <div>
<v-btn <v-btn
variant="text"
color="primary"
size="large"
prepend-icon="mdi-refresh"
:loading="loading" :loading="loading"
@click="loadConfig"
class="mr-2" class="mr-2"
color="primary"
prepend-icon="mdi-refresh"
size="large"
variant="text"
@click="loadConfig"
> >
重新加载 重新加载
</v-btn> </v-btn>
<v-btn <v-btn
color="success"
size="large"
prepend-icon="mdi-content-save"
:loading="loading" :loading="loading"
color="success"
prepend-icon="mdi-content-save"
size="large"
@click="saveConfig" @click="saveConfig"
> >
保存 保存
</v-btn> </v-btn>
<v-btn <v-btn
variant="text"
prepend-icon="mdi-restore"
:loading="loading" :loading="loading"
@click="resetToDefault"
class="mr-2" class="mr-2"
prepend-icon="mdi-restore"
variant="text"
@click="resetToDefault"
> >
重置为默认 重置为默认
</v-btn> </v-btn>
@ -64,12 +64,12 @@
<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 || '科目名称不能为空']"
append-inner-icon="mdi-plus"
density="comfortable"
label="科目名称" label="科目名称"
variant="outlined" variant="outlined"
density="comfortable"
:rules="[v => !!v || '科目名称不能为空']"
@keyup.enter="addSubject" @keyup.enter="addSubject"
append-inner-icon="mdi-plus"
@click:append-inner="addSubject" @click:append-inner="addSubject"
/> />
</v-col> </v-col>
@ -88,17 +88,17 @@
<template v-slot:prepend> <template v-slot: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
icon="mdi-chevron-up"
variant="text"
size="small"
:disabled="index === 0" :disabled="index === 0"
icon="mdi-chevron-up"
size="small"
variant="text"
@click="moveSubject(index, -1)" @click="moveSubject(index, -1)"
/> />
<v-btn <v-btn
icon="mdi-chevron-down"
variant="text"
size="small"
:disabled="index === subjects.length - 1" :disabled="index === subjects.length - 1"
icon="mdi-chevron-down"
size="small"
variant="text"
@click="moveSubject(index, 1)" @click="moveSubject(index, 1)"
/> />
</div> </div>
@ -107,19 +107,19 @@
<v-list-item-title> <v-list-item-title>
<v-text-field <v-text-field
v-model="subject.name" v-model="subject.name"
variant="plain"
density="compact" density="compact"
hide-details hide-details
variant="plain"
@blur="updateSubject(subject)" @blur="updateSubject(subject)"
/> />
</v-list-item-title> </v-list-item-title>
<template v-slot:append> <template v-slot:append>
<v-btn <v-btn
icon="mdi-delete"
variant="text"
color="error" color="error"
icon="mdi-delete"
size="small" size="small"
variant="text"
@click="deleteSubject(subject)" @click="deleteSubject(subject)"
/> />
</template> </template>
@ -291,6 +291,7 @@ export default {
.v-list-item { .v-list-item {
border-bottom: 1px solid rgba(0, 0, 0, 0.12); border-bottom: 1px solid rgba(0, 0, 0, 0.12);
} }
.v-list-item:last-child { .v-list-item:last-child {
border-bottom: none; border-bottom: none;
} }

View File

@ -1,24 +1,24 @@
<template> <template>
<settings-card title="主题设置" icon="mdi-palette"> <settings-card icon="mdi-palette" title="主题设置">
<v-list> <v-list>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-theme-light-dark" class="mr-3" /> <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>
<template #append> <template #append>
<v-btn-toggle <v-btn-toggle
v-model="localTheme" v-model="localTheme"
density="comfortable"
color="primary" color="primary"
density="comfortable"
> >
<v-btn value="light"> <v-btn value="light">
<v-icon icon="mdi-white-balance-sunny" class="mr-2" /> <v-icon class="mr-2" icon="mdi-white-balance-sunny"/>
明亮 明亮
</v-btn> </v-btn>
<v-btn value="dark"> <v-btn value="dark">
<v-icon icon="mdi-moon-waning-crescent" class="mr-2" /> <v-icon class="mr-2" icon="mdi-moon-waning-crescent"/>
暗黑 暗黑
</v-btn> </v-btn>
</v-btn-toggle> </v-btn-toggle>

View File

@ -1,5 +1,7 @@
# Layouts # Layouts
Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across multiple pages. Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across
multiple pages.
Full documentation for this feature can be found in the Official [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) repository. Full documentation for this feature can be found in the
Official [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) repository.

View File

@ -7,6 +7,7 @@
// Plugins // Plugins
import {registerPlugins} from '@/plugins' import {registerPlugins} from '@/plugins'
import {createPinia} from 'pinia' import {createPinia} from 'pinia'
const pinia = createPinia() const pinia = createPinia()
// Components // Components
@ -16,6 +17,7 @@ import GlobalMessage from '@/components/GlobalMessage.vue'
// Composables // Composables
import {createApp} from 'vue' import {createApp} from 'vue'
import Clarity from '@microsoft/clarity'; import Clarity from '@microsoft/clarity';
const projectId = "rhp8uqoc3l" const projectId = "rhp8uqoc3l"
//import TDesign from 'tdesign-vue-next' //import TDesign from 'tdesign-vue-next'
//import 'tdesign-vue-next/es/style/index.css' //import 'tdesign-vue-next/es/style/index.css'

View File

@ -3,16 +3,16 @@
<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 size="x-large" color="primary" class="mr-3">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 ">缓存管理</h1>
<div class="text-subtitle-1 text-grey">管理应用的本地缓存资源</div> <div class="text-subtitle-1 text-grey">管理应用的本地缓存资源</div>
</div> </div>
</div> </div>
<v-card class="mb-6" variant="tonal" color="info" density="compact"> <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 color="info" class="mr-2">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>
@ -22,10 +22,11 @@
<v-card class="mb-4" variant="tonal"> <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 color="primary" class="mr-2">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>
@ -34,7 +35,7 @@
<v-card class="mb-4" variant="tonal"> <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 color="warning" class="mr-2">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">

View File

@ -4,9 +4,9 @@
<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 <v-icon
size="x-large"
color="primary"
class="mr-3" class="mr-3"
color="primary"
size="x-large"
> >
mdi-database-sync mdi-database-sync
</v-icon> </v-icon>
@ -22,13 +22,13 @@
<v-card <v-card
class="mb-6" class="mb-6"
variant="tonal"
color="info" color="info"
variant="tonal"
> >
<v-card-text class="d-flex align-center"> <v-card-text class="d-flex align-center">
<v-icon <v-icon
color="info"
class="mr-2" class="mr-2"
color="info"
> >
mdi-information-outline mdi-information-outline
</v-icon> </v-icon>
@ -51,9 +51,9 @@
<v-card> <v-card>
<v-card-title class="text-h5 d-flex align-center"> <v-card-title class="text-h5 d-flex align-center">
<v-icon <v-icon
class="mr-3"
color="primary" color="primary"
size="large" size="large"
class="mr-3"
> >
mdi-database-sync mdi-database-sync
</v-icon> </v-icon>
@ -66,10 +66,10 @@
</p> </p>
<v-alert <v-alert
color="info"
variant="tonal"
class="mt-4" class="mt-4"
color="info"
icon="mdi-information-outline" icon="mdi-information-outline"
variant="tonal"
> >
<ul class="ml-3 mt-1"> <ul class="ml-3 mt-1">
<li>数据源: {{ dataSourceText }}</li> <li>数据源: {{ dataSourceText }}</li>
@ -92,16 +92,16 @@
稍后再说 稍后再说
</v-btn> </v-btn>
<v-btn <v-btn
:disabled="isAutoMigrating"
:loading="isAutoMigrating"
color="primary" color="primary"
size="large" size="large"
variant="elevated" variant="elevated"
:loading="isAutoMigrating"
:disabled="isAutoMigrating"
@click="startAutoMigration" @click="startAutoMigration"
> >
<v-icon <v-icon
left
class="mr-2" class="mr-2"
left
> >
mdi-database-export mdi-database-export
</v-icon> </v-icon>

View File

@ -2,4 +2,5 @@
Vue components created in this folder will automatically be converted to navigatable routes. Vue components created in this folder will automatically be converted to navigatable routes.
Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository. Full documentation for this feature can be found in the
Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.

View File

@ -1,7 +1,7 @@
<template> <template>
<v-container class="fill-height" fluid> <v-container class="fill-height" fluid>
<v-row align="center" justify="center"> <v-row align="center" justify="center">
<v-col cols="12" sm="8" md="6"> <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' ? '授权成功' : '授权失败' }}
@ -9,9 +9,9 @@
<v-card-text> <v-card-text>
<v-progress-linear <v-progress-linear
v-if="status === 'processing'" v-if="status === 'processing'"
indeterminate
color="primary"
class="mb-4" class="mb-4"
color="primary"
indeterminate
></v-progress-linear> ></v-progress-linear>
<p>{{ message }}</p> <p>{{ message }}</p>
</v-card-text> </v-card-text>

View File

@ -45,12 +45,19 @@
<!-- 输入方式选择 --> <!-- 输入方式选择 -->
<v-tabs v-model="activeTab" class="mb-4 mx-2" color="primary" rounded> <v-tabs v-model="activeTab" 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 value="text" class="px-5">
<v-tab value="file" class="px-5"><v-icon start>mdi-file-upload</v-icon> 文件上传</v-tab> <v-icon start>mdi-text-box</v-icon>
文本粘贴
</v-tab>
<v-tab value="file" class="px-5">
<v-icon start>mdi-file-upload</v-icon>
文件上传
</v-tab>
</v-tabs> </v-tabs>
<!-- 格式选择 --> <!-- 格式选择 -->
<v-btn-toggle v-model="formatMode" color="primary" class="mb-4 mx-2" mandatory density="comfortable" border rounded> <v-btn-toggle v-model="formatMode" color="primary" class="mb-4 mx-2" mandatory density="comfortable" border
rounded>
<v-btn value="auto">自动检测</v-btn> <v-btn value="auto">自动检测</v-btn>
<v-btn value="json">JSON</v-btn> <v-btn value="json">JSON</v-btn>
<v-btn value="yaml" :disabled="!yamlLibLoaded"> <v-btn value="yaml" :disabled="!yamlLibLoaded">
@ -329,7 +336,8 @@
</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 align-tabs="center"> <v-tabs v-if="daysWithSchedule.length > 0" 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"> <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
@ -368,10 +376,12 @@
</td> </td>
<td> <td>
<div v-for="(item, i) in group.items" :key="i" class="mb-1"> <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"> <v-chip size="small" :color="getSubjectColor(item.subject)" label text-color="white"
class="mr-1">
{{ item.subject }} {{ item.subject }}
</v-chip> </v-chip>
<v-chip v-if="group.items.length > 1" size="x-small" class="ml-1" :color="item.weekType === '单' ? 'warning' : 'success'"> <v-chip v-if="group.items.length > 1" size="x-small" class="ml-1"
:color="item.weekType === '单' ? 'warning' : 'success'">
{{ item.weekType }} {{ item.weekType }}
</v-chip> </v-chip>
</div> </div>

View File

@ -29,15 +29,15 @@
<v-divider class="my-4"/> <v-divider class="my-4"/>
<v-btn <v-btn
color="primary"
class="me-2" class="me-2"
color="primary"
@click="applySettings" @click="applySettings"
> >
应用设置 应用设置
</v-btn> </v-btn>
<v-btn <v-btn
color="secondary"
class="me-2" class="me-2"
color="secondary"
@click="clearGuard" @click="clearGuard"
> >
清除重定向守卫 清除重定向守卫
@ -104,7 +104,11 @@ const applySettings = () => {
} }
const clearGuard = () => { const clearGuard = () => {
try { sessionStorage.removeItem(REDIRECT_GUARD_KEY) } catch (e) { console.debug(e) } try {
sessionStorage.removeItem(REDIRECT_GUARD_KEY)
} catch (e) {
console.debug(e)
}
} }
const simulateLoadError = () => { const simulateLoadError = () => {
@ -117,7 +121,11 @@ const simulateLoadError = () => {
} }
const guardRaw = computed(() => { const guardRaw = computed(() => {
try { return sessionStorage.getItem(REDIRECT_GUARD_KEY) } catch (e) { return String(e) } try {
return sessionStorage.getItem(REDIRECT_GUARD_KEY)
} catch (e) {
return String(e)
}
}) })
const settingsDump = computed(() => { const settingsDump = computed(() => {

View File

@ -25,8 +25,8 @@
<v-list-item-subtitle> <v-list-item-subtitle>
<v-chip <v-chip
:color="connected ? 'success' : 'error'" :color="connected ? 'success' : 'error'"
size="small"
class="mr-2" class="mr-2"
size="small"
> >
{{ connected ? 'connected' : 'disconnected' }} {{ connected ? 'connected' : 'disconnected' }}
</v-chip> </v-chip>
@ -50,26 +50,26 @@
> >
<v-text-field <v-text-field
v-model="manualToken" v-model="manualToken"
label="手动加入 Token (留空使用配置的 Token)"
clearable clearable
label="手动加入 Token (留空使用配置的 Token)"
/> />
</v-col> </v-col>
<v-col <v-col
class="d-flex align-center"
cols="12" cols="12"
md="4" md="4"
class="d-flex align-center"
> >
<v-btn <v-btn
color="primary"
class="mr-2" class="mr-2"
color="primary"
@click="handleJoinToken(manualToken || currentToken)" @click="handleJoinToken(manualToken || currentToken)"
> >
加入 加入
</v-btn> </v-btn>
<v-btn <v-btn
color="warning"
class="mr-2"
:disabled="!joinedToken" :disabled="!joinedToken"
class="mr-2"
color="warning"
@click="handleLeaveToken(joinedToken)" @click="handleLeaveToken(joinedToken)"
> >
离开当前 离开当前
@ -86,21 +86,21 @@
<v-divider class="my-4"/> <v-divider class="my-4"/>
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-card variant="tonal" color="primary" border> <v-card border color="primary" variant="tonal">
<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>
<v-textarea <v-textarea
v-model="chatInput" v-model="chatInput"
label="发送到当前已加入的设备频道"
rows="2"
auto-grow auto-grow
clearable clearable
label="发送到当前已加入的设备频道"
rows="2"
/> />
<div class="d-flex"> <div class="d-flex">
<v-spacer/> <v-spacer/>
<v-btn <v-btn
color="primary"
:disabled="!canSendChat" :disabled="!canSendChat"
color="primary"
@click="sendChat" @click="sendChat"
> >
发送聊天 发送聊天
@ -128,8 +128,8 @@
<v-card-title>在线设备</v-card-title> <v-card-title>在线设备</v-card-title>
<v-card-text> <v-card-text>
<v-btn <v-btn
color="primary"
class="mb-3" class="mb-3"
color="primary"
@click="fetchOnline" @click="fetchOnline"
> >
刷新在线列表 刷新在线列表
@ -180,9 +180,9 @@
事件日志 事件日志
<v-spacer/> <v-spacer/>
<v-btn <v-btn
color="error"
size="small" size="small"
variant="text" variant="text"
color="error"
@click="clearLogs" @click="clearLogs"
> >
清空 清空

View File

@ -13,10 +13,10 @@
</v-app-bar-title> </v-app-bar-title>
<v-spacer/> <v-spacer/>
<v-btn <v-btn
color="success"
variant="outlined"
prepend-icon="mdi-content-save"
:loading="saving" :loading="saving"
color="success"
prepend-icon="mdi-content-save"
variant="outlined"
@click="save" @click="save"
> >
保存 保存
@ -31,8 +31,8 @@
v-if="id" v-if="id"
ref="editor" ref="editor"
:config-id="id" :config-id="id"
@saved="onSaved"
@error="onError" @error="onError"
@saved="onSaved"
/> />
</v-container> </v-container>
</v-container> </v-container>

View File

@ -1,11 +1,11 @@
<template> <template>
<v-alert <v-alert
v-if="error" v-if="error"
type="error"
variant="tonal"
border="start" border="start"
class="mb-4" class="mb-4"
closable closable
type="error"
variant="tonal"
@click:close="error = ''" @click:close="error = ''"
> >
{{ error }} {{ error }}
@ -14,17 +14,17 @@
<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 type="warning" variant="tonal" border="start"> <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 class="player" ref="playerRef"> <div ref="playerRef" class="player">
<ExamPlayer <ExamPlayer
v-model:room-number="roomNumberLocal" v-model:room-number="roomNumberLocal"
:exam-config="config"
:config="playerConfigObj" :config="playerConfigObj"
:exam-config="config"
:show-action-bar="true" :show-action-bar="true"
:time-sync-status="'电脑时间'" :time-sync-status="'电脑时间'"
@exit="exit()" @exit="exit()"

View File

@ -2,9 +2,9 @@
<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 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 color="white" class="mr-2">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>
@ -14,11 +14,11 @@
<!-- 错误提示 --> <!-- 错误提示 -->
<v-alert <v-alert
v-if="error" v-if="error"
type="error"
class="mb-4 mt-3 mx-2"
variant="tonal"
border="start" border="start"
class="mb-4 mt-3 mx-2"
closable closable
type="error"
variant="tonal"
@click:close="error = ''" @click:close="error = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
@ -30,11 +30,11 @@
<!-- 成功提示 --> <!-- 成功提示 -->
<v-alert <v-alert
v-if="success" v-if="success"
type="success"
class="mb-4 mt-3 mx-2"
variant="tonal"
border="start" border="start"
class="mb-4 mt-3 mx-2"
closable closable
type="success"
variant="tonal"
@click:close="success = ''" @click:close="success = ''"
> >
<div class="d-flex align-center"> <div class="d-flex align-center">
@ -47,19 +47,19 @@
<div class="d-flex justify-space-between align-center mb-4"> <div class="d-flex justify-space-between align-center mb-4">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-btn <v-btn
class="mr-2"
color="primary" color="primary"
prepend-icon="mdi-plus" prepend-icon="mdi-plus"
class="mr-2"
@click="createNewConfig" @click="createNewConfig"
> >
新建配置 新建配置
</v-btn> </v-btn>
<v-btn <v-btn
:loading="loading"
color="info" color="info"
prepend-icon="mdi-refresh" prepend-icon="mdi-refresh"
:loading="loading"
@click="loadConfigs"
variant="outlined" variant="outlined"
@click="loadConfigs"
> >
刷新 刷新
</v-btn> </v-btn>
@ -77,8 +77,8 @@
<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="list-item-avatar-two-line@3"
class="mx-auto" class="mx-auto"
type="list-item-avatar-two-line@3"
></v-skeleton-loader> ></v-skeleton-loader>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -98,7 +98,7 @@
@click="showEditDialog(config)" @click="showEditDialog(config)"
> >
<template #prepend> <template #prepend>
<v-avatar color="primary" class="mr-2"> <v-avatar class="mr-2" color="primary">
<v-icon color="white">mdi-calendar-text</v-icon> <v-icon color="white">mdi-calendar-text</v-icon>
</v-avatar> </v-avatar>
</template> </template>
@ -108,11 +108,11 @@
</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 size="small" class="mr-1">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 size="small" class="mr-1">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>
@ -120,22 +120,22 @@
<template #append> <template #append>
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-btn <v-btn
class="mr-1"
color="primary"
icon="mdi-pencil" icon="mdi-pencil"
size="small" size="small"
color="primary"
variant="text" variant="text"
class="mr-1"
@click="showEditDialog(config)" @click="showEditDialog(config)"
> >
<v-icon>mdi-pencil</v-icon> <v-icon>mdi-pencil</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn
class="mr-1"
color="info"
icon="mdi-eye" icon="mdi-eye"
size="small" size="small"
color="info"
variant="text" variant="text"
class="mr-1"
@click="showEditDialog(config)" @click="showEditDialog(config)"
> >
<v-icon>mdi-eye</v-icon> <v-icon>mdi-eye</v-icon>
@ -151,7 +151,7 @@
<!-- 空状态 --> <!-- 空状态 -->
<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 size="64" color="grey-lighten-1" class="mb-4"> <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>
@ -176,16 +176,16 @@
<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 color="primary" class="mr-2">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>
<v-text-field <v-text-field
v-model="newConfigName" v-model="newConfigName"
:rules="[v => !!v || '配置名称不能为空']"
label="配置名称" label="配置名称"
prepend-inner-icon="mdi-calendar-text" prepend-inner-icon="mdi-calendar-text"
variant="outlined" variant="outlined"
:rules="[v => !!v || '配置名称不能为空']"
@keyup.enter="renameConfig" @keyup.enter="renameConfig"
></v-text-field> ></v-text-field>
</v-card-text> </v-card-text>
@ -199,11 +199,11 @@
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn
:disabled="!newConfigName"
:loading="renaming"
color="primary" color="primary"
variant="outlined" variant="outlined"
:loading="renaming"
@click="renameConfig" @click="renameConfig"
:disabled="!newConfigName"
> >
确认 确认
</v-btn> </v-btn>
@ -215,22 +215,22 @@
<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 color="white" class="mr-2">mdi-pencil</v-icon> <v-icon class="mr-2" color="white">mdi-pencil</v-icon>
编辑考试配置 编辑考试配置
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-chip <v-chip
v-if="editingConfig" v-if="editingConfig"
color="white"
text-color="primary"
size="small"
class="mr-2" class="mr-2"
color="white"
size="small"
text-color="primary"
> >
ID: {{ editingConfig.id }} ID: {{ editingConfig.id }}
</v-chip> </v-chip>
<v-btn <v-btn
icon="mdi-close"
color="white" color="white"
icon="mdi-close"
variant="text" variant="text"
@click="closeEditDialog" @click="closeEditDialog"
> >
@ -241,30 +241,30 @@
style="max-height: 70vh; overflow-y: auto;"> style="max-height: 70vh; overflow-y: auto;">
<ExamConfigEditor <ExamConfigEditor
v-if="editingConfig" v-if="editingConfig"
:config-id="editingConfig.id"
ref="configEditor" ref="configEditor"
:config-id="editingConfig.id"
:dialog-mode="true" :dialog-mode="true"
@saved="onConfigSaved" @deleted="onConfigDeleted"
@error="onConfigError" @error="onConfigError"
@opened="onConfigOpened" @opened="onConfigOpened"
@deleted="onConfigDeleted" @saved="onConfigSaved"
/> />
</v-card-text> </v-card-text>
<v-card-actions class="pa-4"> <v-card-actions class="pa-4">
<v-btn <v-btn
color="grey" color="grey"
variant="outlined"
prepend-icon="mdi-close" prepend-icon="mdi-close"
variant="outlined"
@click="closeEditDialog" @click="closeEditDialog"
> >
关闭 关闭
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn <v-btn
color="success"
variant="outlined"
prepend-icon="mdi-content-save"
:loading="saving" :loading="saving"
color="success"
prepend-icon="mdi-content-save"
variant="outlined"
@click="saveConfigInDialog" @click="saveConfigInDialog"
> >
保存配置 保存配置
@ -464,11 +464,6 @@ export default {
}, },
/** /**
* 显示重命名对话框 * 显示重命名对话框
*/ */
@ -534,7 +529,6 @@ export default {
}, },
/** /**
* 在弹框中保存配置 * 在弹框中保存配置
*/ */

View File

@ -10,10 +10,10 @@
<!-- 只读 Token 警告 --> <!-- 只读 Token 警告 -->
<v-chip <v-chip
v-if="tokenDisplayInfo.readonly" v-if="tokenDisplayInfo.readonly"
color="warning"
variant="tonal"
class="mx-2" class="mx-2"
color="warning"
prepend-icon="mdi-lock-alert" prepend-icon="mdi-lock-alert"
variant="tonal"
> >
只读 只读
</v-chip> </v-chip>
@ -21,11 +21,11 @@
<!-- 学生名称显示 chip始终蓝色 --> <!-- 学生名称显示 chip始终蓝色 -->
<v-chip <v-chip
v-if="tokenDisplayInfo.show" v-if="tokenDisplayInfo.show"
color="primary"
variant="tonal"
class="mx-2"
prepend-icon="mdi-account"
:style="{ cursor: tokenDisplayInfo.disabled ? 'default' : 'pointer' }" :style="{ cursor: tokenDisplayInfo.disabled ? 'default' : 'pointer' }"
class="mx-2"
color="primary"
prepend-icon="mdi-account"
variant="tonal"
@click="handleTokenChipClick" @click="handleTokenChipClick"
> >
{{ tokenDisplayInfo.text }} {{ tokenDisplayInfo.text }}
@ -33,10 +33,10 @@
<v-btn icon="mdi-chat" variant="text" @click="isChatOpen = true"/> <v-btn icon="mdi-chat" variant="text" @click="isChatOpen = true"/>
<v-btn <v-btn
icon="mdi-bell"
variant="text"
:badge="unreadCount || undefined" :badge="unreadCount || undefined"
:badge-color="unreadCount ? 'error' : undefined" :badge-color="unreadCount ? 'error' : undefined"
icon="mdi-bell"
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')"/>
@ -65,17 +65,17 @@
<div <div
v-for="item in sortedItems" v-for="item in sortedItems"
:key="item.key" :key="item.key"
class="grid-item"
:style="{ :style="{
'grid-row-end': `span ${item.rowSpan}`, 'grid-row-end': `span ${item.rowSpan}`,
order: item.order, order: item.order,
}" }"
class="grid-item"
> >
<v-card <v-card
border
height="100%"
class="glow-track"
:class="{ 'glow-highlight': highlightedCards[item.key] }" :class="{ 'glow-highlight': highlightedCards[item.key] }"
border
class="glow-track"
height="100%"
@click="!isEditingDisabled && openDialog(item.key)" @click="!isEditingDisabled && openDialog(item.key)"
@mousemove="handleMouseMove" @mousemove="handleMouseMove"
@touchmove="handleTouchMove" @touchmove="handleTouchMove"
@ -116,16 +116,16 @@
<v-card <v-card
v-for="subject in unusedSubjects" v-for="subject in unusedSubjects"
:key="subject.name" :key="subject.name"
:disabled="isEditingDisabled"
border border
class="empty-subject-card" class="empty-subject-card"
:disabled="isEditingDisabled"
@click="openDialog(subject.name)" @click="openDialog(subject.name)"
> >
<v-card-title class="text-subtitle-1"> <v-card-title class="text-subtitle-1">
{{ subject.name }} {{ subject.name }}
</v-card-title> </v-card-title>
<v-card-text class="text-center"> <v-card-text class="text-center">
<v-icon size="small" color="grey"> mdi-plus </v-icon> <v-icon color="grey" size="small"> mdi-plus</v-icon>
<div class="text-caption text-grey">点击添加作业</div> <div class="text-caption text-grey">点击添加作业</div>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -134,10 +134,10 @@
</div> </div>
<v-btn <v-btn
v-if="!state.synced" v-if="!state.synced"
color="error"
size="large"
:loading="loading.upload" :loading="loading.upload"
class="ml-2" class="ml-2"
color="error"
size="large"
@click="manualUpload" @click="manualUpload"
> >
上传 上传
@ -147,31 +147,31 @@
</v-btn> </v-btn>
<v-btn <v-btn
v-if="showRandomPickerButton" v-if="showRandomPickerButton"
append-icon="mdi-dice-multiple"
class="ml-2"
color="amber" color="amber"
prepend-icon="mdi-account-question" prepend-icon="mdi-account-question"
append-icon="mdi-dice-multiple"
size="large" size="large"
class="ml-2"
@click="openRandomPicker" @click="openRandomPicker"
> >
随机点名 随机点名
</v-btn> </v-btn>
<v-btn <v-btn
v-if="showExamScheduleButton" v-if="showExamScheduleButton"
class="ml-2"
color="green" color="green"
prepend-icon="mdi-calendar-check" prepend-icon="mdi-calendar-check"
size="large" size="large"
class="ml-2"
@click="$router.push('/examschedule')" @click="$router.push('/examschedule')"
> >
考试看板 考试看板
</v-btn> </v-btn>
<v-btn <v-btn
v-if="showListCardButton" v-if="showListCardButton"
class="ml-2"
color="primary-darken-1" color="primary-darken-1"
prepend-icon="mdi-list-box" prepend-icon="mdi-list-box"
size="large" size="large"
class="ml-2"
@click="$router.push('/list')" @click="$router.push('/list')"
> >
列表 列表
@ -182,8 +182,8 @@
:prepend-icon=" :prepend-icon="
state.isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen' state.isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'
" "
size="large"
class="ml-2" class="ml-2"
size="large"
@click="toggleFullscreen" @click="toggleFullscreen"
> >
{{ state.isFullscreen ? "退出全屏" : "全屏显示" {{ state.isFullscreen ? "退出全屏" : "全屏显示"
@ -197,7 +197,7 @@
variant="tonal" variant="tonal"
> >
<v-card-title class="text-subtitle-1"> <v-card-title class="text-subtitle-1">
<v-icon start icon="mdi-shield-check" size="small" /> <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">
@ -220,15 +220,16 @@
<!-- 出勤统计区域 --> <!-- 出勤统计区域 -->
<v-col <v-col
v-ripple="{ class: `text-${['primary','secondary','info','success','warning','error'][Math.floor(Math.random()*6)]}` }"
v-if="state.studentList && state.studentList.length" v-if="state.studentList && state.studentList.length"
v-ripple="{ class: `text-${['primary','secondary','info','success','warning','error'][Math.floor(Math.random()*6)]}` }"
class="attendance-area no-select" class="attendance-area no-select"
cols="1" cols="1"
@click="setAttendanceArea()" @click="setAttendanceArea()"
> >
<h1>出勤</h1> <h1>出勤</h1>
<h2> <h2>
<snap style="white-space: nowrap"> 应到 </snap>: <snap style="white-space: nowrap"> 应到</snap>
:
<snap style="white-space: nowrap"> <snap style="white-space: nowrap">
{{ {{
state.studentList.length - state.studentList.length -
@ -237,7 +238,8 @@
</snap> </snap>
</h2> </h2>
<h2> <h2>
<snap style="white-space: nowrap"> 实到 </snap>: <snap style="white-space: nowrap"> 实到</snap>
:
<snap style="white-space: nowrap"> <snap style="white-space: nowrap">
{{ {{
state.studentList.length - state.studentList.length -
@ -248,43 +250,46 @@
</snap> </snap>
</h2> </h2>
<h2> <h2>
<snap style="white-space: nowrap"> 请假 </snap>: <snap style="white-space: nowrap"> 请假</snap>
:
<snap style="white-space: nowrap"> <snap style="white-space: nowrap">
{{ state.boardData.attendance.absent.length }} {{ state.boardData.attendance.absent.length }}
</snap> </snap>
</h2> </h2>
<h3 <h3
class="gray-text"
v-for="(name, index) in state.boardData.attendance.absent" v-for="(name, index) in state.boardData.attendance.absent"
:key="'absent-' + index" :key="'absent-' + index"
class="gray-text"
> >
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span <span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
><span style="white-space: nowrap">{{ name }}</span> ><span style="white-space: nowrap">{{ name }}</span>
</h3> </h3>
<h2> <h2>
<snap style="white-space: nowrap">迟到</snap>: <snap style="white-space: nowrap">迟到</snap>
:
<snap style="white-space: nowrap"> <snap style="white-space: nowrap">
{{ state.boardData.attendance.late.length }} {{ state.boardData.attendance.late.length }}
</snap> </snap>
</h2> </h2>
<h3 <h3
class="gray-text"
v-for="(name, index) in state.boardData.attendance.late" v-for="(name, index) in state.boardData.attendance.late"
:key="'late-' + index" :key="'late-' + index"
class="gray-text"
> >
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span <span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
><span style="white-space: nowrap">{{ name }}</span> ><span style="white-space: nowrap">{{ name }}</span>
</h3> </h3>
<h2> <h2>
<snap style="white-space: nowrap">不参与</snap>: <snap style="white-space: nowrap">不参与</snap>
:
<snap style="white-space: nowrap"> <snap style="white-space: nowrap">
{{ state.boardData.attendance.exclude.length }} {{ state.boardData.attendance.exclude.length }}
</snap> </snap>
</h2> </h2>
<h3 <h3
class="gray-text"
v-for="(name, index) in state.boardData.attendance.exclude" v-for="(name, index) in state.boardData.attendance.exclude"
:key="'exclude-' + index" :key="'exclude-' + index"
class="gray-text"
> >
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span <span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
><span style="white-space: nowrap">{{ name }}</span> ><span style="white-space: nowrap">{{ name }}</span>
@ -294,9 +299,9 @@
<homework-edit-dialog <homework-edit-dialog
v-model="state.dialogVisible" v-model="state.dialogVisible"
:title="state.dialogTitle"
:initial-content="state.textarea"
:auto-save="autoSave" :auto-save="autoSave"
:initial-content="state.textarea"
:title="state.dialogTitle"
@save="handleHomeworkSave" @save="handleHomeworkSave"
/> />
@ -306,16 +311,16 @@
<v-dialog <v-dialog
v-model="state.attendanceDialog" v-model="state.attendanceDialog"
max-width="900"
fullscreen-breakpoint="sm" fullscreen-breakpoint="sm"
max-width="900"
@update:model-value="handleAttendanceDialogClose" @update:model-value="handleAttendanceDialogClose"
> >
<v-card> <v-card>
<v-card-title class="d-flex align-center"> <v-card-title class="d-flex align-center">
<v-icon icon="mdi-account-group" class="mr-2" /> <v-icon class="mr-2" icon="mdi-account-group"/>
出勤状态管理 出勤状态管理
<v-spacer/> <v-spacer/>
<v-chip color="primary" size="small" class="ml-2"> <v-chip class="ml-2" color="primary" size="small">
{{ state.dateString }} {{ state.dateString }}
</v-chip> </v-chip>
</v-card-title> </v-card-title>
@ -326,11 +331,11 @@
<v-col cols="12" md="12"> <v-col cols="12" md="12">
<v-text-field <v-text-field
v-model="attendanceSearch" v-model="attendanceSearch"
prepend-inner-icon="mdi-magnify"
label="搜索学生"
hint="支持筛选姓氏,如输入'孙'可筛选所有姓孙的学生"
variant="outlined"
clearable clearable
hint="支持筛选姓氏,如输入'孙'可筛选所有姓孙的学生"
label="搜索学生"
prepend-inner-icon="mdi-magnify"
variant="outlined"
@update:model-value="handleSearchChange" @update:model-value="handleSearchChange"
/> />
@ -339,10 +344,10 @@
<v-btn <v-btn
v-for="surname in extractedSurnames" v-for="surname in extractedSurnames"
:key="surname.name" :key="surname.name"
:color="attendanceSearch === surname.name ? 'primary' : ''"
:variant=" :variant="
attendanceSearch === surname.name ? 'elevated' : 'text' attendanceSearch === surname.name ? 'elevated' : 'text'
" "
:color="attendanceSearch === surname.name ? 'primary' : ''"
@click=" @click="
attendanceSearch = attendanceSearch =
attendanceSearch === surname.name ? '' : surname.name attendanceSearch === surname.name ? '' : surname.name
@ -359,63 +364,63 @@
<div class="d-flex flex-wrap mb-4 gap-2"> <div class="d-flex flex-wrap mb-4 gap-2">
<div> <div>
<v-chip <v-chip
value="present" :append-icon="
attendanceFilter.includes('present') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('present') ? 'success' : ''" :color="attendanceFilter.includes('present') ? 'success' : ''"
:variant=" :variant="
attendanceFilter.includes('present') ? 'elevated' : 'tonal' attendanceFilter.includes('present') ? 'elevated' : 'tonal'
" "
class="px-2 filter-chip" class="px-2 filter-chip"
@click="toggleFilter('present')"
prepend-icon="mdi-account-check" prepend-icon="mdi-account-check"
:append-icon=" value="present"
attendanceFilter.includes('present') ? 'mdi-check' : '' @click="toggleFilter('present')"
"
> >
到课 到课
</v-chip> </v-chip>
<v-chip <v-chip
value="absent" :append-icon="
attendanceFilter.includes('absent') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('absent') ? 'error' : ''" :color="attendanceFilter.includes('absent') ? 'error' : ''"
:variant=" :variant="
attendanceFilter.includes('absent') ? 'elevated' : 'tonal' attendanceFilter.includes('absent') ? 'elevated' : 'tonal'
" "
class="px-2 filter-chip" class="px-2 filter-chip"
@click="toggleFilter('absent')"
prepend-icon="mdi-account-off" prepend-icon="mdi-account-off"
:append-icon=" value="absent"
attendanceFilter.includes('absent') ? 'mdi-check' : '' @click="toggleFilter('absent')"
"
> >
请假 请假
</v-chip> </v-chip>
<v-chip <v-chip
value="late" :append-icon="
attendanceFilter.includes('late') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('late') ? 'warning' : ''" :color="attendanceFilter.includes('late') ? 'warning' : ''"
:variant=" :variant="
attendanceFilter.includes('late') ? 'elevated' : 'tonal' attendanceFilter.includes('late') ? 'elevated' : 'tonal'
" "
class="px-2 filter-chip" class="px-2 filter-chip"
@click="toggleFilter('late')"
prepend-icon="mdi-clock-alert" prepend-icon="mdi-clock-alert"
:append-icon=" value="late"
attendanceFilter.includes('late') ? 'mdi-check' : '' @click="toggleFilter('late')"
"
> >
迟到 迟到
</v-chip> </v-chip>
<v-chip <v-chip
value="exclude" :append-icon="
attendanceFilter.includes('exclude') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('exclude') ? 'grey' : ''" :color="attendanceFilter.includes('exclude') ? 'grey' : ''"
:variant=" :variant="
attendanceFilter.includes('exclude') ? 'elevated' : 'tonal' attendanceFilter.includes('exclude') ? 'elevated' : 'tonal'
" "
class="px-2 filter-chip" class="px-2 filter-chip"
@click="toggleFilter('exclude')"
prepend-icon="mdi-account-cancel" prepend-icon="mdi-account-cancel"
:append-icon=" value="exclude"
attendanceFilter.includes('exclude') ? 'mdi-check' : '' @click="toggleFilter('exclude')"
"
> >
不参与 不参与
</v-chip> </v-chip>
@ -428,11 +433,11 @@
v-for="student in filteredStudents" v-for="student in filteredStudents"
:key="student" :key="student"
cols="12" cols="12"
sm="6"
md="6"
lg="4" lg="4"
md="6"
sm="6"
> >
<v-card class="student-card" border> <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">
@ -442,12 +447,13 @@
state.studentList.indexOf(student) state.studentList.indexOf(student)
) )
" "
size="24"
class="mr-2" class="mr-2"
size="24"
> >
<v-icon size="small">{{ <v-icon size="small">{{
getStudentStatusIcon(state.studentList.indexOf(student)) getStudentStatusIcon(state.studentList.indexOf(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>
@ -459,11 +465,11 @@
? 'success' ? 'success'
: '' : ''
" "
:title="'设为到课'"
icon="mdi-account-check" icon="mdi-account-check"
size="small" size="small"
variant="text" variant="text"
@click="setPresent(state.studentList.indexOf(student))" @click="setPresent(state.studentList.indexOf(student))"
:title="'设为到课'"
/> />
<v-btn <v-btn
:color=" :color="
@ -471,11 +477,11 @@
? 'error' ? 'error'
: '' : ''
" "
:title="'设为请假'"
icon="mdi-account-off" icon="mdi-account-off"
size="small" size="small"
variant="text" variant="text"
@click="setAbsent(state.studentList.indexOf(student))" @click="setAbsent(state.studentList.indexOf(student))"
:title="'设为请假'"
/> />
<v-btn <v-btn
:color=" :color="
@ -483,11 +489,11 @@
? 'warning' ? 'warning'
: '' : ''
" "
:title="'设为迟到'"
icon="mdi-clock-alert" icon="mdi-clock-alert"
size="small" size="small"
variant="text" variant="text"
@click="setLate(state.studentList.indexOf(student))" @click="setLate(state.studentList.indexOf(student))"
:title="'设为迟到'"
/> />
<v-btn <v-btn
:color=" :color="
@ -495,11 +501,11 @@
? 'grey' ? 'grey'
: '' : ''
" "
:title="'设为不参与'"
icon="mdi-account-cancel" icon="mdi-account-cancel"
size="small" size="small"
variant="text" variant="text"
@click="setExclude(state.studentList.indexOf(student))" @click="setExclude(state.studentList.indexOf(student))"
:title="'设为不参与'"
/> />
</div> </div>
</v-card-text> </v-card-text>
@ -508,7 +514,7 @@
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" md="12"> <v-col cols="12" md="12">
<v-card variant="tonal" color="primary" class="mb-4"> <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>
<v-btn-group> <v-btn-group>
@ -545,7 +551,8 @@
</v-btn-group> </v-btn-group>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col></v-row </v-col>
</v-row
> >
</v-card-text> </v-card-text>
@ -566,12 +573,12 @@
<!-- 添加悬浮工具栏 --> <!-- 添加悬浮工具栏 -->
<floating-toolbar <floating-toolbar
:loading="loading.download"
:unread-count="unreadCount"
:selected-date="state.selectedDateObj"
:is-today="isToday" :is-today="isToday"
@zoom="zoom" :loading="loading.download"
:selected-date="state.selectedDateObj"
:unread-count="unreadCount"
@refresh="downloadData" @refresh="downloadData"
@zoom="zoom"
@open-messages="$refs.messageLog.drawer = true" @open-messages="$refs.messageLog.drawer = true"
@open-settings="$router.push('/settings')" @open-settings="$router.push('/settings')"
@date-select="handleDateSelect" @date-select="handleDateSelect"
@ -605,8 +612,8 @@
<!-- 添加随机点名组件 --> <!-- 添加随机点名组件 -->
<random-picker <random-picker
ref="randomPicker" ref="randomPicker"
:student-list="state.studentList"
:attendance="state.boardData.attendance" :attendance="state.boardData.attendance"
:student-list="state.studentList"
/> />
<!-- 添加URL配置确认对话框 --> <!-- 添加URL配置确认对话框 -->
@ -621,17 +628,18 @@
:key="change.key" :key="change.key"
> >
<template #prepend> <template #prepend>
<v-icon :icon="change.icon" size="small" class="mr-2" /> <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 activator="parent" location="top">{{
change.description || change.key 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 icon="mdi-arrow-right" size="small" class="mx-1" /> <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>
@ -652,8 +660,10 @@
确认应用 确认应用
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-dialog </v-card>
><br /><br /><br /> </v-dialog
>
<br/><br/><br/>
</template> </template>
<script> <script>
@ -1613,7 +1623,8 @@ export default {
.replace({ .replace({
query: {date: formattedDate}, query: {date: formattedDate},
}) })
.catch(() => {}); .catch(() => {
});
// Load both data and subjects in parallel, force clear data when switching dates // Load both data and subjects in parallel, force clear data when switching dates
await Promise.all([this.downloadData(true), this.loadSubjects()]); await Promise.all([this.downloadData(true), this.loadSubjects()]);

View File

@ -1,4 +1,5 @@
<template><v-app-bar elevation="1"> <template>
<v-app-bar elevation="1">
<template #prepend> <template #prepend>
<v-btn <v-btn
icon="mdi-arrow-left" icon="mdi-arrow-left"
@ -6,38 +7,38 @@
@click="$router.push('/')" @click="$router.push('/')"
/> />
</template> </template>
<v-app-bar-title class="text-h6" v-if="list && !isRenaming">{{ list.name }}</v-app-bar-title> <v-app-bar-title v-if="list && !isRenaming" class="text-h6">{{ list.name }}</v-app-bar-title>
<v-app-bar-title class="text-h6" v-else>列表</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
icon
class="mr-2"
to="/list"
border border
class="mr-2"
icon
to="/list"
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<h1 v-if="list && !isRenaming"> <h1 v-if="list && !isRenaming">
{{ list.name }} {{ list.name }}
<v-btn icon size="small" @click="startRenaming" border> <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"
label="列表名称"
hide-details
density="compact"
class="mr-2"
style="min-width: 200px;"
autofocus autofocus
class="mr-2"
density="compact"
hide-details
label="列表名称"
style="min-width: 200px;"
@keyup.enter="saveListName" @keyup.enter="saveListName"
></v-text-field> ></v-text-field>
<v-btn color="primary" size="small" class="mr-2" @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">
@ -50,7 +51,7 @@
</div> </div>
<v-card class="mb-5" border 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/>
@ -98,25 +99,26 @@
<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"
color="error" color="error"
prepend-icon="mdi-delete-sweep" prepend-icon="mdi-delete-sweep"
@click="confirmDeleteCompleted" @click="confirmDeleteCompleted"
:disabled="!hasCompletedItems"
> >
删除已完成项目 删除已完成项目
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card><v-card class="mb-5" border rounded="xl"> </v-card>
<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="newItemName" v-model="newItemName"
label="项目名称"
:rules="[(v) => !!v || '名称不能为空']" :rules="[(v) => !!v || '名称不能为空']"
label="项目名称"
/> />
<v-btn <v-btn
color="primary"
:disabled="!newItemName" :disabled="!newItemName"
color="primary"
@click="addItem" @click="addItem"
> >
添加 添加
@ -124,19 +126,19 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-card class="mb-5" border 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="sortSeed" v-model="sortSeed"
label="排序种子 (任意数字或文本)"
hint="输入相同的种子值可以得到相同的排序结果"
persistent-hint
class="mb-3" class="mb-3"
hint="输入相同的种子值可以得到相同的排序结果"
label="排序种子 (任意数字或文本)"
persistent-hint
/> />
<v-btn <v-btn
color="primary"
class="mr-2" class="mr-2"
color="primary"
@click="randomSort" @click="randomSort"
> >
随机排序 随机排序
@ -179,7 +181,8 @@
<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> <v-list-item-title class="text-subtitle-1 font-weight-bold">{{ itemDialog.item.name }}
</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>
@ -206,25 +209,25 @@
<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"
label="名称" label="名称"
variant="outlined" variant="outlined"
class="mb-3"
></v-text-field> ></v-text-field>
<v-textarea <v-textarea
v-model="itemDialog.editedItem.description" v-model="itemDialog.editedItem.description"
label="描述"
variant="outlined"
rows="3"
class="mb-3" class="mb-3"
label="描述"
rows="3"
variant="outlined"
></v-textarea> ></v-textarea>
<v-switch <v-switch
v-model="itemDialog.editedItem.completed" v-model="itemDialog.editedItem.completed"
label="已完成"
color="success" color="success"
hide-details hide-details
label="已完成"
></v-switch> ></v-switch>
</div> </div>
</v-card-text> </v-card-text>

View File

@ -1,4 +1,5 @@
<template><v-app-bar elevation="1"> <template>
<v-app-bar elevation="1">
<template #prepend> <template #prepend>
<v-btn <v-btn
icon="mdi-arrow-left" icon="mdi-arrow-left"
@ -7,9 +8,8 @@
/> />
</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-container> </v-app-bar>
<v-container>
<v-card border class="mb-5" rounded="xl"> <v-card border class="mb-5" rounded="xl">
@ -21,8 +21,8 @@
<v-list-item <v-list-item
v-for="list in lists" v-for="list in lists"
:key="list.id" :key="list.id"
:to="list.id !== editingListId ? `/list/${list.id}` : undefined"
:active="list.id === editingListId" :active="list.id === editingListId"
:to="list.id !== editingListId ? `/list/${list.id}` : undefined"
> >
<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>
@ -30,27 +30,27 @@
<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"
label="列表名称"
hide-details
density="compact"
class="mr-2"
autofocus autofocus
class="mr-2"
density="compact"
hide-details
label="列表名称"
@keyup.enter="saveListName" @keyup.enter="saveListName"
></v-text-field> ></v-text-field>
<v-btn icon color="primary" @click.stop.prevent="saveListName" class="mr-2" border> <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 icon color="error" @click.stop.prevent="cancelEditing" border> <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 icon @click.stop.prevent="startEditing(list.id)" class="mr-2" border> <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 icon @click.stop.prevent="confirmDeleteList(list.id)" border> <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,15 +58,15 @@
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-card> </v-card>
<v-card class="mb-5" border 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"
label="列表名称"
:rules="[v => !!v || '名称不能为空']" :rules="[v => !!v || '名称不能为空']"
label="列表名称"
></v-text-field> ></v-text-field>
<v-btn color="primary" @click="createNewList" :disabled="!newListName"> <v-btn :disabled="!newListName" color="primary" @click="createNewList">
创建列表 创建列表
</v-btn> </v-btn>
</v-card-text> </v-card-text>
@ -92,6 +92,7 @@
<script> <script>
import dataProvider from "@/utils/dataProvider.js"; import dataProvider from "@/utils/dataProvider.js";
export default { export default {
data() { data() {
return { return {

View File

@ -27,11 +27,11 @@
<v-list-item <v-list-item
v-for="tab in settingsTabs" v-for="tab in settingsTabs"
:key="tab.value" :key="tab.value"
@click="settingsTab = tab.value"
:active="settingsTab === tab.value" :active="settingsTab === tab.value"
:color="settingsTab === tab.value ? 'primary' : 'default'"
:prepend-icon="tab.icon" :prepend-icon="tab.icon"
class="rounded-e-xl" class="rounded-e-xl"
:color="settingsTab === tab.value ? 'primary' : 'default'" @click="settingsTab = tab.value"
> >
<v-list-item-title>{{ tab.title }}</v-list-item-title> <v-list-item-title>{{ tab.title }}</v-list-item-title>
</v-list-item> </v-list-item>
@ -40,11 +40,13 @@
<v-tabs-window <v-tabs-window
v-model="settingsTab" v-model="settingsTab"
style="width: 100%"
direction="vertical" direction="vertical"
style="width: 100%"
> >
<v-tabs-window-item value="index" <v-tabs-window-item value="index"
><v-card class="service-card gradient-right clickable mb-4" elevation="8" rounded="xl" border hover @click="openClassworksKV" color="primary" variant="tonal"> >
<v-card border 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>
@ -58,10 +60,10 @@
<v-card-text> <v-card-text>
<div class="mt-4"> <div class="mt-4">
<v-btn <v-btn
variant="text"
class="text-none"
append-icon="mdi-arrow-right" append-icon="mdi-arrow-right"
class="text-none"
rounded="xl" rounded="xl"
variant="text"
@click="openClassworksKV" @click="openClassworksKV"
> >
打开 Classworks KV 打开 Classworks KV
@ -69,35 +71,39 @@
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-card title="Classworks" subtitle="设置" class="rounded-xl mb-4" border> <v-card border class="rounded-xl mb-4" subtitle="设置" title="Classworks">
<v-card-text> <v-card-text>
<v-alert <v-alert
color="error"
variant="tonal"
icon="mdi-alert-circle"
class="rounded-xl" class="rounded-xl"
color="error"
icon="mdi-alert-circle"
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"
variant="tonal"
icon="mdi-information" icon="mdi-information"
>请不要使用浏览器清除缓存功能否则会导致配置丢失<del variant="tonal"
>恶意的操作可能导致您受到贵校教师的处理</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"
variant="tonal"
icon="mdi-information" icon="mdi-information"
variant="tonal"
><p> ><p>
请不要使用包括但不限于360极速浏览器360安全浏览器夸克浏览器QQ浏览器等浏览器使用 请不要使用包括但不限于360极速浏览器360安全浏览器夸克浏览器QQ浏览器等浏览器使用
Classworks Classworks
@ -108,24 +114,28 @@
上述浏览器商标为其所属公司所有Classworks 上述浏览器商标为其所属公司所有Classworks
与上述浏览器所属公司无竞争关系 与上述浏览器所属公司无竞争关系
</p> </p>
<br /><v-btn <br/>
<v-btn
append-icon="mdi-open-in-new"
class="text-none rounded-xl"
color="warning"
href="https://www.microsoft.com/zh-cn/windows/microsoft-edge" href="https://www.microsoft.com/zh-cn/windows/microsoft-edge"
target="_blank" target="_blank"
color="warning"
variant="tonal" variant="tonal"
class="text-none rounded-xl" >下载 Microsoft Edge微软边缘浏览器
append-icon="mdi-open-in-new" </v-btn
>下载 Microsoft Edge微软边缘浏览器</v-btn >
></v-alert </v-alert
> >
</v-card-text> </v-card-text>
</v-card><about-card /> </v-card>
<about-card/>
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="server"> <v-tabs-window-item value="server">
<server-settings-card <server-settings-card
border
:loading="loading.server" :loading="loading.server"
border
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
<data-provider-settings-card border class="mt-4"/> <data-provider-settings-card border class="mt-4"/>
@ -133,7 +143,7 @@
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="student"> <v-tabs-window-item value="student">
<student-list-card border :is-mobile="isMobile" /> <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"/>
@ -141,54 +151,57 @@
<v-tabs-window-item value="refresh"> <v-tabs-window-item value="refresh">
<refresh-settings-card <refresh-settings-card
border
:loading="loading.refresh" :loading="loading.refresh"
border
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="edit"> <v-tabs-window-item value="edit">
<edit-settings-card <edit-settings-card
border
:loading="loading.edit" :loading="loading.edit"
border
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="display"> <v-tabs-window-item value="display">
<display-settings-card <display-settings-card
border
:loading="loading.display" :loading="loading.display"
border
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="theme"> <v-tabs-window-item value="theme">
<theme-settings-card <theme-settings-card
border
:loading="loading.theme" :loading="loading.theme"
border
@saved="onSettingsSaved" @saved="onSettingsSaved"
/> />
</v-tabs-window-item> </v-tabs-window-item>
<v-tabs-window-item value="randomPicker"> <v-tabs-window-item value="randomPicker">
<random-picker-card border :is-mobile="isMobile" /> <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 /> <br /> <subject-management-card border/>
<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 border title="开发者选项" icon="mdi-developer-board"> >
<settings-card border icon="mdi-developer-board" title="开发者选项">
<v-list> <v-list>
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-code-tags" class="mr-3" /> <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
@ -202,14 +215,14 @@
</v-list> </v-list>
</settings-card> </settings-card>
<developer-settings-card <developer-settings-card
border
:loading="loading.developer" :loading="loading.developer"
border
@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 icon="mdi-cog-outline" class="mr-2" /> <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>
@ -259,6 +272,7 @@ import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
import HomeworkTemplateCard from "@/components/settings/cards/HomeworkTemplateCard.vue"; import HomeworkTemplateCard from "@/components/settings/cards/HomeworkTemplateCard.vue";
import SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue"; import SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue";
import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue"; import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue";
export default { export default {
name: "Settings", name: "Settings",
components: { components: {
@ -642,6 +656,7 @@ export default {
.settings-page { .settings-page {
.v-card { .v-card {
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s, box-shadow 0.2s;
&:hover { &:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
} }

View File

@ -1,3 +1,4 @@
# Plugins # Plugins
Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally. Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you
want to use globally.

View File

@ -1,6 +1,3 @@
// 添加卡片发光效果 // 添加卡片发光效果
.glow-track { .glow-track {
position: relative; position: relative;
@ -140,6 +137,7 @@
transform: scale(0.95); transform: scale(0.95);
} }
} }
.grid-masonry { .grid-masonry {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);

View File

@ -16,11 +16,19 @@
} }
@keyframes pulse-warning { @keyframes pulse-warning {
0%, 100% { transform: scale(1); } 0%, 100% {
50% { transform: scale(1.002); } transform: scale(1);
}
50% {
transform: scale(1.002);
}
} }
@keyframes pulse-border { @keyframes pulse-border {
0%, 100% { opacity: 1; } 0%, 100% {
50% { opacity: 0.5; } opacity: 1;
}
50% {
opacity: 0.5;
}
} }

View File

@ -256,7 +256,6 @@ export default {
}; };
export const ErrorCodes = { export const ErrorCodes = {
NOT_FOUND: "数据不存在", NOT_FOUND: "数据不存在",
NETWORK_ERROR: "网络连接失败", NETWORK_ERROR: "网络连接失败",

View File

@ -87,8 +87,12 @@ export default {
warning: (title, content, options) => createMessage(MessageType.WARNING, title, content, options), warning: (title, content, options) => createMessage(MessageType.WARNING, title, content, options),
}; };
}, },
onSnackbar: (callback) => { snackbarCallback = callback; }, onSnackbar: (callback) => {
onLog: (callback) => { logCallback = callback; }, snackbarCallback = callback;
},
onLog: (callback) => {
logCallback = callback;
},
getMessages: async () => { getMessages: async () => {
try { try {
return await logDB.getLogs(); return await logDB.getLogs();
@ -107,7 +111,8 @@ export default {
} }
}, },
MessageType, MessageType,
markAsRead: () => {}, // 移除标记已读功能 markAsRead: () => {
}, // 移除标记已读功能
deleteMessage: async (messageId) => { deleteMessage: async (messageId) => {
try { try {
await logDB.deleteLog(messageId); await logDB.deleteLog(messageId);

View File

@ -636,7 +636,8 @@ class SettingsManagerClass {
* @returns {Function} 取消监听的函数 * @returns {Function} 取消监听的函数
*/ */
watchSettings(callback) { watchSettings(callback) {
if (typeof window === "undefined") return () => {}; if (typeof window === "undefined") return () => {
};
const handler = (event) => { const handler = (event) => {
if (event.key === SETTINGS_STORAGE_KEY) { if (event.key === SETTINGS_STORAGE_KEY) {

View File

@ -20,7 +20,9 @@ export function getSocket() {
const serverUrl = getServerUrl(); const serverUrl = getServerUrl();
if (!socket || connectedDomain !== serverUrl) { if (!socket || connectedDomain !== serverUrl) {
if (socket) { if (socket) {
try { socket.disconnect(); } catch (e) { try {
socket.disconnect();
} catch (e) {
void e; // ignore void e; // ignore
} }
socket = null; socket = null;
@ -78,7 +80,9 @@ export function onConnect(handler) {
export function disconnect() { export function disconnect() {
if (!socket) return; if (!socket) return;
try { socket.disconnect(); } catch (e) { try {
socket.disconnect();
} catch (e) {
void e; // ignore void e; // ignore
} }
socket = null; socket = null;