1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-12-07 13:03:59 +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 }">
<transition
name="md3"
mode="out-in"
name="md3"
>
<component
:is="Component"
@ -23,6 +23,7 @@ import { useTheme } from "vuetify";
import {getSetting} from "@/utils/settings";
import RateLimitModal from "@/components/RateLimitModal.vue";
import Clarity from "@microsoft/clarity";
const theme = useTheme();
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)">
<path fill="#FFFFFF" d="M0 256L256 256L256 0L0 0L0 256Z">
</path>
@ -11,7 +12,8 @@
<path d="M28 128L128 28L228 28L128 128L28 128Z" fill-rule="evenodd" fill="#52452A">
</path>
<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>
</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">
<span>缓存管理</span>
<v-spacer></v-spacer>
<v-btn color="error" @click="clearAllCaches" :loading="loading">
<v-btn :loading="loading" color="error" @click="clearAllCaches">
清除所有缓存
</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-btn>
</v-card-title>
<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 未激活缓存管理功能不可用
</v-alert>
@ -30,7 +30,7 @@
</v-expansion-panel-title>
<v-expansion-panel-text>
<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>
</div>
@ -43,7 +43,7 @@
{{ url }}
</v-list-item-subtitle>
<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-btn>
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,16 +38,23 @@
<template #subtitle>
<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>
</template>
<v-overlay
opacity=".12"
scrim="primary"
contained
model-value
opacity=".12"
persistent
scrim="primary"
/>
</v-card>
</v-col>
@ -67,11 +74,11 @@
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
opacity=".06"
persistent
scrim="primary"
/>
</v-card>
</v-col>
@ -91,11 +98,11 @@
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
opacity=".06"
persistent
scrim="primary"
/>
</v-card>
</v-col>
@ -115,11 +122,11 @@
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
opacity=".06"
persistent
scrim="primary"
/>
</v-card>
</v-col>
@ -139,11 +146,11 @@
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
opacity=".06"
persistent
scrim="primary"
/>
</v-card>
</v-col>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<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-title>消息记录</v-toolbar-title>
@ -8,13 +8,14 @@
<v-list>
<v-list-item v-for="msg in messages" :key="msg.id" rounded>
<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>
<v-list-item-title>{{ msg.title }}</v-list-item-title>
<v-list-item-subtitle v-if="msg.content">{{
msg.content
}}</v-list-item-subtitle>
}}
</v-list-item-subtitle>
<span class="text-caption text-grey">
{{ new Date(msg.timestamp).toLocaleTimeString() }}
</span>
@ -24,7 +25,7 @@
<v-list-item v-if="!messages.length">
<template #prepend>
<v-icon icon="mdi-inbox" color="grey" />
<v-icon color="grey" icon="mdi-inbox"/>
</template>
<v-list-item-title class="text-grey">暂无消息</v-list-item-title>
</v-list-item>

View File

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

View File

@ -4,7 +4,9 @@ Vue template files in this folder are automatically imported.
## 🚀 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`:

View File

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

View File

@ -2,20 +2,21 @@
<v-dialog v-model="isVisible" max-width="500" persistent>
<v-card class="rate-limit-modal">
<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-text class="pa-6">
<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-list
v-for="(request, index) in activeRequests"
:key="index"
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>
等待时间:
<span class="text-primary font-weight-bold">{{
@ -25,7 +26,8 @@
<v-list-item-subtitle>
{{ request.method }} {{ request.path }}
</v-list-item-subtitle>
</v-list-item></v-list
</v-list-item>
</v-list
>
<v-divider
v-if="index < activeRequests.length - 1"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,18 +3,19 @@
<div>
<v-text-field v-model="searchQuery" label="搜索设置" prepend-inner-icon="mdi-magnify" clearable variant="outlined"
density="comfortable" class="mb-4" />
<v-text-field v-model="searchQuery" class="mb-4" clearable density="comfortable" label="搜索设置"
prepend-inner-icon="mdi-magnify" variant="outlined"/>
<v-list>
<div v-for="setting in allSettings" :key="setting.key">
<setting-item :key="setting.key" :setting-key="setting.key"
:disabled="setting.requireDeveloper && !isDeveloperMode" @update="onSettingUpdate" @error="onSettingError" />
<setting-item :key="setting.key" :disabled="setting.requireDeveloper && !isDeveloperMode"
:setting-key="setting.key" @error="onSettingError"
@update="onSettingUpdate"/>
<v-divider class="my-2"/>
</div>
</v-list><v-card border>
</v-list>
<v-card border>
<v-card-title class="text-subtitle-1">当前配置</v-card-title>
<v-card-text>
<pre class="settings-json">{{ formattedSettings }}</pre>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template>
<settings-card title="数据源设置" icon="mdi-database-cog">
<settings-card icon="mdi-database-cog" title="数据源设置">
<v-list>
<!-- 服务器模式设置 -->
<template
@ -10,7 +10,7 @@
>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-lan-connect" class="mr-3" />
<v-icon class="mr-3" icon="mdi-lan-connect"/>
</template>
<v-list-item-title>检查服务器连接</v-list-item-title>
<template #append>
@ -21,7 +21,8 @@
>
测试连接
</v-btn>
</template> </v-list-item
</template>
</v-list-item
><!-- 数据迁移仅对KV本地存储有效 -->
</template>
@ -29,11 +30,12 @@
<template v-if="currentProvider === 'kv-local'">
<v-list-item>
<template #prepend>
<v-icon icon="mdi-database" class="mr-3" />
<v-icon class="mr-3" icon="mdi-database"/>
</template>
<v-list-item-title>清除数据库缓存</v-list-item-title>
<v-list-item-subtitle
>这将清除所有本地数据库中的数据</v-list-item-subtitle
>这将清除所有本地数据库中的数据
</v-list-item-subtitle
>
<template #append>
<v-btn color="error" variant="tonal" @click="confirmClearIndexedDB">
@ -43,7 +45,7 @@
</v-list-item>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-database-export" class="mr-3" />
<v-icon class="mr-3" icon="mdi-database-export"/>
</template>
<v-list-item-title>导出数据库</v-list-item-title>
<template #append>
@ -53,35 +55,38 @@
</template>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-database-import" class="mr-3" />
<v-icon class="mr-3" icon="mdi-database-import"/>
</template>
<v-list-item-title>迁移旧数据</v-list-item-title>
<v-list-item-subtitle
>将旧的存储格式数据转移到新的KV存储</v-list-item-subtitle
>将旧的存储格式数据转移到新的KV存储
</v-list-item-subtitle
>
<template #append>
<v-btn :loading="migrateLoading" variant="tonal" @click="migrateData">
迁移
</v-btn>
</template> </v-list-item
</template>
</v-list-item
><!-- 显示机器ID -->
<v-list-item>
<template #prepend>
<v-icon icon="mdi-identifier" class="mr-3" />
<v-icon class="mr-3" icon="mdi-identifier"/>
</template>
<v-list-item-title>本机唯一标识符</v-list-item-title>
<v-list-item-subtitle v-if="machineId">{{
machineId
}}</v-list-item-subtitle>
}}
</v-list-item-subtitle>
<v-list-item-subtitle v-else>正在加载...</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-lan-connect" class="mr-3" />
<v-icon class="mr-3" icon="mdi-lan-connect"/>
</template>
<v-list-item-title>查看本地缓存</v-list-item-title>
<template #append>
<v-btn variant="tonal" to="/cachemanagement"> 查看 </v-btn>
<v-btn to="/cachemanagement" variant="tonal"> 查看</v-btn>
</template>
</v-list-item>
</v-list>
@ -94,10 +99,12 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" variant="text" @click="confirmDialog = false"
>取消</v-btn
>取消
</v-btn
>
<v-btn color="error" variant="tonal" @click="handleConfirm"
>确认</v-btn
>确认
</v-btn
>
</v-card-actions>
</v-card>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template>
<settings-card title="编辑设置" icon="mdi-cog">
<settings-card icon="mdi-cog" title="编辑设置">
<v-list>
<setting-item setting-key="randomPicker.enabled"/>
<v-divider class="my-2"/>
@ -17,8 +17,6 @@
<setting-item setting-key="randomPicker.animation"/>
</v-list>
</settings-card>
</template>

View File

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

View File

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

View File

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

View File

@ -1,24 +1,24 @@
<template>
<settings-card title="主题设置" icon="mdi-palette">
<settings-card icon="mdi-palette" title="主题设置">
<v-list>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-theme-light-dark" class="mr-3" />
<v-icon class="mr-3" icon="mdi-theme-light-dark"/>
</template>
<v-list-item-title>主题模式</v-list-item-title>
<v-list-item-subtitle>选择明亮或暗黑主题</v-list-item-subtitle>
<template #append>
<v-btn-toggle
v-model="localTheme"
density="comfortable"
color="primary"
density="comfortable"
>
<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 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-toggle>

View File

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

View File

@ -3,16 +3,16 @@
<v-row>
<v-col cols="12">
<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>
<h1 class="text-h4 ">缓存管理</h1>
<div class="text-subtitle-1 text-grey">管理应用的本地缓存资源</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-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>
</v-card-text>
</v-card>
@ -22,10 +22,11 @@
<v-card class="mb-4" variant="tonal">
<v-card-text>
<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>
</div>
<p>缓存是浏览器在本地存储的网站资源副本如图片脚本和样式表等这些缓存可以加快页面加载速度减少数据使用并在离线时提供基本功能</p>
<p>
缓存是浏览器在本地存储的网站资源副本如图片脚本和样式表等这些缓存可以加快页面加载速度减少数据使用并在离线时提供基本功能</p>
</v-card-text>
</v-card>
</v-col>
@ -34,7 +35,7 @@
<v-card class="mb-4" variant="tonal">
<v-card-text>
<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>
</div>
<ul class="pl-4">

View File

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

View File

@ -2,4 +2,5 @@
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>
<v-container class="fill-height" fluid>
<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-title class="text-h5">
{{ status === 'processing' ? '正在处理授权...' : status === 'success' ? '授权成功' : '授权失败' }}
@ -9,9 +9,9 @@
<v-card-text>
<v-progress-linear
v-if="status === 'processing'"
indeterminate
color="primary"
class="mb-4"
color="primary"
indeterminate
></v-progress-linear>
<p>{{ message }}</p>
</v-card-text>

View File

@ -45,12 +45,19 @@
<!-- 输入方式选择 -->
<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="file" class="px-5"><v-icon start>mdi-file-upload</v-icon> 文件上传</v-tab>
<v-tab value="text" class="px-5">
<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-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="json">JSON</v-btn>
<v-btn value="yaml" :disabled="!yamlLibLoaded">
@ -329,7 +336,8 @@
</v-card-title>
<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">
{{ dayNames[day] }}
<v-badge
@ -368,10 +376,12 @@
</td>
<td>
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
<v-chip size="small" :color="getSubjectColor(item.subject)" label text-color="white" class="mr-1">
<v-chip size="small" :color="getSubjectColor(item.subject)" label text-color="white"
class="mr-1">
{{ item.subject }}
</v-chip>
<v-chip v-if="group.items.length > 1" size="x-small" class="ml-1" :color="item.weekType === '单' ? 'warning' : 'success'">
<v-chip v-if="group.items.length > 1" size="x-small" class="ml-1"
:color="item.weekType === '单' ? 'warning' : 'success'">
{{ item.weekType }}
</v-chip>
</div>

View File

@ -29,15 +29,15 @@
<v-divider class="my-4"/>
<v-btn
color="primary"
class="me-2"
color="primary"
@click="applySettings"
>
应用设置
</v-btn>
<v-btn
color="secondary"
class="me-2"
color="secondary"
@click="clearGuard"
>
清除重定向守卫
@ -104,7 +104,11 @@ const applySettings = () => {
}
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 = () => {
@ -117,7 +121,11 @@ const simulateLoadError = () => {
}
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(() => {

View File

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

View File

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

View File

@ -1,11 +1,11 @@
<template>
<v-alert
v-if="error"
type="error"
variant="tonal"
border="start"
class="mb-4"
closable
type="error"
variant="tonal"
@click:close="error = ''"
>
{{ error }}
@ -14,17 +14,17 @@
<v-skeleton-loader v-if="loading" type="article"/>
<div v-else-if="!config">
<v-alert type="warning" variant="tonal" border="start">
<v-alert border="start" type="warning" variant="tonal">
缺少配置请通过 URL 参数 id url 传入配置
</v-alert>
</div>
<div v-else>
<div class="player" ref="playerRef">
<div ref="playerRef" class="player">
<ExamPlayer
v-model:room-number="roomNumberLocal"
:exam-config="config"
:config="playerConfigObj"
:exam-config="config"
:show-action-bar="true"
:time-sync-status="'电脑时间'"
@exit="exit()"

View File

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

View File

@ -10,10 +10,10 @@
<!-- 只读 Token 警告 -->
<v-chip
v-if="tokenDisplayInfo.readonly"
color="warning"
variant="tonal"
class="mx-2"
color="warning"
prepend-icon="mdi-lock-alert"
variant="tonal"
>
只读
</v-chip>
@ -21,11 +21,11 @@
<!-- 学生名称显示 chip始终蓝色 -->
<v-chip
v-if="tokenDisplayInfo.show"
color="primary"
variant="tonal"
class="mx-2"
prepend-icon="mdi-account"
:style="{ cursor: tokenDisplayInfo.disabled ? 'default' : 'pointer' }"
class="mx-2"
color="primary"
prepend-icon="mdi-account"
variant="tonal"
@click="handleTokenChipClick"
>
{{ tokenDisplayInfo.text }}
@ -33,10 +33,10 @@
<v-btn icon="mdi-chat" variant="text" @click="isChatOpen = true"/>
<v-btn
icon="mdi-bell"
variant="text"
:badge="unreadCount || undefined"
:badge-color="unreadCount ? 'error' : undefined"
icon="mdi-bell"
variant="text"
@click="$refs.messageLog.drawer = true"
/>
<v-btn icon="mdi-cog" variant="text" @click="$router.push('/settings')"/>
@ -65,17 +65,17 @@
<div
v-for="item in sortedItems"
:key="item.key"
class="grid-item"
:style="{
'grid-row-end': `span ${item.rowSpan}`,
order: item.order,
}"
class="grid-item"
>
<v-card
border
height="100%"
class="glow-track"
:class="{ 'glow-highlight': highlightedCards[item.key] }"
border
class="glow-track"
height="100%"
@click="!isEditingDisabled && openDialog(item.key)"
@mousemove="handleMouseMove"
@touchmove="handleTouchMove"
@ -116,16 +116,16 @@
<v-card
v-for="subject in unusedSubjects"
:key="subject.name"
:disabled="isEditingDisabled"
border
class="empty-subject-card"
:disabled="isEditingDisabled"
@click="openDialog(subject.name)"
>
<v-card-title class="text-subtitle-1">
{{ subject.name }}
</v-card-title>
<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>
</v-card-text>
</v-card>
@ -134,10 +134,10 @@
</div>
<v-btn
v-if="!state.synced"
color="error"
size="large"
:loading="loading.upload"
class="ml-2"
color="error"
size="large"
@click="manualUpload"
>
上传
@ -147,31 +147,31 @@
</v-btn>
<v-btn
v-if="showRandomPickerButton"
append-icon="mdi-dice-multiple"
class="ml-2"
color="amber"
prepend-icon="mdi-account-question"
append-icon="mdi-dice-multiple"
size="large"
class="ml-2"
@click="openRandomPicker"
>
随机点名
</v-btn>
<v-btn
v-if="showExamScheduleButton"
class="ml-2"
color="green"
prepend-icon="mdi-calendar-check"
size="large"
class="ml-2"
@click="$router.push('/examschedule')"
>
考试看板
</v-btn>
<v-btn
v-if="showListCardButton"
class="ml-2"
color="primary-darken-1"
prepend-icon="mdi-list-box"
size="large"
class="ml-2"
@click="$router.push('/list')"
>
列表
@ -182,8 +182,8 @@
:prepend-icon="
state.isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'
"
size="large"
class="ml-2"
size="large"
@click="toggleFullscreen"
>
{{ state.isFullscreen ? "退出全屏" : "全屏显示"
@ -197,7 +197,7 @@
variant="tonal"
>
<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-text class="text-body-2">
@ -220,15 +220,16 @@
<!-- 出勤统计区域 -->
<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-ripple="{ class: `text-${['primary','secondary','info','success','warning','error'][Math.floor(Math.random()*6)]}` }"
class="attendance-area no-select"
cols="1"
@click="setAttendanceArea()"
>
<h1>出勤</h1>
<h2>
<snap style="white-space: nowrap"> 应到 </snap>:
<snap style="white-space: nowrap"> 应到</snap>
:
<snap style="white-space: nowrap">
{{
state.studentList.length -
@ -237,7 +238,8 @@
</snap>
</h2>
<h2>
<snap style="white-space: nowrap"> 实到 </snap>:
<snap style="white-space: nowrap"> 实到</snap>
:
<snap style="white-space: nowrap">
{{
state.studentList.length -
@ -248,43 +250,46 @@
</snap>
</h2>
<h2>
<snap style="white-space: nowrap"> 请假 </snap>:
<snap style="white-space: nowrap"> 请假</snap>
:
<snap style="white-space: nowrap">
{{ state.boardData.attendance.absent.length }}
</snap>
</h2>
<h3
class="gray-text"
v-for="(name, index) in state.boardData.attendance.absent"
:key="'absent-' + index"
class="gray-text"
>
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
><span style="white-space: nowrap">{{ name }}</span>
</h3>
<h2>
<snap style="white-space: nowrap">迟到</snap>:
<snap style="white-space: nowrap">迟到</snap>
:
<snap style="white-space: nowrap">
{{ state.boardData.attendance.late.length }}
</snap>
</h2>
<h3
class="gray-text"
v-for="(name, index) in state.boardData.attendance.late"
:key="'late-' + index"
class="gray-text"
>
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
><span style="white-space: nowrap">{{ name }}</span>
</h3>
<h2>
<snap style="white-space: nowrap">不参与</snap>:
<snap style="white-space: nowrap">不参与</snap>
:
<snap style="white-space: nowrap">
{{ state.boardData.attendance.exclude.length }}
</snap>
</h2>
<h3
class="gray-text"
v-for="(name, index) in state.boardData.attendance.exclude"
:key="'exclude-' + index"
class="gray-text"
>
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
><span style="white-space: nowrap">{{ name }}</span>
@ -294,9 +299,9 @@
<homework-edit-dialog
v-model="state.dialogVisible"
:title="state.dialogTitle"
:initial-content="state.textarea"
:auto-save="autoSave"
:initial-content="state.textarea"
:title="state.dialogTitle"
@save="handleHomeworkSave"
/>
@ -306,16 +311,16 @@
<v-dialog
v-model="state.attendanceDialog"
max-width="900"
fullscreen-breakpoint="sm"
max-width="900"
@update:model-value="handleAttendanceDialogClose"
>
<v-card>
<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-chip color="primary" size="small" class="ml-2">
<v-chip class="ml-2" color="primary" size="small">
{{ state.dateString }}
</v-chip>
</v-card-title>
@ -326,11 +331,11 @@
<v-col cols="12" md="12">
<v-text-field
v-model="attendanceSearch"
prepend-inner-icon="mdi-magnify"
label="搜索学生"
hint="支持筛选姓氏,如输入'孙'可筛选所有姓孙的学生"
variant="outlined"
clearable
hint="支持筛选姓氏,如输入'孙'可筛选所有姓孙的学生"
label="搜索学生"
prepend-inner-icon="mdi-magnify"
variant="outlined"
@update:model-value="handleSearchChange"
/>
@ -339,10 +344,10 @@
<v-btn
v-for="surname in extractedSurnames"
:key="surname.name"
:color="attendanceSearch === surname.name ? 'primary' : ''"
:variant="
attendanceSearch === surname.name ? 'elevated' : 'text'
"
:color="attendanceSearch === surname.name ? 'primary' : ''"
@click="
attendanceSearch =
attendanceSearch === surname.name ? '' : surname.name
@ -359,63 +364,63 @@
<div class="d-flex flex-wrap mb-4 gap-2">
<div>
<v-chip
value="present"
:append-icon="
attendanceFilter.includes('present') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('present') ? 'success' : ''"
:variant="
attendanceFilter.includes('present') ? 'elevated' : 'tonal'
"
class="px-2 filter-chip"
@click="toggleFilter('present')"
prepend-icon="mdi-account-check"
:append-icon="
attendanceFilter.includes('present') ? 'mdi-check' : ''
"
value="present"
@click="toggleFilter('present')"
>
到课
</v-chip>
<v-chip
value="absent"
:append-icon="
attendanceFilter.includes('absent') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('absent') ? 'error' : ''"
:variant="
attendanceFilter.includes('absent') ? 'elevated' : 'tonal'
"
class="px-2 filter-chip"
@click="toggleFilter('absent')"
prepend-icon="mdi-account-off"
:append-icon="
attendanceFilter.includes('absent') ? 'mdi-check' : ''
"
value="absent"
@click="toggleFilter('absent')"
>
请假
</v-chip>
<v-chip
value="late"
:append-icon="
attendanceFilter.includes('late') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('late') ? 'warning' : ''"
:variant="
attendanceFilter.includes('late') ? 'elevated' : 'tonal'
"
class="px-2 filter-chip"
@click="toggleFilter('late')"
prepend-icon="mdi-clock-alert"
:append-icon="
attendanceFilter.includes('late') ? 'mdi-check' : ''
"
value="late"
@click="toggleFilter('late')"
>
迟到
</v-chip>
<v-chip
value="exclude"
:append-icon="
attendanceFilter.includes('exclude') ? 'mdi-check' : ''
"
:color="attendanceFilter.includes('exclude') ? 'grey' : ''"
:variant="
attendanceFilter.includes('exclude') ? 'elevated' : 'tonal'
"
class="px-2 filter-chip"
@click="toggleFilter('exclude')"
prepend-icon="mdi-account-cancel"
:append-icon="
attendanceFilter.includes('exclude') ? 'mdi-check' : ''
"
value="exclude"
@click="toggleFilter('exclude')"
>
不参与
</v-chip>
@ -428,11 +433,11 @@
v-for="student in filteredStudents"
:key="student"
cols="12"
sm="6"
md="6"
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">
<div class="flex-grow-1">
<div class="d-flex align-center">
@ -442,12 +447,13 @@
state.studentList.indexOf(student)
)
"
size="24"
class="mr-2"
size="24"
>
<v-icon size="small">{{
getStudentStatusIcon(state.studentList.indexOf(student))
}}</v-icon>
}}
</v-icon>
</v-avatar>
<div class="text-subtitle-1">{{ student }}</div>
</div>
@ -459,11 +465,11 @@
? 'success'
: ''
"
:title="'设为到课'"
icon="mdi-account-check"
size="small"
variant="text"
@click="setPresent(state.studentList.indexOf(student))"
:title="'设为到课'"
/>
<v-btn
:color="
@ -471,11 +477,11 @@
? 'error'
: ''
"
:title="'设为请假'"
icon="mdi-account-off"
size="small"
variant="text"
@click="setAbsent(state.studentList.indexOf(student))"
:title="'设为请假'"
/>
<v-btn
:color="
@ -483,11 +489,11 @@
? 'warning'
: ''
"
:title="'设为迟到'"
icon="mdi-clock-alert"
size="small"
variant="text"
@click="setLate(state.studentList.indexOf(student))"
:title="'设为迟到'"
/>
<v-btn
:color="
@ -495,11 +501,11 @@
? 'grey'
: ''
"
:title="'设为不参与'"
icon="mdi-account-cancel"
size="small"
variant="text"
@click="setExclude(state.studentList.indexOf(student))"
:title="'设为不参与'"
/>
</div>
</v-card-text>
@ -508,7 +514,7 @@
</v-row>
<v-row>
<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>
<div class="text-subtitle-2 mb-2">批量操作</div>
<v-btn-group>
@ -545,7 +551,8 @@
</v-btn-group>
</v-card-text>
</v-card>
</v-col></v-row
</v-col>
</v-row
>
</v-card-text>
@ -566,12 +573,12 @@
<!-- 添加悬浮工具栏 -->
<floating-toolbar
:loading="loading.download"
:unread-count="unreadCount"
:selected-date="state.selectedDateObj"
:is-today="isToday"
@zoom="zoom"
:loading="loading.download"
:selected-date="state.selectedDateObj"
:unread-count="unreadCount"
@refresh="downloadData"
@zoom="zoom"
@open-messages="$refs.messageLog.drawer = true"
@open-settings="$router.push('/settings')"
@date-select="handleDateSelect"
@ -605,8 +612,8 @@
<!-- 添加随机点名组件 -->
<random-picker
ref="randomPicker"
:student-list="state.studentList"
:attendance="state.boardData.attendance"
:student-list="state.studentList"
/>
<!-- 添加URL配置确认对话框 -->
@ -621,17 +628,18 @@
:key="change.key"
>
<template #prepend>
<v-icon :icon="change.icon" size="small" class="mr-2" />
<v-icon :icon="change.icon" class="mr-2" size="small"/>
</template>
<v-list-item-title class="d-flex align-center">
<span class="text-subtitle-1">{{ change.name }}</span>
<v-tooltip activator="parent" location="top">{{
change.description || change.key
}}</v-tooltip>
}}
</v-tooltip>
</v-list-item-title>
<v-list-item-subtitle>
<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">{{
change.newValue
}}</span>
@ -652,8 +660,10 @@
确认应用
</v-btn>
</v-card-actions>
</v-card> </v-dialog
><br /><br /><br />
</v-card>
</v-dialog
>
<br/><br/><br/>
</template>
<script>
@ -1613,7 +1623,8 @@ export default {
.replace({
query: {date: formattedDate},
})
.catch(() => {});
.catch(() => {
});
// Load both data and subjects in parallel, force clear data when switching dates
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>
<v-btn
icon="mdi-arrow-left"
@ -6,38 +7,38 @@
@click="$router.push('/')"
/>
</template>
<v-app-bar-title class="text-h6" v-if="list && !isRenaming">{{ list.name }}</v-app-bar-title>
<v-app-bar-title class="text-h6" v-else>列表</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 v-else class="text-h6">列表</v-app-bar-title>
</v-app-bar>
<v-container>
<div class="d-flex align-center mb-4">
<v-btn
icon
class="mr-2"
to="/list"
border
class="mr-2"
icon
to="/list"
>
<v-icon>mdi-arrow-left</v-icon>
</v-btn>
<h1 v-if="list && !isRenaming">
{{ 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-btn>
</h1>
<div v-else-if="list && isRenaming" class="d-flex align-center">
<v-text-field
v-model="newListName"
label="列表名称"
hide-details
density="compact"
class="mr-2"
style="min-width: 200px;"
autofocus
class="mr-2"
density="compact"
hide-details
label="列表名称"
style="min-width: 200px;"
@keyup.enter="saveListName"
></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-btn>
<v-btn color="error" size="small" @click="cancelRenaming">
@ -50,7 +51,7 @@
</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-spacer/>
@ -98,25 +99,26 @@
<v-card-actions v-if="sortedItems.length > 0">
<v-spacer/>
<v-btn
:disabled="!hasCompletedItems"
color="error"
prepend-icon="mdi-delete-sweep"
@click="confirmDeleteCompleted"
:disabled="!hasCompletedItems"
>
删除已完成项目
</v-btn>
</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-text>
<v-text-field
v-model="newItemName"
label="项目名称"
:rules="[(v) => !!v || '名称不能为空']"
label="项目名称"
/>
<v-btn
color="primary"
:disabled="!newItemName"
color="primary"
@click="addItem"
>
添加
@ -124,19 +126,19 @@
</v-card-text>
</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-text>
<v-text-field
v-model="sortSeed"
label="排序种子 (任意数字或文本)"
hint="输入相同的种子值可以得到相同的排序结果"
persistent-hint
class="mb-3"
hint="输入相同的种子值可以得到相同的排序结果"
label="排序种子 (任意数字或文本)"
persistent-hint
/>
<v-btn
color="primary"
class="mr-2"
color="primary"
@click="randomSort"
>
随机排序
@ -179,7 +181,8 @@
<div v-if="!itemDialog.isEditing && itemDialog.item">
<v-list>
<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>
@ -206,25 +209,25 @@
<div v-else-if="itemDialog.isEditing && itemDialog.item" class="pa-2">
<v-text-field
v-model="itemDialog.editedItem.name"
class="mb-3"
label="名称"
variant="outlined"
class="mb-3"
></v-text-field>
<v-textarea
v-model="itemDialog.editedItem.description"
label="描述"
variant="outlined"
rows="3"
class="mb-3"
label="描述"
rows="3"
variant="outlined"
></v-textarea>
<v-switch
v-model="itemDialog.editedItem.completed"
label="已完成"
color="success"
hide-details
label="已完成"
></v-switch>
</div>
</v-card-text>

View File

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

View File

@ -27,11 +27,11 @@
<v-list-item
v-for="tab in settingsTabs"
:key="tab.value"
@click="settingsTab = tab.value"
:active="settingsTab === tab.value"
:color="settingsTab === tab.value ? 'primary' : 'default'"
:prepend-icon="tab.icon"
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>
@ -40,11 +40,13 @@
<v-tabs-window
v-model="settingsTab"
style="width: 100%"
direction="vertical"
style="width: 100%"
>
<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>
<div class="card-title">
<div>
@ -58,10 +60,10 @@
<v-card-text>
<div class="mt-4">
<v-btn
variant="text"
class="text-none"
append-icon="mdi-arrow-right"
class="text-none"
rounded="xl"
variant="text"
@click="openClassworksKV"
>
打开 Classworks KV
@ -69,35 +71,39 @@
</div>
</v-card-text>
</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-alert
color="error"
variant="tonal"
icon="mdi-alert-circle"
class="rounded-xl"
color="error"
icon="mdi-alert-circle"
variant="tonal"
>Classworks
是开源免费的软件官方没有提供任何形式的付费支持服务源代码仓库地址在
<a
href="https://github.com/ZeroCatDev/Classworks"
target="_blank"
>https://github.com/ZeroCatDev/Classworks</a
>如果您通过有偿协助等付费方式取得本应用在遇到问题时请在与卖家约定的服务框架下优先向卖家求助如果卖家没有提供您预期的服务请退款或通过其它形式积极维护您的合法权益</v-alert
>如果您通过有偿协助等付费方式取得本应用在遇到问题时请在与卖家约定的服务框架下优先向卖家求助如果卖家没有提供您预期的服务请退款或通过其它形式积极维护您的合法权益
</v-alert
>
<v-alert
class="mt-4 rounded-xl"
color="info"
variant="tonal"
icon="mdi-information"
>请不要使用浏览器清除缓存功能否则会导致配置丢失<del
>恶意的操作可能导致您受到贵校教师的处理</del
></v-alert
variant="tonal"
>请不要使用浏览器清除缓存功能否则会导致配置丢失
<del
>恶意的操作可能导致您受到贵校教师的处理
</del
>
</v-alert
>
<v-alert
class="mt-4 rounded-xl"
color="warning"
variant="tonal"
icon="mdi-information"
variant="tonal"
><p>
请不要使用包括但不限于360极速浏览器360安全浏览器夸克浏览器QQ浏览器等浏览器使用
Classworks
@ -108,24 +114,28 @@
上述浏览器商标为其所属公司所有Classworks
与上述浏览器所属公司无竞争关系
</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"
target="_blank"
color="warning"
variant="tonal"
class="text-none rounded-xl"
append-icon="mdi-open-in-new"
>下载 Microsoft Edge微软边缘浏览器</v-btn
></v-alert
>下载 Microsoft Edge微软边缘浏览器
</v-btn
>
</v-alert
>
</v-card-text>
</v-card><about-card />
</v-card>
<about-card/>
</v-tabs-window-item>
<v-tabs-window-item value="server">
<server-settings-card
border
:loading="loading.server"
border
@saved="onSettingsSaved"
/>
<data-provider-settings-card border class="mt-4"/>
@ -133,7 +143,7 @@
</v-tabs-window-item>
<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 value="share">
<settings-link-generator border class="mt-4"/>
@ -141,54 +151,57 @@
<v-tabs-window-item value="refresh">
<refresh-settings-card
border
:loading="loading.refresh"
border
@saved="onSettingsSaved"
/>
</v-tabs-window-item>
<v-tabs-window-item value="edit">
<edit-settings-card
border
:loading="loading.edit"
border
@saved="onSettingsSaved"
/>
</v-tabs-window-item>
<v-tabs-window-item value="display">
<display-settings-card
border
:loading="loading.display"
border
@saved="onSettingsSaved"
/>
</v-tabs-window-item>
<v-tabs-window-item value="theme">
<theme-settings-card
border
:loading="loading.theme"
border
@saved="onSettingsSaved"
/>
</v-tabs-window-item>
<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 value="subject">
<subject-management-card border /> <br />
<subject-management-card border/>
<br/>
<homework-template-card border/>
</v-tabs-window-item>
<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-item>
<template #prepend>
<v-icon icon="mdi-code-tags" class="mr-3" />
<v-icon class="mr-3" icon="mdi-code-tags"/>
</template>
<v-list-item-title>启用开发者选项</v-list-item-title>
<v-list-item-subtitle
>启用后可以查看和修改开发者设置</v-list-item-subtitle
>启用后可以查看和修改开发者设置
</v-list-item-subtitle
>
<template #append>
<v-switch
@ -202,14 +215,14 @@
</v-list>
</settings-card>
<developer-settings-card
border
:loading="loading.developer"
border
@saved="onSettingsSaved"
/>
<template v-if="settings.developer.enabled">
<v-card border class="mt-4 rounded-lg">
<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-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 SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue";
import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue";
export default {
name: "Settings",
components: {
@ -642,6 +656,7 @@ export default {
.settings-page {
.v-card {
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
}

View File

@ -1,3 +1,4 @@
# 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 {
position: relative;
@ -140,6 +137,7 @@
transform: scale(0.95);
}
}
.grid-masonry {
display: grid;
grid-template-columns: repeat(3, 1fr);

View File

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

View File

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

View File

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

View File

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

View File

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