mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-07-03 01:39:22 +00:00
1
This commit is contained in:
parent
69c67a7e52
commit
ec9f355f1b
@ -13,6 +13,7 @@
|
|||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"idb": "^8.0.2",
|
"idb": "^8.0.2",
|
||||||
|
"pinyin-pro": "^3.26.0",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"vue": "^3.4.31",
|
"vue": "^3.4.31",
|
||||||
"vue-masonry-wall": "^0.3.2",
|
"vue-masonry-wall": "^0.3.2",
|
||||||
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -17,6 +17,9 @@ importers:
|
|||||||
idb:
|
idb:
|
||||||
specifier: ^8.0.2
|
specifier: ^8.0.2
|
||||||
version: 8.0.2
|
version: 8.0.2
|
||||||
|
pinyin-pro:
|
||||||
|
specifier: ^3.26.0
|
||||||
|
version: 3.26.0
|
||||||
roboto-fontface:
|
roboto-fontface:
|
||||||
specifier: '*'
|
specifier: '*'
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
@ -1279,6 +1282,9 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
pinyin-pro@3.26.0:
|
||||||
|
resolution: {integrity: sha512-HcBZZb0pvm0/JkPhZHWA5Hqp2cWHXrrW/WrV+OtaYYM+kf35ffvZppIUuGmyuQ7gDr1JDJKMkbEE+GN0wfMoGg==}
|
||||||
|
|
||||||
pkg-types@1.2.1:
|
pkg-types@1.2.1:
|
||||||
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
|
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
|
||||||
|
|
||||||
@ -3019,6 +3025,8 @@ snapshots:
|
|||||||
vue: 3.5.13
|
vue: 3.5.13
|
||||||
vue-demi: 0.14.10(vue@3.5.13)
|
vue-demi: 0.14.10(vue@3.5.13)
|
||||||
|
|
||||||
|
pinyin-pro@3.26.0: {}
|
||||||
|
|
||||||
pkg-types@1.2.1:
|
pkg-types@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
confbox: 0.1.8
|
confbox: 0.1.8
|
||||||
|
@ -10,17 +10,23 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-card-title class="text-h6">学生列表</v-card-title>
|
<v-card-title class="text-h6">学生列表</v-card-title>
|
||||||
<template #append>
|
<template #append>
|
||||||
<unsaved-warning
|
<unsaved-warning :show="unsavedChanges" message="有未保存的更改" />
|
||||||
:show="unsavedChanges"
|
<v-btn
|
||||||
message="有未保存的更改"
|
prepend-icon="mdi-sort-alphabetical-variant"
|
||||||
/>
|
variant="text"
|
||||||
|
class="mr-2"
|
||||||
|
@click="sortStudentsByPinyin"
|
||||||
|
:disabled="modelValue.list.length === 0"
|
||||||
|
>
|
||||||
|
按姓名首字母排序
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
:color="modelValue.advanced ? 'primary' : undefined"
|
:color="modelValue.advanced ? 'primary' : undefined"
|
||||||
variant="text"
|
variant="text"
|
||||||
prepend-icon="mdi-code-braces"
|
prepend-icon="mdi-code-braces"
|
||||||
@click="toggleAdvanced"
|
@click="toggleAdvanced"
|
||||||
>
|
>
|
||||||
{{ modelValue.advanced ? '返回基础编辑' : '高级编辑' }}
|
{{ modelValue.advanced ? "返回基础编辑" : "高级编辑" }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-card-item>
|
</v-card-item>
|
||||||
@ -33,13 +39,7 @@
|
|||||||
class="mb-4"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-alert
|
<v-alert v-if="error" type="error" variant="tonal" closable class="mb-4">
|
||||||
v-if="error"
|
|
||||||
type="error"
|
|
||||||
variant="tonal"
|
|
||||||
closable
|
|
||||||
class="mb-4"
|
|
||||||
>
|
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</v-alert>
|
</v-alert>
|
||||||
|
|
||||||
@ -83,7 +83,7 @@
|
|||||||
<v-hover v-slot="{ isHovering, props }">
|
<v-hover v-slot="{ isHovering, props }">
|
||||||
<v-card
|
<v-card
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
:elevation="isMobile ? 1 : (isHovering ? 4 : 1)"
|
:elevation="isMobile ? 1 : isHovering ? 4 : 1"
|
||||||
class="student-card"
|
class="student-card"
|
||||||
border
|
border
|
||||||
>
|
>
|
||||||
@ -145,7 +145,10 @@
|
|||||||
{{ student }}
|
{{ student }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="d-flex gap-1 action-buttons" :class="{ 'opacity-100': isHovering || isMobile }">
|
<div
|
||||||
|
class="d-flex gap-1 action-buttons"
|
||||||
|
:class="{ 'opacity-100': isHovering || isMobile }"
|
||||||
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
icon="mdi-pencil"
|
icon="mdi-pencil"
|
||||||
variant="text"
|
variant="text"
|
||||||
@ -213,13 +216,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import UnsavedWarning from '../common/UnsavedWarning.vue'
|
import UnsavedWarning from "../common/UnsavedWarning.vue";
|
||||||
import '@/styles/warnings.scss'
|
import "@/styles/warnings.scss";
|
||||||
|
import { pinyin } from "pinyin-pro";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StudentListCard',
|
name: "StudentListCard",
|
||||||
components: {
|
components: {
|
||||||
UnsavedWarning
|
UnsavedWarning,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -227,27 +231,27 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
default: () => ({
|
default: () => ({
|
||||||
list: [],
|
list: [],
|
||||||
text: '',
|
text: "",
|
||||||
advanced: false
|
advanced: false,
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
error: String,
|
error: String,
|
||||||
isMobile: Boolean,
|
isMobile: Boolean,
|
||||||
unsavedChanges: Boolean
|
unsavedChanges: Boolean,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
newStudentName: '',
|
newStudentName: "",
|
||||||
editState: {
|
editState: {
|
||||||
index: -1,
|
index: -1,
|
||||||
name: ''
|
name: "",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['update:modelValue', 'save', 'reload'],
|
emits: ["update:modelValue", "save", "reload"],
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
text: {
|
text: {
|
||||||
@ -256,8 +260,8 @@ export default {
|
|||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.handleTextInput(value);
|
this.handleTextInput(value);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -266,15 +270,15 @@ export default {
|
|||||||
const advanced = !this.modelValue.advanced;
|
const advanced = !this.modelValue.advanced;
|
||||||
this.updateModelValue({
|
this.updateModelValue({
|
||||||
advanced,
|
advanced,
|
||||||
text: advanced ? this.modelValue.list.join('\n') : this.modelValue.text,
|
text: advanced ? this.modelValue.list.join("\n") : this.modelValue.text,
|
||||||
list: this.modelValue.list
|
list: this.modelValue.list,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateModelValue(newData) {
|
updateModelValue(newData) {
|
||||||
this.$emit('update:modelValue', {
|
this.$emit("update:modelValue", {
|
||||||
...this.modelValue,
|
...this.modelValue,
|
||||||
...newData
|
...newData,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -286,16 +290,16 @@ export default {
|
|||||||
const newList = [...this.modelValue.list, name];
|
const newList = [...this.modelValue.list, name];
|
||||||
this.updateModelValue({
|
this.updateModelValue({
|
||||||
list: newList,
|
list: newList,
|
||||||
text: newList.join('\n')
|
text: newList.join("\n"),
|
||||||
});
|
});
|
||||||
this.newStudentName = '';
|
this.newStudentName = "";
|
||||||
},
|
},
|
||||||
|
|
||||||
removeStudent(index) {
|
removeStudent(index) {
|
||||||
const newList = this.modelValue.list.filter((_, i) => i !== index);
|
const newList = this.modelValue.list.filter((_, i) => i !== index);
|
||||||
this.updateModelValue({
|
this.updateModelValue({
|
||||||
list: newList,
|
list: newList,
|
||||||
text: newList.join('\n')
|
text: newList.join("\n"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -303,9 +307,9 @@ export default {
|
|||||||
const newList = [...this.modelValue.list];
|
const newList = [...this.modelValue.list];
|
||||||
let targetIndex;
|
let targetIndex;
|
||||||
|
|
||||||
if (direction === 'top') {
|
if (direction === "top") {
|
||||||
targetIndex = 0;
|
targetIndex = 0;
|
||||||
} else if (direction === 'up') {
|
} else if (direction === "up") {
|
||||||
targetIndex = index - 1;
|
targetIndex = index - 1;
|
||||||
} else {
|
} else {
|
||||||
targetIndex = index + 1;
|
targetIndex = index + 1;
|
||||||
@ -317,7 +321,7 @@ export default {
|
|||||||
|
|
||||||
this.updateModelValue({
|
this.updateModelValue({
|
||||||
list: newList,
|
list: newList,
|
||||||
text: newList.join('\n')
|
text: newList.join("\n"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -336,9 +340,9 @@ export default {
|
|||||||
|
|
||||||
this.updateModelValue({
|
this.updateModelValue({
|
||||||
list: newList,
|
list: newList,
|
||||||
text: newList.join('\n')
|
text: newList.join("\n"),
|
||||||
});
|
});
|
||||||
this.editState = { index: -1, name: '' };
|
this.editState = { index: -1, name: "" };
|
||||||
},
|
},
|
||||||
|
|
||||||
handleClick(index, student) {
|
handleClick(index, student) {
|
||||||
@ -349,17 +353,30 @@ export default {
|
|||||||
|
|
||||||
handleTextInput(value) {
|
handleTextInput(value) {
|
||||||
const list = value
|
const list = value
|
||||||
.split('\n')
|
.split("\n")
|
||||||
.map(s => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(s => s);
|
.filter((s) => s);
|
||||||
|
|
||||||
this.updateModelValue({
|
this.updateModelValue({
|
||||||
text: value,
|
text: value,
|
||||||
list
|
list,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
sortStudentsByPinyin() {
|
||||||
|
const newList = [...this.modelValue.list].sort((a, b) => {
|
||||||
|
const pinyinA = pinyin(a, { toneType: "none" });
|
||||||
|
const pinyinB = pinyin(b, { toneType: "none" });
|
||||||
|
return pinyinA.localeCompare(pinyinB);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateModelValue({
|
||||||
|
list: newList,
|
||||||
|
text: newList.join("\n"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -379,7 +396,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse-warning {
|
@keyframes pulse-warning {
|
||||||
0%, 100% {
|
0%,
|
||||||
|
100% {
|
||||||
border-color: rgba(var(--v-theme-warning), 1) !important;
|
border-color: rgba(var(--v-theme-warning), 1) !important;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<settings-card
|
<settings-card title="数据源设置" icon="mdi-database-cog">
|
||||||
title="数据源设置"
|
|
||||||
icon="mdi-database-cog"
|
|
||||||
>
|
|
||||||
<v-list>
|
<v-list>
|
||||||
<!-- 服务器模式设置 -->
|
<!-- 服务器模式设置 -->
|
||||||
<template v-if="currentProvider === 'server'">
|
<template v-if="currentProvider === 'server'">
|
||||||
@ -31,7 +28,9 @@
|
|||||||
<v-icon icon="mdi-database" class="mr-3" />
|
<v-icon icon="mdi-database" class="mr-3" />
|
||||||
</template>
|
</template>
|
||||||
<v-list-item-title>清除数据库缓存</v-list-item-title>
|
<v-list-item-title>清除数据库缓存</v-list-item-title>
|
||||||
<v-list-item-subtitle>这将清除所有IndexedDB中的数据</v-list-item-subtitle>
|
<v-list-item-subtitle
|
||||||
|
>这将清除所有IndexedDB中的数据</v-list-item-subtitle
|
||||||
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="error"
|
color="error"
|
||||||
@ -49,11 +48,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-list-item-title>导出数据库</v-list-item-title>
|
<v-list-item-title>导出数据库</v-list-item-title>
|
||||||
<template #append>
|
<template #append>
|
||||||
<v-btn
|
<v-btn variant="tonal" size="small" @click="exportData">
|
||||||
variant="tonal"
|
|
||||||
size="small"
|
|
||||||
@click="exportData"
|
|
||||||
>
|
|
||||||
导出
|
导出
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -68,8 +63,12 @@
|
|||||||
<v-card-text>{{ confirmMessage }}</v-card-text>
|
<v-card-text>{{ confirmMessage }}</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn color="grey" variant="text" @click="confirmDialog = false">取消</v-btn>
|
<v-btn color="grey" variant="text" @click="confirmDialog = false"
|
||||||
<v-btn color="error" variant="tonal" @click="handleConfirm">确认</v-btn>
|
>取消</v-btn
|
||||||
|
>
|
||||||
|
<v-btn color="error" variant="tonal" @click="handleConfirm"
|
||||||
|
>确认</v-btn
|
||||||
|
>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@ -77,54 +76,59 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SettingsCard from '@/components/SettingsCard.vue';
|
import SettingsCard from "@/components/SettingsCard.vue";
|
||||||
import { getSetting } from '@/utils/settings';
|
import { getSetting } from "@/utils/settings";
|
||||||
|
import axios from "axios";
|
||||||
export default {
|
export default {
|
||||||
name: 'DataProviderSettingsCard',
|
name: "DataProviderSettingsCard",
|
||||||
components: { SettingsCard },
|
components: { SettingsCard },
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
serverchecktime: {},
|
||||||
confirmDialog: false,
|
confirmDialog: false,
|
||||||
confirmTitle: '',
|
confirmTitle: "",
|
||||||
confirmMessage: '',
|
confirmMessage: "",
|
||||||
confirmAction: null
|
confirmAction: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
currentProvider() {
|
currentProvider() {
|
||||||
return getSetting('server.provider');
|
return getSetting("server.provider");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async checkServerConnection() {
|
async checkServerConnection() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.serverchecktime = new Date();
|
||||||
try {
|
try {
|
||||||
const domain = getSetting('server.domain');
|
const domain = getSetting("server.domain");
|
||||||
const response = await fetch(`${domain}`, {
|
const response = await axios.get(`${domain}/api/test`, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: { 'Accept': 'application/json' }
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.data.status === "success") {
|
||||||
this.$message.success('连接成功', '服务器连接正常');
|
this.$message.success(
|
||||||
|
"连接成功",
|
||||||
|
"服务器连接正常 延迟" + (new Date() - this.serverchecktime) + "ms"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('服务器响应异常');
|
throw new Error("服务器响应异常");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error('连接失败', error.message || '无法连接到服务器');
|
this.$message.error("连接失败", error.message || "无法连接到服务器");
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmClearLocalStorage() {
|
confirmClearLocalStorage() {
|
||||||
this.confirmTitle = '确认清除';
|
this.confirmTitle = "确认清除";
|
||||||
this.confirmMessage = '此操作将清除所有本地存储的数据,确定要继续吗?';
|
this.confirmMessage = "此操作将清除所有本地存储的数据,确定要继续吗?";
|
||||||
this.confirmAction = this.clearLocalStorage;
|
this.confirmAction = this.clearLocalStorage;
|
||||||
this.confirmDialog = true;
|
this.confirmDialog = true;
|
||||||
},
|
},
|
||||||
@ -132,35 +136,35 @@ export default {
|
|||||||
clearLocalStorage() {
|
clearLocalStorage() {
|
||||||
try {
|
try {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
this.$message.success('清除成功', '本地存储数据已清除');
|
this.$message.success("清除成功", "本地存储数据已清除");
|
||||||
this.confirmDialog = false;
|
this.confirmDialog = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error('清除失败', error.message);
|
this.$message.error("清除失败", error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmClearIndexedDB() {
|
confirmClearIndexedDB() {
|
||||||
this.confirmTitle = '确认清除';
|
this.confirmTitle = "确认清除";
|
||||||
this.confirmMessage = '此操作将清除所有IndexedDB中的数据,确定要继续吗?';
|
this.confirmMessage = "此操作将清除所有IndexedDB中的数据,确定要继续吗?";
|
||||||
this.confirmAction = this.clearIndexedDB;
|
this.confirmAction = this.clearIndexedDB;
|
||||||
this.confirmDialog = true;
|
this.confirmDialog = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async clearIndexedDB() {
|
async clearIndexedDB() {
|
||||||
try {
|
try {
|
||||||
const DBName = 'HomeworkDB';
|
const DBName = "HomeworkDB";
|
||||||
// 删除整个数据库
|
// 删除整个数据库
|
||||||
await window.indexedDB.deleteDatabase(DBName);
|
await window.indexedDB.deleteDatabase(DBName);
|
||||||
this.$message.success('清除成功', '数据库缓存已清除');
|
this.$message.success("清除成功", "数据库缓存已清除");
|
||||||
this.confirmDialog = false;
|
this.confirmDialog = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error('清除失败', error.message);
|
this.$message.error("清除失败", error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async exportData() {
|
async exportData() {
|
||||||
try {
|
try {
|
||||||
const DBName = 'HomeworkDB';
|
const DBName = "HomeworkDB";
|
||||||
const data = { indexedDB: {} };
|
const data = { indexedDB: {} };
|
||||||
|
|
||||||
// 打开数据库
|
// 打开数据库
|
||||||
@ -175,7 +179,7 @@ export default {
|
|||||||
|
|
||||||
// 导出每个存储对象的数据
|
// 导出每个存储对象的数据
|
||||||
for (const storeName of stores) {
|
for (const storeName of stores) {
|
||||||
const transaction = db.transaction(storeName, 'readonly');
|
const transaction = db.transaction(storeName, "readonly");
|
||||||
const store = transaction.objectStore(storeName);
|
const store = transaction.objectStore(storeName);
|
||||||
|
|
||||||
// 获取存储对象中的所有数据
|
// 获取存储对象中的所有数据
|
||||||
@ -189,19 +193,21 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建并下载文件
|
// 创建并下载文件
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
||||||
|
type: "application/json",
|
||||||
|
});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement("a");
|
||||||
const timestamp = new Date().toISOString().split('T')[0];
|
const timestamp = new Date().toISOString().split("T")[0];
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = `homework-indexeddb-${timestamp}.json`;
|
a.download = `homework-indexeddb-${timestamp}.json`;
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
this.$message.success('导出成功', 'IndexedDB数据已导出');
|
this.$message.success("导出成功", "IndexedDB数据已导出");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('导出失败:', error);
|
console.error("导出失败:", error);
|
||||||
this.$message.error('导出失败', error.message || '无法导出数据库数据');
|
this.$message.error("导出失败", error.message || "无法导出数据库数据");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -209,7 +215,7 @@ export default {
|
|||||||
if (this.confirmAction) {
|
if (this.confirmAction) {
|
||||||
this.confirmAction();
|
this.confirmAction();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -79,13 +79,39 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
save() {
|
async save() {
|
||||||
|
try {
|
||||||
|
// 先保存设置
|
||||||
Object.entries(this.localSettings).forEach(([key, value]) => {
|
Object.entries(this.localSettings).forEach(([key, value]) => {
|
||||||
setSetting(`server.${key}`, value);
|
const success = setSetting(`server.${key}`, value);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(`保存设置 ${key} 失败`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 如果切换到服务器模式,验证服务器连接
|
||||||
|
if (this.localSettings.provider === 'server' && this.localSettings.domain) {
|
||||||
|
const testUrl = `${this.localSettings.domain.replace(/\/$/, '')}/api/test`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(testUrl);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('服务器连接测试失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('无法连接到服务器,请检查域名设置');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.originalSettings = { ...this.localSettings };
|
this.originalSettings = { ...this.localSettings };
|
||||||
this.$emit("saved");
|
this.$emit('saved');
|
||||||
window.location.reload();
|
|
||||||
|
// 延迟刷新页面,让用户看到保存成功的提示
|
||||||
|
setTimeout(() => {
|
||||||
|
//window.location.reload();
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
this.$message?.error('保存失败', error.message);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.localSettings = { ...this.originalSettings };
|
this.localSettings = { ...this.originalSettings };
|
||||||
|
@ -216,7 +216,6 @@ export default {
|
|||||||
settings,
|
settings,
|
||||||
dataProviders: [
|
dataProviders: [
|
||||||
{ title: '服务器', value: 'server' },
|
{ title: '服务器', value: 'server' },
|
||||||
{ title: '本地存储', value: 'localStorage' },
|
|
||||||
{ title:'本地数据库',value:'indexedDB'}
|
{ title:'本地数据库',value:'indexedDB'}
|
||||||
],
|
],
|
||||||
studentData: {
|
studentData: {
|
||||||
|
@ -102,7 +102,18 @@ const settingsDefinitions = {
|
|||||||
"server.domain": {
|
"server.domain": {
|
||||||
type: "string",
|
type: "string",
|
||||||
default: "",
|
default: "",
|
||||||
validate: (value) => !value || /^https?:\/\//.test(value),
|
validate: value => {
|
||||||
|
// 如果不是服务器模式或值为空,直接通过
|
||||||
|
if (!value) return true;
|
||||||
|
// 验证URL格式
|
||||||
|
try {
|
||||||
|
new URL(value);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('域名格式无效:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
description: "后端服务器域名",
|
description: "后端服务器域名",
|
||||||
},
|
},
|
||||||
"server.classNumber": {
|
"server.classNumber": {
|
||||||
@ -116,8 +127,7 @@ const settingsDefinitions = {
|
|||||||
"server.provider": {
|
"server.provider": {
|
||||||
type: "string",
|
type: "string",
|
||||||
default: "indexedDB",
|
default: "indexedDB",
|
||||||
validate: (value) =>
|
validate: (value) => ["server", "indexedDB"].includes(value),
|
||||||
["server", "indexedDB"].includes(value),
|
|
||||||
description: "数据提供者,用于决定数据存储方式",
|
description: "数据提供者,用于决定数据存储方式",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user