pwa,设置项,自动刷新

This commit is contained in:
SunWuyuan
2025-04-04 21:40:24 +08:00
parent 8e4d29afa3
commit 291c593178
32 changed files with 8794 additions and 965 deletions
@@ -144,7 +144,7 @@ export default {
async clearIndexedDB() {
try {
const DBName = "HomeworkDB";
const DBName = "ClassworksDB";
// 删除整个数据库
await window.indexedDB.deleteDatabase(DBName);
this.$message.success("清除成功", "数据库缓存已清除");
@@ -156,7 +156,7 @@ export default {
async exportData() {
try {
const DBName = "HomeworkDB";
const DBName = "ClassworksDB";
const data = { indexedDB: {} };
// 打开数据库
@@ -1,260 +1,46 @@
<template>
<settings-card
title="显示设置"
icon="mdi-monitor"
border
>
<v-form v-model="isValid" @submit.prevent="save">
<settings-card title="显示设置" icon="mdi-monitor" border>
<v-list>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-card-outline" class="mr-3" />
</template>
<v-list-item-title>空科目显示方式</v-list-item-title>
<v-list-item-subtitle>选择空科目的显示方式</v-list-item-subtitle>
<template #append>
<v-select
v-model="emptySubjectDisplay"
:items="displayOptions"
density="compact"
hide-details
variant="outlined"
style="max-width: 150px"
/>
</template>
</v-list-item>
<setting-item :setting-key="'display.emptySubjectDisplay'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-sort-variant" class="mr-3" />
</template>
<v-list-item-title>动态排序</v-list-item-title>
<v-list-item-subtitle>优化卡片布局以提高显示效果</v-list-item-subtitle>
<template #append>
<v-switch
v-model="dynamicSort"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<setting-item :setting-key="'display.dynamicSort'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-dice-multiple" class="mr-3" />
</template>
<v-list-item-title>显示随机按钮</v-list-item-title>
<v-list-item-subtitle>在主页显示随机点名按钮</v-list-item-subtitle>
<template #append>
<v-switch
v-model="showRandomButton"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<setting-item :setting-key="'display.showRandomButton'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-fullscreen" class="mr-3" />
</template>
<v-list-item-title>显示全屏按钮</v-list-item-title>
<v-list-item-subtitle>在主页显示全屏切换按钮</v-list-item-subtitle>
<template #append>
<v-switch
v-model="showFullscreenButton"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<setting-item :setting-key="'display.showFullscreenButton'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-cards-outline" class="mr-3" />
</template>
<v-list-item-title>卡片悬浮效果</v-list-item-title>
<v-list-item-subtitle>启用卡片悬停时的动画效果</v-list-item-subtitle>
<template #append>
<v-switch
v-model="cardHoverEffect"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<setting-item :setting-key="'display.cardHoverEffect'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-gesture-tap" class="mr-3" />
</template>
<v-list-item-title>增强触摸模式</v-list-item-title>
<v-list-item-subtitle>优化触摸屏操作体验</v-list-item-subtitle>
<template #append>
<v-switch
v-model="enhancedTouchMode"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<setting-item :setting-key="'display.enhancedTouchMode'" />
<v-divider class="my-2" />
<setting-item :setting-key="'display.showAntiScreenBurnCard'" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-shield-check" class="mr-3" />
</template>
<v-list-item-title>显示防烧屏提示</v-list-item-title>
<v-list-item-subtitle>显示防烧屏技术提示卡片</v-list-item-subtitle>
<template #append>
<v-switch
v-model="showAntiScreenBurnCard"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<div class="d-flex gap-2 mt-4">
<v-btn
color="primary"
type="submit"
:disabled="!hasChanges || !isValid"
prepend-icon="mdi-content-save"
>
保存更改
</v-btn>
<v-btn
variant="outlined"
@click="reset"
:disabled="!hasChanges"
>
重置
</v-btn>
</div>
</v-list>
</v-form>
</settings-card>
</template>
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import { getSetting, setSetting } from '@/utils/settings';
import SettingItem from '@/components/settings/SettingItem.vue';
export default {
name: 'DisplaySettingsCard',
components: { SettingsCard },
components: { SettingsCard, SettingItem },
data() {
const settings = {
emptySubjectDisplay: getSetting('display.emptySubjectDisplay'),
dynamicSort: getSetting('display.dynamicSort'),
showRandomButton: getSetting('display.showRandomButton'),
showFullscreenButton: getSetting('display.showFullscreenButton'),
cardHoverEffect: getSetting('display.cardHoverEffect'),
enhancedTouchMode: getSetting('display.enhancedTouchMode'),
showAntiScreenBurnCard: getSetting('display.showAntiScreenBurnCard')
};
return {
localSettings: { ...settings },
originalSettings: settings,
isValid: true,
displayOptions: [
{ title: '卡片', value: 'card' },
{ title: '按钮', value: 'button' }
]
};
},
computed: {
hasChanges() {
return JSON.stringify(this.localSettings) !== JSON.stringify(this.originalSettings);
},
emptySubjectDisplay: {
get() {
return this.localSettings.emptySubjectDisplay;
},
set(value) {
this.localSettings.emptySubjectDisplay = value;
this.$emit('saved');
}
},
dynamicSort: {
get() {
return this.localSettings.dynamicSort;
},
set(value) {
this.localSettings.dynamicSort = value;
this.$emit('saved');
}
},
showRandomButton: {
get() {
return this.localSettings.showRandomButton;
},
set(value) {
this.localSettings.showRandomButton = value;
this.$emit('saved');
}
},
showFullscreenButton: {
get() {
return this.localSettings.showFullscreenButton;
},
set(value) {
this.localSettings.showFullscreenButton = value;
this.$emit('saved');
}
},
cardHoverEffect: {
get() {
return this.localSettings.cardHoverEffect;
},
set(value) {
this.localSettings.cardHoverEffect = value;
this.$emit('saved');
}
},
enhancedTouchMode: {
get() {
return this.localSettings.enhancedTouchMode;
},
set(value) {
this.localSettings.enhancedTouchMode = value;
this.$emit('saved');
}
},
showAntiScreenBurnCard: {
get() {
return this.localSettings.showAntiScreenBurnCard;
},
set(value) {
this.localSettings.showAntiScreenBurnCard = value;
this.$emit('saved');
}
}
},
methods: {
save() {
Object.entries(this.localSettings).forEach(([key, value]) => {
setSetting(`display.${key}`, value);
});
this.originalSettings = { ...this.localSettings };
this.$emit('saved');
},
reset() {
this.localSettings = { ...this.originalSettings };
}
}
};
</script>
@@ -1,135 +1,27 @@
<template>
<settings-card
title="编辑设置"
icon="mdi-pencil-cog"
>
<v-form v-model="isValid" @submit.prevent="save">
<v-list>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-content-save" class="mr-3" />
</template>
<v-list-item-title>自动保存</v-list-item-title>
<v-list-item-subtitle>在编辑完成后自动保存到服务器</v-list-item-subtitle>
<template #append>
<v-switch
v-model="localSettings.autoSave"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<settings-card title="编辑设置" icon="mdi-cog">
<v-list>
<setting-item :setting-key="'edit.autoSave'" />
<v-divider v-if="localSettings.autoSave" class="my-2" />
<v-list-item v-if="localSettings.autoSave">
<template #prepend>
<v-icon icon="mdi-calendar-lock" class="mr-3" />
</template>
<v-list-item-title>禁止自动保存非当天数据</v-list-item-title>
<v-list-item-subtitle>仅允许自动保存当天的数据,避免误修改历史记录</v-list-item-subtitle>
<template #append>
<v-switch
v-model="localSettings.blockNonTodayAutoSave"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<v-divider class="my-2" />
<setting-item :setting-key="'edit.blockNonTodayAutoSave'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-calendar-alert" class="mr-3" />
</template>
<v-list-item-title>确认保存历史数据</v-list-item-title>
<v-list-item-subtitle>保存非当天数据时显示确认对话框</v-list-item-subtitle>
<template #append>
<v-switch
v-model="localSettings.confirmNonTodaySave"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<v-divider class="my-2" />
<setting-item :setting-key="'edit.confirmNonTodaySave'" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-refresh" class="mr-3" />
</template>
<v-list-item-title>编辑前刷新</v-list-item-title>
<v-list-item-subtitle>在打开编辑框前从服务器获取最新数据</v-list-item-subtitle>
<template #append>
<v-switch
v-model="localSettings.refreshBeforeEdit"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<div class="d-flex gap-2 mt-4">
<v-btn
color="primary"
type="submit"
:disabled="!hasChanges || !isValid"
prepend-icon="mdi-content-save"
>
保存更改
</v-btn>
<v-btn
variant="outlined"
@click="reset"
:disabled="!hasChanges"
>
重置
</v-btn>
</div>
</v-list>
</v-form>
<v-divider class="my-2" />
<setting-item :setting-key="'edit.refreshBeforeEdit'" />
</v-list>
</settings-card>
</template>
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import { getSetting, setSetting } from '@/utils/settings';
import SettingItem from '../SettingItem.vue';
export default {
name: 'EditSettingsCard',
components: { SettingsCard },
data() {
const settings = {
autoSave: getSetting('edit.autoSave'),
blockNonTodayAutoSave: getSetting('edit.blockNonTodayAutoSave'),
confirmNonTodaySave: getSetting('edit.confirmNonTodaySave'),
refreshBeforeEdit: getSetting('edit.refreshBeforeEdit')
};
return {
localSettings: { ...settings },
originalSettings: settings,
isValid: true
};
},
computed: {
hasChanges() {
return JSON.stringify(this.localSettings) !== JSON.stringify(this.originalSettings);
}
},
methods: {
save() {
Object.entries(this.localSettings).forEach(([key, value]) => {
setSetting(`edit.${key}`, value);
});
this.originalSettings = { ...this.localSettings };
this.$emit('saved');
},
reset() {
this.localSettings = { ...this.originalSettings };
}
}
};
</script>
@@ -0,0 +1,117 @@
<template>
<v-card border class="mb-4">
<v-card-title class="d-flex align-center">
<v-icon icon="mdi-account-question" class="mr-2" />
随机点名设置
</v-card-title>
<v-card-text>
<v-switch
v-model="randomPickerEnabled"
label="启用随机点名功能"
color="primary"
hide-details
class="mb-4"
/>
<v-switch
v-model="randomPickerAnimation"
label="启用随机点名动画效果"
color="primary"
hide-details
class="mb-4"
:disabled="!randomPickerEnabled"
/>
<v-slider
v-model="defaultCount"
label="默认抽取人数"
min="1"
max="10"
step="1"
thumb-label
:disabled="!randomPickerEnabled"
class="mb-4"
/>
<v-divider class="my-4" />
<div class="text-subtitle-1 mb-2">学生过滤设置</div>
<v-switch
v-model="excludeAbsent"
label="排除请假学生"
color="primary"
hide-details
class="mb-2"
:disabled="!randomPickerEnabled"
/>
<v-switch
v-model="excludeLate"
label="排除迟到学生"
color="primary"
hide-details
class="mb-2"
:disabled="!randomPickerEnabled"
/>
<v-switch
v-model="excludeExcluded"
label="排除不参与学生"
color="primary"
hide-details
class="mb-2"
:disabled="!randomPickerEnabled"
/>
<v-alert
v-if="randomPickerEnabled"
type="info"
variant="tonal"
class="mt-4"
density="compact"
>
随机点名功能将在主页显示一个按钮,点击后可以随机抽取学生。
</v-alert>
</v-card-text>
</v-card>
</template>
<script>
import { getSetting, setSetting } from '@/utils/settings';
export default {
name: 'RandomPickerSettingsCard',
data() {
return {
randomPickerEnabled: getSetting('randomPicker.enabled'),
randomPickerAnimation: getSetting('randomPicker.animation'),
defaultCount: getSetting('randomPicker.defaultCount'),
excludeAbsent: getSetting('randomPicker.excludeAbsent'),
excludeLate: getSetting('randomPicker.excludeLate'),
excludeExcluded: getSetting('randomPicker.excludeExcluded')
};
},
watch: {
randomPickerEnabled(newValue) {
setSetting('randomPicker.enabled', newValue);
},
randomPickerAnimation(newValue) {
setSetting('randomPicker.animation', newValue);
},
defaultCount(newValue) {
setSetting('randomPicker.defaultCount', newValue);
},
excludeAbsent(newValue) {
setSetting('randomPicker.excludeAbsent', newValue);
},
excludeLate(newValue) {
setSetting('randomPicker.excludeLate', newValue);
},
excludeExcluded(newValue) {
setSetting('randomPicker.excludeExcluded', newValue);
}
}
};
</script>
@@ -1,102 +1,28 @@
<template>
<settings-card
title="刷新设置"
icon="mdi-refresh-circle"
>
<v-form v-model="isValid" @submit.prevent="save">
<settings-card title="刷新设置" icon="mdi-refresh-circle">
<v-form>
<v-list>
<v-list-item>
<template #prepend>
<v-icon icon="mdi-refresh" class="mr-3" />
</template>
<v-list-item-title>自动刷新</v-list-item-title>
<v-list-item-subtitle>在后台自动刷新数据</v-list-item-subtitle>
<template #append>
<v-switch
v-model="localSettings.auto"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<setting-item setting-key="refresh.auto" title="自动刷新" /> <v-divider class="my-2" />
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-timer" class="mr-3" />
</template>
<v-list-item-title>刷新间隔</v-list-item-title>
<v-list-item-subtitle>设置自动刷新的时间间隔(分钟)</v-list-item-subtitle>
<template #append>
<v-text-field
v-model="localSettings.interval"
type="number"
min="1"
max="60"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<div class="d-flex gap-2 mt-4">
<v-btn
color="primary"
type="submit"
:disabled="!hasChanges || !isValid"
prepend-icon="mdi-content-save"
>
保存更改
</v-btn>
<v-btn
variant="outlined"
@click="reset"
:disabled="!hasChanges"
>
重置
</v-btn>
</div>
<setting-item setting-key="refresh.interval" title="刷新间隔" />
</v-list>
</v-form>
</settings-card>
</template>
<script>
import SettingsCard from '@/components/SettingsCard.vue';
import { getSetting, setSetting } from '@/utils/settings';
import SettingItem from '@/components/settings/SettingItem.vue';
export default {
name: 'RefreshSettingsCard',
components: { SettingsCard },
data() {
const settings = {
auto: getSetting('refresh.auto'),
interval: getSetting('refresh.interval')
};
return {
localSettings: { ...settings },
originalSettings: settings,
isValid: true
};
},
computed: {
hasChanges() {
return JSON.stringify(this.localSettings) !== JSON.stringify(this.originalSettings);
}
},
methods: {
save() {
Object.entries(this.localSettings).forEach(([key, value]) => {
setSetting(`refresh.${key}`, value);
});
this.originalSettings = { ...this.localSettings };
this.$emit('saved');
},
reset() {
this.localSettings = { ...this.originalSettings };
}
}
};
</script>
@@ -1,52 +1,19 @@
<template>
<settings-card title="数据源设置" icon="mdi-database" :loading="loading">
<v-form v-model="isValid" @submit.prevent="save">
<v-select
v-model="localSettings.provider"
:items="dataProviders"
label="数据提供者"
class="mb-4"
/>
<v-expand-transition>
<div v-if="localSettings.provider === 'server'">
<v-text-field
v-model="localSettings.domain"
label="服务器域名"
placeholder="例如: http://example.com"
prepend-inner-icon="mdi-web"
/>
</div>
</v-expand-transition>
<v-form>
<setting-item setting-key="server.provider" title="数据提供者" />
<v-divider class="my-2" />
<setting-item setting-key="server.domain" title="服务器域名" /> <v-divider class="my-2" />
<v-text-field
v-model="localSettings.classNumber"
label="班号"
placeholder="例如: 1 或 A"
prepend-inner-icon="mdi-account-group"
persistent-hint
/>
<div class="d-flex gap-2 mt-4">
<v-btn
color="primary"
type="submit"
:disabled="!hasChanges || !isValid"
prepend-icon="mdi-content-save"
>
保存更改
</v-btn>
<v-btn variant="outlined" @click="reset" :disabled="!hasChanges">
重置
</v-btn>
</div>
<setting-item setting-key="server.classNumber" title="班号" />
</v-form>
</settings-card>
</template>
<script>
import SettingsCard from "@/components/SettingsCard.vue";
import { getSetting, setSetting } from "@/utils/settings";
import SettingItem from "@/components/settings/SettingItem.vue";
export default {
name: "ServerSettingsCard",
components: { SettingsCard },
@@ -54,68 +21,12 @@ export default {
loading: Boolean,
},
data() {
const settings = {
provider: getSetting("server.provider"),
domain: getSetting("server.domain"),
classNumber: getSetting("server.classNumber"),
};
return {
localSettings: { ...settings },
originalSettings: settings,
isValid: true,
dataProviders: [
{ title: "服务器", value: "server" },
{ title: "本地数据库", value: "indexedDB" },
],
};
},
computed: {
hasChanges() {
return (
JSON.stringify(this.localSettings) !==
JSON.stringify(this.originalSettings)
);
},
},
methods: {
async save() {
try {
// 先保存设置
Object.entries(this.localSettings).forEach(([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.$emit('saved');
// 延迟刷新页面,让用户看到保存成功的提示
setTimeout(() => {
//window.location.reload();
}, 1000);
} catch (error) {
this.$message?.error('保存失败', error.message);
}
},
reset() {
this.localSettings = { ...this.originalSettings };
},
},
};
</script>