规范代码格式

This commit is contained in:
Sunwuyuan
2025-11-16 15:14:17 +08:00
parent 0af2c4cc63
commit 76c2dba502
87 changed files with 3402 additions and 3246 deletions
+24 -23
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 }}
@@ -180,7 +181,7 @@
</template>
<script>
import { ref, onMounted } from "vue";
import {ref, onMounted} from "vue";
import packageJson from "../../../package.json";
export default {
+11 -11
View File
@@ -1,14 +1,14 @@
<template>
<v-card :border="border" class="setting-group">
<v-card-title v-if="title" class="d-flex align-center">
<v-icon v-if="icon" :icon="icon" class="mr-2" />
<v-icon v-if="icon" :icon="icon" class="mr-2"/>
{{ title }}
</v-card-title>
<v-card-subtitle v-if="description">
{{ description }}
</v-card-subtitle>
<v-card-text>
<v-list>
<slot>
@@ -16,7 +16,7 @@
</slot>
</v-list>
</v-card-text>
<v-card-actions v-if="$slots.actions">
<slot name="actions"></slot>
</v-card-actions>
@@ -26,7 +26,7 @@
<script>
export default {
name: 'SettingGroup',
props: {
/**
* 设置组的标题
@@ -35,7 +35,7 @@ export default {
type: String,
default: null
},
/**
* 设置组的描述
*/
@@ -43,7 +43,7 @@ export default {
type: String,
default: null
},
/**
* 设置组的图标
*/
@@ -51,7 +51,7 @@ export default {
type: String,
default: null
},
/**
* 是否显示边框
*/
@@ -60,12 +60,12 @@ export default {
default: false
}
},
methods: {
onSettingUpdate(key, value) {
this.$emit('update', key, value);
},
onSettingError(key) {
this.$emit('error', key);
}
@@ -77,4 +77,4 @@ export default {
.setting-group {
margin-bottom: 16px;
}
</style>
</style>
+41 -41
View File
@@ -1,7 +1,7 @@
<template>
<v-list-item class="setting-item" :disabled="disabled">
<v-list-item :disabled="disabled" class="setting-item">
<template #prepend>
<v-icon :icon="settingIcon" />
<v-icon :icon="settingIcon"/>
</template>
<v-list-item-title class="text-wrap">
@@ -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,34 +78,34 @@
<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">
<v-list-item @click="copySettingId">
<template v-slot:prepend>
<v-icon icon="mdi-key" size="small" />
<v-icon icon="mdi-key" size="small"/>
</template>
<v-list-item-title>复制设置ID</v-list-item-title>
</v-list-item>
<v-list-item @click="copySettingValue">
<template v-slot:prepend>
<v-icon icon="mdi-content-copy" size="small" />
<v-icon icon="mdi-content-copy" size="small"/>
</template>
<v-list-item-title>复制设置值</v-list-item-title>
</v-list-item>
<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" />
<v-icon icon="mdi-restore" size="small"/>
</template>
<v-list-item-title>重置为默认值</v-list-item-title>
</v-list-item>
@@ -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>
@@ -206,20 +206,20 @@ export default {
showSnackbar: false,
snackbarText: "",
fontFamilies: [
{ title: "Arial", value: "Arial, sans-serif" },
{ title: "Calibri", value: "Calibri, sans-serif" },
{ title: "Cambria", value: "Cambria, serif" },
{ title: "Consolas", value: "Consolas, monospace" },
{ title: "Courier New", value: "Courier New, monospace" },
{ title: "Georgia", value: "Georgia, serif" },
{ title: "Helvetica", value: "Helvetica, sans-serif" },
{ title: "Segoe UI", value: "Segoe UI, sans-serif" },
{ title: "Times New Roman", value: "Times New Roman, serif" },
{ title: "Trebuchet MS", value: "Trebuchet MS, sans-serif" },
{ title: "Verdana", value: "Verdana, sans-serif" },
{ title: "Monospace", value: "monospace" },
{ title: "Sans-serif", value: "sans-serif" },
{ title: "Serif", value: "serif" },
{title: "Arial", value: "Arial, sans-serif"},
{title: "Calibri", value: "Calibri, sans-serif"},
{title: "Cambria", value: "Cambria, serif"},
{title: "Consolas", value: "Consolas, monospace"},
{title: "Courier New", value: "Courier New, monospace"},
{title: "Georgia", value: "Georgia, serif"},
{title: "Helvetica", value: "Helvetica, sans-serif"},
{title: "Segoe UI", value: "Segoe UI, sans-serif"},
{title: "Times New Roman", value: "Times New Roman, serif"},
{title: "Trebuchet MS", value: "Trebuchet MS, sans-serif"},
{title: "Verdana", value: "Verdana, sans-serif"},
{title: "Monospace", value: "monospace"},
{title: "Sans-serif", value: "sans-serif"},
{title: "Serif", value: "serif"},
],
// 设置项的显示名称映射
displayValueMappings: {
+16 -15
View File
@@ -1,27 +1,28 @@
<template>
<div class="settings-explorer">
<div >
<v-text-field v-model="searchQuery" label="搜索设置" prepend-inner-icon="mdi-magnify" clearable variant="outlined"
density="comfortable" class="mb-4" />
<div>
<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" />
<v-divider class="my-2" />
<setting-item :key="setting.key" :disabled="setting.requireDeveloper && !isDeveloperMode"
:setting-key="setting.key" @error="onSettingError"
@update="onSettingUpdate"/>
<v-divider class="my-2"/>
</div>
</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>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="copySettingsToClipboard">
<v-btn @click="copySettingsToClipboard">
复制到剪贴板
<v-icon right>mdi-content-copy</v-icon>
</v-btn>
@@ -32,7 +33,7 @@
</template>
<script>
import { getSetting, settingsDefinitions, exportSettingsAsKeyValue, watchSettings } from '@/utils/settings';
import {getSetting, settingsDefinitions, exportSettingsAsKeyValue, watchSettings} from '@/utils/settings';
import SettingItem from './SettingItem.vue';
export default {
@@ -82,7 +83,7 @@ export default {
created() {
// 初始化当前设置
this.updateCurrentSettings();
// 监听设置变化
this.unwatchFunction = watchSettings(() => {
this.updateCurrentSettings();
@@ -115,11 +116,11 @@ export default {
navigator.clipboard.writeText(JSON.stringify(this.currentSettings))
.then(() => {
// 可以添加一个提示,表示复制成功
this.$emit('message', { type: 'success', text: '设置已复制到剪贴板' });
this.$emit('message', {type: 'success', text: '设置已复制到剪贴板'});
})
.catch(err => {
console.error('复制到剪贴板失败:', err);
this.$emit('message', { type: 'error', text: '复制到剪贴板失败' });
this.$emit('message', {type: 'error', text: '复制到剪贴板失败'});
});
}
}
@@ -145,4 +146,4 @@ export default {
.v-theme--dark .settings-json {
background-color: rgba(255, 255, 255, 0.05);
}
</style>
</style>
+54 -54
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="有未保存的更改" />
<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-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"
>
重载名单
@@ -218,9 +218,9 @@
<script>
import UnsavedWarning from "../common/UnsavedWarning.vue";
import "@/styles/warnings.scss";
import { pinyin } from "pinyin-pro";
import {pinyin} from "pinyin-pro";
import dataProvider from "@/utils/dataProvider";
import { getSetting } from "@/utils/settings";
import {getSetting} from "@/utils/settings";
export default {
name: "StudentListCard",
@@ -289,7 +289,7 @@ export default {
if (response.success != false && Array.isArray(response)) {
this.modelValue.list = response.map((item, index) => {
if (typeof item === 'string') {
return { id: index + 1, name: item };
return {id: index + 1, name: item};
}
return {
id: item.id || index + 1,
@@ -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(
@@ -371,9 +371,9 @@ export default {
const newList = lines.map(name => {
name = name.trim();
if (currentIds.has(name)) {
return { id: currentIds.get(name), name };
return {id: currentIds.get(name), name};
}
return { id: ++maxId, name };
return {id: ++maxId, name};
});
// Update the list
@@ -384,7 +384,7 @@ export default {
const name = this.newStudentName.trim();
if (name && !this.modelValue.list.some(s => s.name === name)) {
const maxId = Math.max(0, ...this.modelValue.list.map(s => s.id));
this.modelValue.list.push({ id: maxId + 1, name });
this.modelValue.list.push({id: maxId + 1, name});
this.newStudentName = "";
}
},
@@ -442,8 +442,8 @@ export default {
sortStudentsByPinyin() {
const sorted = [...this.modelValue.list].sort((a, b) => {
const pinyinA = pinyin(a.name, { toneType: "none" });
const pinyinB = pinyin(b.name, { toneType: "none" });
const pinyinA = pinyin(a.name, {toneType: "none"});
const pinyinB = pinyin(b.name, {toneType: "none"});
return pinyinA.localeCompare(pinyinB);
});
sorted.forEach((s, i) => s.id = i + 1);
@@ -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
@@ -174,11 +175,11 @@
</v-card-text>
<v-card-actions>
<v-spacer />
<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),您需要重新进行授权。
@@ -212,7 +213,7 @@
<p>您确定要重新初始化云端存储吗?</p>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-spacer/>
<v-btn
variant="text"
@click="showReinitDialog = false"
@@ -232,8 +233,8 @@
</template>
<script>
import { kvServerProvider } from "@/utils/providers/kvServerProvider";
import { setSetting, getSetting } from "@/utils/settings";
import {kvServerProvider} from "@/utils/providers/kvServerProvider";
import {setSetting, getSetting} from "@/utils/settings";
export default {
name: "CloudNamespaceInfoCard",
@@ -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,45 +45,48 @@
</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>
<v-btn variant="tonal" @click="exportData"> 导出 </v-btn>
<v-btn variant="tonal" @click="exportData"> 导出</v-btn>
</template>
</v-list-item>
</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>
@@ -107,12 +114,12 @@
<script>
import SettingsCard from "@/components/SettingsCard.vue";
import { getSetting } from "@/utils/settings";
import {getSetting} from "@/utils/settings";
import axios from "axios";
export default {
name: "DataProviderSettingsCard",
components: { SettingsCard },
components: {SettingsCard},
data() {
return {
@@ -156,7 +163,7 @@ export default {
const siteKey = getSetting("server.siteKey");
// Prepare headers including site key if available
const headers = { Accept: "application/json" };
const headers = {Accept: "application/json"};
if (siteKey) {
headers["x-site-key"] = siteKey;
}
@@ -227,7 +234,7 @@ export default {
async exportData() {
try {
const DBName = "ClassworksDB";
const data = { indexedDB: {} };
const data = {indexedDB: {}};
// 打开数据库
const db = await new Promise((resolve, reject) => {
@@ -1,50 +1,49 @@
<template>
<settings-card title="显示设置" icon="mdi-monitor" border>
<v-list>
<setting-item :setting-key="'display.emptySubjectDisplay'" />
<settings-card border icon="mdi-monitor" title="显示设置">
<v-list>
<setting-item :setting-key="'display.emptySubjectDisplay'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.dynamicSort'" />
<v-divider class="my-2" />
<setting-item :setting-key="'display.showRandomButton'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.dynamicSort'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.showFullscreenButton'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.showRandomButton'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.cardHoverEffect'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.showFullscreenButton'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.enhancedTouchMode'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.cardHoverEffect'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.showQuickTools'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.enhancedTouchMode'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.showAntiScreenBurnCard'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.showQuickTools'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'display.showExamScheduleButton'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'display.showAntiScreenBurnCard'"/>
</v-list>
<v-divider class="my-2"/>
<setting-item :setting-key="'display.showExamScheduleButton'"/>
</v-list>
</settings-card>
</template>
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import SettingItem from '@/components/settings/SettingItem.vue';
export default {
name: 'DisplaySettingsCard',
components: { SettingsCard, SettingItem },
components: {SettingsCard, SettingItem},
data() {
return {
};
return {};
},
};
</script>
@@ -1,8 +1,8 @@
<template>
<settings-card
border
title="回声洞"
icon="mdi-thought-bubble"
title="回声洞"
@click="handleClick"
>
<v-card-text>
@@ -12,7 +12,7 @@
<transition name="fade">
<v-chip v-if="currentQuote?.contributor" class="contributor">
<v-avatar start>
<v-img :src="`https://github.com/${currentQuote.contributor}.png`" />
<v-img :src="`https://github.com/${currentQuote.contributor}.png`"/>
</v-avatar>
{{ currentQuote.contributor }}
</v-chip>
@@ -31,13 +31,13 @@ const INITIAL_STATE = {
};
const TYPEWRITER_CONFIG = {
main: { delay: 50, deleteSpeed: 100 },
source: { delay: 10, deleteSpeed: 10, cursor: "" }
main: {delay: 50, deleteSpeed: 100},
source: {delay: 10, deleteSpeed: 10, cursor: ""}
};
export default {
name: "EchoChamberCard",
components: { SettingsCard },
components: {SettingsCard},
data: () => ({
typewriter: null,
sourceWriter: null,
@@ -79,7 +79,7 @@ export default {
async copyToClipboard() {
if (!this.currentQuote) return;
const { text, author, contributor, link } = this.currentQuote;
const {text, author, contributor, link} = this.currentQuote;
const parts = [
text,
author && `作者:${author}`,
@@ -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>
@@ -1,20 +1,19 @@
<template>
<settings-card title="编辑设置" icon="mdi-cog">
<settings-card icon="mdi-cog" title="编辑设置">
<v-list>
<setting-item :setting-key="'edit.autoSave'" />
<setting-item :setting-key="'edit.autoSave'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'edit.blockNonTodayAutoSave'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'edit.blockNonTodayAutoSave'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'edit.confirmNonTodaySave'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'edit.confirmNonTodaySave'"/>
<v-divider class="my-2" />
<setting-item :setting-key="'edit.refreshBeforeEdit'" />
<v-divider class="my-2"/>
<setting-item :setting-key="'edit.refreshBeforeEdit'"/>
</v-list>
</settings-card>
@@ -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-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>
@@ -176,22 +176,22 @@
</v-list>
</v-list-item>
<v-divider class="my-2" />
<v-divider class="my-2"/>
<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>
@@ -318,7 +318,7 @@
</template>
<script>
import { reactive } from 'vue';
import {reactive} from 'vue';
import SettingsCard from '@/components/SettingsCard.vue';
import SettingGroup from '@/components/settings/SettingGroup.vue';
import dataProvider from "@/utils/dataProvider.js";
@@ -466,7 +466,7 @@ export default {
addSubject() {
if (!this.newSubject) return;
if (!this.config.subjects[this.newSubject]) {
this.config.subjects[this.newSubject] = { books: {} };
this.config.subjects[this.newSubject] = {books: {}};
}
this.newSubject = '';
},
@@ -627,7 +627,7 @@ export default {
},
saveDialog() {
const { type, name, subject, originalName, tasks } = this.dialog.editedItem;
const {type, name, subject, originalName, tasks} = this.dialog.editedItem;
if (!name) {
this.showMessage('名称不能为空', 'error');
@@ -687,4 +687,4 @@ export default {
.v-card-text {
padding-top: 0;
}
</style>
</style>
+104 -109
View File
@@ -1,36 +1,36 @@
<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>
</v-list-item>
<v-divider class="my-2" />
<v-divider class="my-2"/>
<!-- 数据列表 -->
<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-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,10 +105,10 @@
<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" />
<v-spacer/>
<v-btn icon="mdi-close" variant="text" @click="viewDialog = false"/>
</v-card-title>
<v-card-subtitle v-if="selectedItem">
@@ -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-spacer/>
<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,10 +144,10 @@
<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" />
<v-spacer/>
<v-btn icon="mdi-close" variant="text" @click="closeEditDialog"/>
</v-card-title>
<v-card-subtitle v-if="editingItem">
@@ -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-spacer/>
<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,46 +188,46 @@
<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" />
<v-spacer/>
<v-btn icon="mdi-close" variant="text" @click="closeCreateDialog"/>
</v-card-title>
<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-spacer/>
<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,10 +239,10 @@
<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" />
<v-spacer/>
<v-btn icon="mdi-close" variant="text" @click="cloudUrlDialog = false"/>
</v-card-title>
<v-card-subtitle v-if="selectedCloudItem">
@@ -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>
@@ -312,17 +312,17 @@
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="cloudUrlDialog = false" variant="text">
<v-spacer/>
<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-spacer/>
<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>
@@ -367,8 +367,8 @@
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import dataProvider from '@/utils/dataProvider';
import { getSetting } from '@/utils/settings';
import { openDB } from 'idb';
import {getSetting} from '@/utils/settings';
import {openDB} from 'idb';
export default {
name: 'KvDatabaseCard',
@@ -414,8 +414,8 @@ export default {
// 表格头部
tableHeaders: [
{ title: '键名', key: 'key', sortable: true },
{ title: '操作', key: 'actions', sortable: false, width: '120px' }
{title: '键名', key: 'key', sortable: true},
{title: '操作', key: 'actions', sortable: false, width: '120px'}
]
};
},
@@ -426,7 +426,7 @@ export default {
},
isKvProvider() {
return this.currentProvider === 'kv-local' || this.currentProvider === 'kv-server'||this.currentProvider === 'classworkscloud'
return this.currentProvider === 'kv-local' || this.currentProvider === 'kv-server' || this.currentProvider === 'classworkscloud'
},
connectionStatus() {
@@ -537,15 +537,10 @@ export default {
},
async viewItem(item) {
this.selectedItem = item;
this.viewDialog = true;
// 如果数据未加载,则加载数据
if (!item.loaded || item.value === null) {
await this.loadItemData(item);
@@ -554,12 +549,12 @@ export default {
async editItem(item) {
this.editingItem = item;
// 如果数据未加载,则加载数据
if (!item.loaded || item.value === null) {
await this.loadItemData(item);
}
this.editingData = this.formatJsonData(item.value);
this.editDialog = true;
},
@@ -716,22 +711,22 @@ export default {
this.cloudUrlResult = null;
this.cloudUrlError = null;
this.cloudUrlDialog = true;
await this.fetchCloudUrl();
},
async fetchCloudUrl() {
if (!this.selectedCloudItem) return;
this.gettingCloudUrl = true;
this.cloudUrlError = null;
try {
const result = await dataProvider.getKeyCloudUrl(
this.selectedCloudItem.key,
this.cloudUrlOptions
);
if (result.success) {
this.cloudUrlResult = result;
this.$message.success('云端地址获取成功');
@@ -753,7 +748,7 @@ export default {
async copyCloudUrl() {
if (!this.cloudUrlResult?.url) return;
try {
await navigator.clipboard.writeText(this.cloudUrlResult.url);
this.$message.success('云端地址已复制到剪贴板');
@@ -764,7 +759,7 @@ export default {
openCloudUrl() {
if (!this.cloudUrlResult?.url) return;
try {
window.open(this.cloudUrlResult.url, '_blank');
} catch (error) {
@@ -1,30 +1,28 @@
<template>
<settings-card title="编辑设置" icon="mdi-cog">
<v-list>
<setting-item setting-key="randomPicker.enabled" />
<v-divider class="my-2" />
<settings-card icon="mdi-cog" title="编辑设置">
<v-list>
<setting-item setting-key="randomPicker.enabled"/>
<v-divider class="my-2"/>
<setting-item setting-key="randomPicker.mode" />
<v-divider class="my-2" />
<setting-item setting-key="randomPicker.minNumber" />
<v-divider class="my-2" />
<setting-item setting-key="randomPicker.maxNumber" />
<v-divider class="my-2" />
<setting-item setting-key="randomPicker.defaultCount" />
<setting-item setting-key="randomPicker.mode"/>
<v-divider class="my-2"/>
<setting-item setting-key="randomPicker.minNumber"/>
<v-divider class="my-2"/>
<setting-item setting-key="randomPicker.maxNumber"/>
<v-divider class="my-2"/>
<setting-item setting-key="randomPicker.defaultCount"/>
<v-divider class="my-2" />
<setting-item setting-key="randomPicker.animation" />
<v-divider class="my-2"/>
<setting-item setting-key="randomPicker.animation"/>
</v-list>
</settings-card>
</template>
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import SettingItem from '../SettingItem.vue';
</v-list>
</settings-card>
</template>
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import SettingItem from '../SettingItem.vue';
</script>
</script>
@@ -1,10 +1,11 @@
<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="刷新间隔" />
<setting-item setting-key="refresh.interval" title="刷新间隔"/>
</v-list>
</v-form>
@@ -14,15 +15,14 @@
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import SettingItem from '@/components/settings/SettingItem.vue';
export default {
name: 'RefreshSettingsCard',
components: { SettingsCard },
components: {SettingsCard},
data() {
return {
};
return {};
},
};
</script>
@@ -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>
@@ -118,11 +117,11 @@
<script>
import SettingsCard from "@/components/SettingsCard.vue";
import CloudNamespaceInfoCard from "./CloudNamespaceInfoCard.vue";
import { getSetting, setSetting, watchSettings } from "@/utils/settings";
import {getSetting, setSetting, watchSettings} from "@/utils/settings";
export default {
name: "ServerSettingsCard",
components: { SettingsCard, CloudNamespaceInfoCard },
components: {SettingsCard, CloudNamespaceInfoCard},
props: {
loading: Boolean,
},
@@ -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"
<v-btn
: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>
@@ -161,16 +161,16 @@ export default {
snackbarText: '',
snackbarColor: 'success',
defaultSubjects: [
{ name: '语文', order: 0 },
{ name: '数学', order: 1 },
{ name: '英语', order: 2 },
{ name: '物理', order: 3 },
{ name: '化学', order: 4 },
{ name: '生物', order: 5 },
{ name: '政治', order: 6 },
{ name: '历史', order: 7 },
{ name: '地理', order: 8 },
{ name: '其他', order: 9 }
{name: '语文', order: 0},
{name: '数学', order: 1},
{name: '英语', order: 2},
{name: '物理', order: 3},
{name: '化学', order: 4},
{name: '生物', order: 5},
{name: '政治', order: 6},
{name: '历史', order: 7},
{name: '地理', order: 8},
{name: '其他', order: 9}
]
};
},
@@ -250,7 +250,7 @@ export default {
updateSubject(subject) {
const index = this.subjects.findIndex(s => s.order === subject.order);
if (index > -1) {
this.subjects[index] = { ...subject };
this.subjects[index] = {...subject};
}
},
@@ -291,7 +291,8 @@ export default {
.v-list-item {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
.v-list-item:last-child {
border-bottom: none;
}
</style>
</style>
@@ -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>
@@ -30,12 +30,12 @@
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import { getSetting, setSetting } from '@/utils/settings';
import { useTheme } from 'vuetify';
import {getSetting, setSetting} from '@/utils/settings';
import {useTheme} from 'vuetify';
export default {
name: 'ThemeSettingsCard',
components: { SettingsCard },
components: {SettingsCard},
data() {
return {
@@ -52,7 +52,7 @@ export default {
setup() {
const theme = useTheme();
return { theme };
return {theme};
},
methods: {