mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-07-03 01:39:22 +00:00
111
This commit is contained in:
parent
127347d76a
commit
6ab280e484
@ -190,7 +190,7 @@
|
|||||||
prepend-icon="mdi-content-save"
|
prepend-icon="mdi-content-save"
|
||||||
size="large"
|
size="large"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="loading || !unsavedChanges"
|
:disabled="loading"
|
||||||
@click="$emit('save')"
|
@click="$emit('save')"
|
||||||
>
|
>
|
||||||
保存名单
|
保存名单
|
||||||
@ -201,7 +201,7 @@
|
|||||||
prepend-icon="mdi-refresh"
|
prepend-icon="mdi-refresh"
|
||||||
size="large"
|
size="large"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="loading || !unsavedChanges"
|
:disabled="loading"
|
||||||
@click="$emit('reload')"
|
@click="$emit('reload')"
|
||||||
>
|
>
|
||||||
重载名单
|
重载名单
|
||||||
|
121
src/components/settings/cards/DisplaySettingsCard.vue
Normal file
121
src/components/settings/cards/DisplaySettingsCard.vue
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<settings-card
|
||||||
|
title="显示设置"
|
||||||
|
icon="mdi-monitor-dashboard"
|
||||||
|
>
|
||||||
|
<v-form v-model="isValid" @submit.prevent="save">
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-eye" class="mr-3" />
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>空科目显示</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>选择空科目的显示方式</v-list-item-subtitle>
|
||||||
|
<template #append>
|
||||||
|
<v-btn-toggle
|
||||||
|
v-model="localSettings.emptySubjectDisplay"
|
||||||
|
density="comfortable"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<v-btn value="button" :ripple="false">按钮</v-btn>
|
||||||
|
<v-btn value="card" :ripple="false">卡片</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-divider class="my-2" />
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-sort" class="mr-3" />
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>动态排序</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>根据科目动态排序</v-list-item-subtitle>
|
||||||
|
<template #append>
|
||||||
|
<v-switch
|
||||||
|
disabled
|
||||||
|
v-model="localSettings.dynamicSort"
|
||||||
|
density="comfortable"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<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>指向IslandCaller的链接</v-list-item-subtitle>
|
||||||
|
<template #append>
|
||||||
|
<v-switch
|
||||||
|
v-model="localSettings.showRandomButton"
|
||||||
|
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';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DisplaySettingsCard',
|
||||||
|
components: { SettingsCard },
|
||||||
|
data() {
|
||||||
|
const settings = {
|
||||||
|
emptySubjectDisplay: getSetting('display.emptySubjectDisplay'),
|
||||||
|
dynamicSort: getSetting('display.dynamicSort'),
|
||||||
|
showRandomButton: getSetting('display.showRandomButton')
|
||||||
|
};
|
||||||
|
|
||||||
|
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(`display.${key}`, value);
|
||||||
|
});
|
||||||
|
this.originalSettings = { ...this.localSettings };
|
||||||
|
this.$emit('saved');
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.localSettings = { ...this.originalSettings };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
135
src/components/settings/cards/EditSettingsCard.vue
Normal file
135
src/components/settings/cards/EditSettingsCard.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</settings-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SettingsCard from '@/components/SettingsCard.vue';
|
||||||
|
import { getSetting, setSetting } from '@/utils/settings';
|
||||||
|
|
||||||
|
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>
|
102
src/components/settings/cards/RefreshSettingsCard.vue
Normal file
102
src/components/settings/cards/RefreshSettingsCard.vue
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<settings-card
|
||||||
|
title="刷新设置"
|
||||||
|
icon="mdi-refresh-circle"
|
||||||
|
>
|
||||||
|
<v-form v-model="isValid" @submit.prevent="save">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</v-list>
|
||||||
|
</v-form>
|
||||||
|
</settings-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SettingsCard from '@/components/SettingsCard.vue';
|
||||||
|
import { getSetting, setSetting } from '@/utils/settings';
|
||||||
|
|
||||||
|
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>
|
95
src/components/settings/cards/ServerSettingsCard.vue
Normal file
95
src/components/settings/cards/ServerSettingsCard.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<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-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>
|
||||||
|
</v-form>
|
||||||
|
</settings-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SettingsCard from "@/components/SettingsCard.vue";
|
||||||
|
import { getSetting, setSetting } from "@/utils/settings";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ServerSettingsCard",
|
||||||
|
components: { SettingsCard },
|
||||||
|
props: {
|
||||||
|
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: {
|
||||||
|
save() {
|
||||||
|
Object.entries(this.localSettings).forEach(([key, value]) => {
|
||||||
|
setSetting(`server.${key}`, value);
|
||||||
|
});
|
||||||
|
this.originalSettings = { ...this.localSettings };
|
||||||
|
this.$emit("saved");
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.localSettings = { ...this.originalSettings };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@ -338,7 +338,7 @@ import "../styles/transitions.scss"; // 添加新的样式导入
|
|||||||
import { debounce, throttle } from "@/utils/debounce";
|
import { debounce, throttle } from "@/utils/debounce";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HomeworkBoard",
|
name: "Classworks作业板",
|
||||||
components: {
|
components: {
|
||||||
MessageLog,
|
MessageLog,
|
||||||
},
|
},
|
||||||
|
@ -13,223 +13,23 @@
|
|||||||
|
|
||||||
<v-container class="py-4">
|
<v-container class="py-4">
|
||||||
<v-row>
|
<v-row>
|
||||||
<!-- 数据源设置卡片 -->
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<settings-card
|
<server-settings-card
|
||||||
title="数据源设置"
|
|
||||||
icon="mdi-database"
|
|
||||||
:loading="loading.server"
|
:loading="loading.server"
|
||||||
>
|
@saved="onSettingsSaved"
|
||||||
<v-select
|
/>
|
||||||
v-model="settings.server.provider"
|
|
||||||
:items="dataProviders"
|
|
||||||
label="数据提供者"
|
|
||||||
class="mb-4"
|
|
||||||
/>
|
|
||||||
<v-expand-transition>
|
|
||||||
<div v-if="settings.server.provider === 'server'">
|
|
||||||
<v-text-field
|
|
||||||
v-model="settings.server.domain"
|
|
||||||
label="服务器域名"
|
|
||||||
placeholder="例如: http://example.com"
|
|
||||||
prepend-inner-icon="mdi-web"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</v-expand-transition>
|
|
||||||
|
|
||||||
<v-text-field
|
|
||||||
v-model="settings.server.classNumber"
|
|
||||||
label="班号"
|
|
||||||
placeholder="例如: 1 或 A"
|
|
||||||
prepend-inner-icon="mdi-account-group"
|
|
||||||
:hint="settings.server.provider === 'localStorage' ? '使用本地存储时也需要设置班号' : ''"
|
|
||||||
persistent-hint
|
|
||||||
/>
|
|
||||||
</settings-card>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<!-- 编辑设置卡片 -->
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<settings-card
|
<edit-settings-card @saved="onSettingsSaved" />
|
||||||
title="编辑设置"
|
|
||||||
icon="mdi-pencil-cog"
|
|
||||||
>
|
|
||||||
<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="settings.edit.autoSave"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-divider v-if="settings.edit.autoSave" class="my-2" />
|
|
||||||
|
|
||||||
<v-list-item v-if="settings.edit.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="settings.edit.blockNonTodayAutoSave"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<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="settings.edit.confirmNonTodaySave"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item> <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="settings.edit.refreshBeforeEdit"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</settings-card>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<!-- 刷新设置卡片 -->
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<settings-card
|
<refresh-settings-card @saved="onSettingsSaved" />
|
||||||
title="刷新设置"
|
|
||||||
icon="mdi-refresh-circle"
|
|
||||||
>
|
|
||||||
<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="settings.refresh.auto"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<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="settings.refresh.interval"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="60"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</settings-card>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<!-- 显示设置卡片 -->
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<settings-card
|
<display-settings-card @saved="onSettingsSaved" />
|
||||||
title="显示设置"
|
|
||||||
icon="mdi-monitor-dashboard"
|
|
||||||
>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon icon="mdi-eye" class="mr-3" />
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>空科目显示</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>选择空科目的显示方式</v-list-item-subtitle>
|
|
||||||
<template #append>
|
|
||||||
<v-btn-toggle
|
|
||||||
v-model="settings.display.emptySubjectDisplay"
|
|
||||||
density="comfortable"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<v-btn value="button" :ripple="false">
|
|
||||||
按钮
|
|
||||||
</v-btn>
|
|
||||||
<v-btn value="card" :ripple="false">
|
|
||||||
卡片
|
|
||||||
</v-btn>
|
|
||||||
</v-btn-toggle>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-divider class="my-2" />
|
|
||||||
|
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon icon="mdi-sort" class="mr-3" />
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>动态排序</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>根据科目动态排序</v-list-item-subtitle>
|
|
||||||
<template #append>
|
|
||||||
<v-switch
|
|
||||||
disabled
|
|
||||||
v-model="settings.display.dynamicSort"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<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>指向IslandCaller的链接</v-list-item-subtitle>
|
|
||||||
<template #append>
|
|
||||||
<v-switch
|
|
||||||
v-model="settings.display.showRandomButton"
|
|
||||||
density="comfortable"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</settings-card>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<!-- 开发者选项卡片 -->
|
<!-- 开发者选项卡片 -->
|
||||||
@ -335,6 +135,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useDisplay } from 'vuetify';
|
import { useDisplay } from 'vuetify';
|
||||||
|
import ServerSettingsCard from '@/components/settings/cards/ServerSettingsCard.vue';
|
||||||
|
import EditSettingsCard from '@/components/settings/cards/EditSettingsCard.vue';
|
||||||
|
import RefreshSettingsCard from '@/components/settings/cards/RefreshSettingsCard.vue';
|
||||||
|
import DisplaySettingsCard from '@/components/settings/cards/DisplaySettingsCard.vue';
|
||||||
import {
|
import {
|
||||||
getSetting,
|
getSetting,
|
||||||
setSetting,
|
setSetting,
|
||||||
@ -351,6 +155,10 @@ import dataProvider from '@/utils/dataProvider';
|
|||||||
export default {
|
export default {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
components: {
|
components: {
|
||||||
|
ServerSettingsCard,
|
||||||
|
EditSettingsCard,
|
||||||
|
RefreshSettingsCard,
|
||||||
|
DisplaySettingsCard,
|
||||||
MessageLog,
|
MessageLog,
|
||||||
SettingsCard,
|
SettingsCard,
|
||||||
StudentListCard,
|
StudentListCard,
|
||||||
@ -730,6 +538,11 @@ export default {
|
|||||||
this.settings.font.size = size - step;
|
this.settings.font.size = size - step;
|
||||||
}
|
}
|
||||||
this.handleSettingsChange(this.settings);
|
this.handleSettingsChange(this.settings);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSettingsSaved() {
|
||||||
|
this.showMessage('设置已更新', '您的设置已成功保存');
|
||||||
|
// 如果需要,可以在这里重新加载相关数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,271 +1,20 @@
|
|||||||
import axios from "axios";
|
import { serverProvider } from './providers/server';
|
||||||
import { openDB } from "idb";
|
import { indexedDBProvider } from './providers/indexedDB';
|
||||||
|
|
||||||
const DB_NAME = "HomeworkDB";
|
export const formatResponse = (data, message = null) => ({
|
||||||
const DB_VERSION = 1;
|
|
||||||
|
|
||||||
const initDB = async () => {
|
|
||||||
return openDB(DB_NAME, DB_VERSION, {
|
|
||||||
upgrade(db) {
|
|
||||||
if (!db.objectStoreNames.contains("homework")) {
|
|
||||||
db.createObjectStore("homework");
|
|
||||||
}
|
|
||||||
if (!db.objectStoreNames.contains("config")) {
|
|
||||||
db.createObjectStore("config");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatResponse = (data, message = null) => ({
|
|
||||||
success: true,
|
success: true,
|
||||||
data,
|
data,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatError = (message, code = "UNKNOWN_ERROR") => ({
|
export const formatError = (message, code = "UNKNOWN_ERROR") => ({
|
||||||
success: false,
|
success: false,
|
||||||
error: { code, message },
|
error: { code, message },
|
||||||
});
|
});
|
||||||
|
|
||||||
const providers = {
|
const providers = {
|
||||||
localStorage: {
|
server: serverProvider,
|
||||||
async loadData(key, date) {
|
indexedDB: indexedDBProvider,
|
||||||
try {
|
|
||||||
// 检查是否设置了班号
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用班号作为本地存储的前缀
|
|
||||||
const storageKey = `homework_${classNumber}_${date}`;
|
|
||||||
const rawData = localStorage.getItem(storageKey);
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
// 如果是今天的数据且没有找到,返回空结构而不是null
|
|
||||||
const today = new Date().toISOString().split("T")[0];
|
|
||||||
if (date === today) {
|
|
||||||
return formatResponse({
|
|
||||||
homework: {},
|
|
||||||
attendance: { absent: [], late: [] },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return formatError("数据不存在", "NOT_FOUND");
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatResponse(JSON.parse(rawData));
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("读取本地数据失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveData(key, data, date) {
|
|
||||||
try {
|
|
||||||
// 检查是否设置了班号
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用班号作为本地存储的前缀
|
|
||||||
const storageKey = `homework_${classNumber}_${date}`; // 使用传入的date参数
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify(data));
|
|
||||||
return formatResponse(null, "保存成功");
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("保存本地数据失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadConfig(key) {
|
|
||||||
try {
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageKey = `config_${classNumber}`;
|
|
||||||
const rawData = localStorage.getItem(storageKey);
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
return formatResponse({
|
|
||||||
studentList: [],
|
|
||||||
displayOptions: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatResponse(JSON.parse(rawData));
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("读取本地配置失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveConfig(key, config) {
|
|
||||||
try {
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageKey = `config_${classNumber}`;
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify(config));
|
|
||||||
return formatResponse(null, "保存成功");
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("保存本地配置失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
server: {
|
|
||||||
async loadData(key, date) {
|
|
||||||
try {
|
|
||||||
const res = await axios.get(`${key}/homework?date=${date}`);
|
|
||||||
if (res.data?.status === false) {
|
|
||||||
return formatError(res.data.msg || "获取数据失败", "SERVER_ERROR");
|
|
||||||
}
|
|
||||||
return formatResponse(res.data);
|
|
||||||
} catch (error) {
|
|
||||||
return formatError(
|
|
||||||
error.response?.data?.message || "服务器连接失败",
|
|
||||||
"NETWORK_ERROR"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveData(key, data, date) {
|
|
||||||
try {
|
|
||||||
// 添加date参数到URL
|
|
||||||
const url = date ? `${key}/homework?date=${date}` : `${key}/homework`;
|
|
||||||
await axios.post(url, data);
|
|
||||||
return formatResponse(null, "保存成功");
|
|
||||||
} catch (error) {
|
|
||||||
return formatError(
|
|
||||||
error.response?.data?.message || "保存失败",
|
|
||||||
"SAVE_ERROR"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadConfig(key) {
|
|
||||||
try {
|
|
||||||
const res = await axios.get(`${key}/config`);
|
|
||||||
if (res.data?.status === false) {
|
|
||||||
return formatError(res.data.msg || "获取配置失败", "SERVER_ERROR");
|
|
||||||
}
|
|
||||||
return formatResponse(res.data);
|
|
||||||
} catch (error) {
|
|
||||||
return formatError(
|
|
||||||
error.response?.data?.message || "服务器连接失败",
|
|
||||||
"NETWORK_ERROR"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveConfig(key, config) {
|
|
||||||
try {
|
|
||||||
const res = await axios.put(`${key}/config`, config);
|
|
||||||
if (res.data?.status === false) {
|
|
||||||
return formatError(res.data.msg || "保存失败", "SAVE_ERROR");
|
|
||||||
}
|
|
||||||
return formatResponse(null, "保存成功");
|
|
||||||
} catch (error) {
|
|
||||||
return formatError(
|
|
||||||
error.response?.data?.message || "保存失败",
|
|
||||||
"SAVE_ERROR"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
indexedDB: {
|
|
||||||
async loadData(key, date) {
|
|
||||||
try {
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await initDB();
|
|
||||||
const storageKey = `homework_${classNumber}_${date}`;
|
|
||||||
const data = await db.get("homework", storageKey);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
const today = new Date().toISOString().split("T")[0];
|
|
||||||
if (date === today) {
|
|
||||||
return formatResponse({
|
|
||||||
homework: {},
|
|
||||||
attendance: { absent: [], late: [] },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return formatError("数据不存在", "NOT_FOUND");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从字符串解析数据
|
|
||||||
return formatResponse(JSON.parse(data));
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("读取IndexedDB数据失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveData(key, data, date) {
|
|
||||||
try {
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await initDB();
|
|
||||||
const storageKey = `homework_${classNumber}_${date}`; // 使用传入的date参数
|
|
||||||
// 将数据序列化为字符串存储
|
|
||||||
await db.put("homework", JSON.stringify(data), storageKey);
|
|
||||||
return formatResponse(null, "保存成功");
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("保存IndexedDB数据失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadConfig(key) {
|
|
||||||
try {
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await initDB();
|
|
||||||
const storageKey = `config_${classNumber}`;
|
|
||||||
const config = await db.get("config", storageKey);
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return formatResponse({
|
|
||||||
studentList: [],
|
|
||||||
displayOptions: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从字符串解析配置
|
|
||||||
return formatResponse(JSON.parse(config));
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("读取IndexedDB配置失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveConfig(key, config) {
|
|
||||||
try {
|
|
||||||
const classNumber = key.split("/").pop();
|
|
||||||
if (!classNumber) {
|
|
||||||
return formatError("请先设置班号", "CONFIG_ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await initDB();
|
|
||||||
const storageKey = `config_${classNumber}`;
|
|
||||||
// 将配置序列化为字符串存储
|
|
||||||
await db.put("config", JSON.stringify(config), storageKey);
|
|
||||||
return formatResponse(null, "保存成功");
|
|
||||||
} catch (error) {
|
|
||||||
return formatError("保存IndexedDB配置失败:" + error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
104
src/utils/db.js
104
src/utils/db.js
@ -1,104 +0,0 @@
|
|||||||
const DB_NAME = 'homeworkboard';
|
|
||||||
const DB_VERSION = 1;
|
|
||||||
const LOG_STORE = 'message_logs';
|
|
||||||
|
|
||||||
class LogDB {
|
|
||||||
constructor() {
|
|
||||||
this.db = null;
|
|
||||||
this.ready = this.initDB();
|
|
||||||
}
|
|
||||||
|
|
||||||
async initDB() {
|
|
||||||
try {
|
|
||||||
this.db = await new Promise((resolve, reject) => {
|
|
||||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
||||||
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
request.onsuccess = () => resolve(request.result);
|
|
||||||
request.onupgradeneeded = (event) => {
|
|
||||||
const db = event.target.result;
|
|
||||||
if (!db.objectStoreNames.contains(LOG_STORE)) {
|
|
||||||
const store = db.createObjectStore(LOG_STORE, {
|
|
||||||
keyPath: 'id'
|
|
||||||
});
|
|
||||||
// 只保留时间和类型索引
|
|
||||||
store.createIndex('timestamp', 'timestamp');
|
|
||||||
store.createIndex('type', 'type');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('初始化日志数据库失败:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureDB() {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.ready;
|
|
||||||
}
|
|
||||||
if (!this.db) {
|
|
||||||
throw new Error('数据库未初始化');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addLog(message) {
|
|
||||||
await this.ensureDB();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = this.db.transaction([LOG_STORE], 'readwrite');
|
|
||||||
const store = transaction.objectStore(LOG_STORE);
|
|
||||||
|
|
||||||
const request = store.add(message);
|
|
||||||
|
|
||||||
transaction.oncomplete = () => resolve(request.result);
|
|
||||||
transaction.onerror = () => reject(transaction.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLogs(limit = 20) {
|
|
||||||
await this.ensureDB();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = this.db.transaction([LOG_STORE], 'readonly');
|
|
||||||
const store = transaction.objectStore(LOG_STORE);
|
|
||||||
const index = store.index('timestamp');
|
|
||||||
|
|
||||||
const request = index.getAll(null, limit);
|
|
||||||
|
|
||||||
request.onsuccess = () => resolve(request.result.reverse());
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteLog(messageId) {
|
|
||||||
await this.ensureDB();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = this.db.transaction([LOG_STORE], 'readwrite');
|
|
||||||
const store = transaction.objectStore(LOG_STORE);
|
|
||||||
|
|
||||||
const request = store.delete(messageId);
|
|
||||||
|
|
||||||
transaction.oncomplete = () => resolve(true);
|
|
||||||
transaction.onerror = () => reject(transaction.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearLogs() {
|
|
||||||
await this.ensureDB();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transaction = this.db.transaction([LOG_STORE], 'readwrite');
|
|
||||||
const store = transaction.objectStore(LOG_STORE);
|
|
||||||
|
|
||||||
const request = store.clear();
|
|
||||||
|
|
||||||
transaction.oncomplete = () => resolve(true);
|
|
||||||
transaction.onerror = () => reject(transaction.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new LogDB();
|
|
@ -1,6 +1,26 @@
|
|||||||
import logDB from './db';
|
|
||||||
import { getSetting } from './settings';
|
import { getSetting } from './settings';
|
||||||
|
|
||||||
|
class LogDB {
|
||||||
|
constructor() {
|
||||||
|
this.logs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async addLog(message) {
|
||||||
|
this.logs.push(message);
|
||||||
|
// 只保留最近100条消息
|
||||||
|
if (this.logs.length > 100) {
|
||||||
|
this.logs.shift();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLogs(limit = 20) {
|
||||||
|
return this.logs.slice(-limit).reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logDB = new LogDB();
|
||||||
|
|
||||||
const messages = [];
|
const messages = [];
|
||||||
let snackbarCallback = null;
|
let snackbarCallback = null;
|
||||||
let logCallback = null;
|
let logCallback = null;
|
||||||
|
104
src/utils/providers/indexedDB.js
Normal file
104
src/utils/providers/indexedDB.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { openDB } from 'idb';
|
||||||
|
import { formatResponse, formatError } from '../dataProvider';
|
||||||
|
|
||||||
|
const DB_NAME = "HomeworkDB";
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
|
||||||
|
const initDB = async () => {
|
||||||
|
return openDB(DB_NAME, DB_VERSION, {
|
||||||
|
upgrade(db) {
|
||||||
|
if (!db.objectStoreNames.contains("homework")) {
|
||||||
|
db.createObjectStore("homework");
|
||||||
|
}
|
||||||
|
if (!db.objectStoreNames.contains("config")) {
|
||||||
|
db.createObjectStore("config");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const indexedDBProvider = {
|
||||||
|
async loadData(key, date) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await initDB();
|
||||||
|
const storageKey = `homework_${classNumber}_${date}`;
|
||||||
|
const data = await db.get("homework", storageKey);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
const today = new Date().toISOString().split("T")[0];
|
||||||
|
if (date === today) {
|
||||||
|
return formatResponse({
|
||||||
|
homework: {},
|
||||||
|
attendance: { absent: [], late: [] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return formatError("数据不存在", "NOT_FOUND");
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResponse(JSON.parse(data));
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("读取IndexedDB数据失败:" + error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveData(key, data, date) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await initDB();
|
||||||
|
const storageKey = `homework_${classNumber}_${date}`;
|
||||||
|
await db.put("homework", JSON.stringify(data), storageKey);
|
||||||
|
return formatResponse(null, "保存成功");
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("保存IndexedDB数据失败:" + error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadConfig(key) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await initDB();
|
||||||
|
const storageKey = `config_${classNumber}`;
|
||||||
|
const config = await db.get("config", storageKey);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return formatResponse({
|
||||||
|
studentList: [],
|
||||||
|
displayOptions: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResponse(JSON.parse(config));
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("读取IndexedDB配置失败:" + error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveConfig(key, config) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await initDB();
|
||||||
|
const storageKey = `config_${classNumber}`;
|
||||||
|
await db.put("config", JSON.stringify(config), storageKey);
|
||||||
|
return formatResponse(null, "保存成功");
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("保存IndexedDB配置失败:" + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
83
src/utils/providers/localStorage.js
Normal file
83
src/utils/providers/localStorage.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { formatResponse, formatError } from '../dataProvider';
|
||||||
|
|
||||||
|
export const localStorageProvider = {
|
||||||
|
async loadData(key, date) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = `homework_${classNumber}_${date}`;
|
||||||
|
const rawData = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
if (!rawData) {
|
||||||
|
const today = new Date().toISOString().split("T")[0];
|
||||||
|
if (date === today) {
|
||||||
|
return formatResponse({
|
||||||
|
homework: {},
|
||||||
|
attendance: { absent: [], late: [] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return formatError("数据不存在", "NOT_FOUND");
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResponse(JSON.parse(rawData));
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("读取本地数据失败:" + error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveData(key, data, date) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = `homework_${classNumber}_${date}`; // 使用传入的date参数
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(data));
|
||||||
|
return formatResponse(null, "保存成功");
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("保存本地数据失败:" + error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadConfig(key) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = `config_${classNumber}`;
|
||||||
|
const rawData = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
if (!rawData) {
|
||||||
|
return formatResponse({
|
||||||
|
studentList: [],
|
||||||
|
displayOptions: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResponse(JSON.parse(rawData));
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("读取本地配置失败:" + error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveConfig(key, config) {
|
||||||
|
try {
|
||||||
|
const classNumber = key.split("/").pop();
|
||||||
|
if (!classNumber) {
|
||||||
|
return formatError("请先设置班号", "CONFIG_ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = `config_${classNumber}`;
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(config));
|
||||||
|
return formatResponse(null, "保存成功");
|
||||||
|
} catch (error) {
|
||||||
|
return formatError("保存本地配置失败:" + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
63
src/utils/providers/server.js
Normal file
63
src/utils/providers/server.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { formatResponse, formatError } from '../dataProvider';
|
||||||
|
|
||||||
|
export const serverProvider = {
|
||||||
|
async loadData(key, date) {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${key}/homework?date=${date}`);
|
||||||
|
if (res.data?.status === false) {
|
||||||
|
return formatError(res.data.msg || "获取数据失败", "SERVER_ERROR");
|
||||||
|
}
|
||||||
|
return formatResponse(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
return formatError(
|
||||||
|
error.response?.data?.message || "服务器连接失败",
|
||||||
|
"NETWORK_ERROR"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveData(key, data, date) {
|
||||||
|
try {
|
||||||
|
// 添加date参数到URL
|
||||||
|
const url = date ? `${key}/homework?date=${date}` : `${key}/homework`;
|
||||||
|
await axios.post(url, data);
|
||||||
|
return formatResponse(null, "保存成功");
|
||||||
|
} catch (error) {
|
||||||
|
return formatError(
|
||||||
|
error.response?.data?.message || "保存失败",
|
||||||
|
"SAVE_ERROR"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadConfig(key) {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${key}/config`);
|
||||||
|
if (res.data?.status === false) {
|
||||||
|
return formatError(res.data.msg || "获取配置失败", "SERVER_ERROR");
|
||||||
|
}
|
||||||
|
return formatResponse(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
return formatError(
|
||||||
|
error.response?.data?.message || "服务器连接失败",
|
||||||
|
"NETWORK_ERROR"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveConfig(key, config) {
|
||||||
|
try {
|
||||||
|
const res = await axios.put(`${key}/config`, config);
|
||||||
|
if (res.data?.status === false) {
|
||||||
|
return formatError(res.data.msg || "保存失败", "SAVE_ERROR");
|
||||||
|
}
|
||||||
|
return formatResponse(null, "保存成功");
|
||||||
|
} catch (error) {
|
||||||
|
return formatError(
|
||||||
|
error.response?.data?.message || "保存失败",
|
||||||
|
"SAVE_ERROR"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -90,7 +90,7 @@ const settingsDefinitions = {
|
|||||||
type: "string",
|
type: "string",
|
||||||
default: "indexedDB",
|
default: "indexedDB",
|
||||||
validate: (value) =>
|
validate: (value) =>
|
||||||
["server", "localStorage", "indexedDB"].includes(value),
|
["server", "indexedDB"].includes(value),
|
||||||
description: "数据提供者,用于决定数据存储方式",
|
description: "数据提供者,用于决定数据存储方式",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user