mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-07-07 04:29:24 +00:00
Update .hintrc to disable no-inline-styles hint, modify GlobalMessage.vue to reposition snackbar, remove AppFooter from default layout, and enhance index.vue with a floating toolbar and ICP component. Clean up unused imports in settings.vue.
This commit is contained in:
parent
965f36cbf5
commit
53ed1f556f
5
.hintrc
5
.hintrc
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"development"
|
"development"
|
||||||
]
|
],
|
||||||
|
"hints": {
|
||||||
|
"no-inline-styles": "off"
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,5 +14,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="display: none;">浙ICP备2024068645号-4</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
77
src/components/FloatingICP.vue
Normal file
77
src/components/FloatingICP.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<v-slide-x-transition>
|
||||||
|
<v-card
|
||||||
|
class="floating-icp"
|
||||||
|
elevation="2"
|
||||||
|
rounded="pill"
|
||||||
|
variant="tonal"
|
||||||
|
color="surface-variant"
|
||||||
|
@mouseenter="isHovered = true"
|
||||||
|
@mouseleave="isHovered = false"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
class="icp-button"
|
||||||
|
href="https://beian.miit.gov.cn/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
icon="mdi-shield-check"
|
||||||
|
size="small"
|
||||||
|
:class="{ 'rotate-icon': isHovered }"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
<span class="text-caption">浙ICP备2024068645号</span>
|
||||||
|
</v-btn>
|
||||||
|
</v-card>
|
||||||
|
</v-slide-x-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FloatingICP',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isHovered: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.floating-icp {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
z-index: 100;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-icp:hover {
|
||||||
|
transform: translateX(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icp-button {
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 32px;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotate-icon {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
transition: transform 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.floating-icp {
|
||||||
|
right: 16px;
|
||||||
|
bottom: 80px; /* 避免与其他悬浮组件重叠 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.icp-button {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
203
src/components/FloatingToolbar.vue
Normal file
203
src/components/FloatingToolbar.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<v-slide-y-transition>
|
||||||
|
<v-card
|
||||||
|
class="floating-toolbar"
|
||||||
|
elevation="4"
|
||||||
|
rounded="xl"
|
||||||
|
:class="{ 'toolbar-expanded': isExpanded }"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<v-btn-group variant="text" class="toolbar-buttons">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-chevron-left"
|
||||||
|
variant="text"
|
||||||
|
@click="$emit('prev-day')"
|
||||||
|
:title="'查看昨天'"
|
||||||
|
class="toolbar-btn"
|
||||||
|
v-ripple
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-format-font-size-decrease"
|
||||||
|
variant="text"
|
||||||
|
@click="$emit('zoom', 'out')"
|
||||||
|
:title="'缩小字体'"
|
||||||
|
class="toolbar-btn"
|
||||||
|
v-ripple
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-format-font-size-increase"
|
||||||
|
variant="text"
|
||||||
|
@click="$emit('zoom', 'up')"
|
||||||
|
:title="'放大字体'"
|
||||||
|
class="toolbar-btn"
|
||||||
|
v-ripple
|
||||||
|
/>
|
||||||
|
<v-menu location="top" :close-on-content-click="false">
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-calendar"
|
||||||
|
variant="text"
|
||||||
|
v-bind="props"
|
||||||
|
:title="'选择日期'"
|
||||||
|
class="toolbar-btn"
|
||||||
|
v-ripple
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<v-card border class="date-picker-card">
|
||||||
|
<v-date-picker
|
||||||
|
:model-value="selectedDate"
|
||||||
|
color="primary"
|
||||||
|
@update:model-value="handleDateSelect"
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-refresh"
|
||||||
|
variant="text"
|
||||||
|
:loading="loading"
|
||||||
|
@click="$emit('refresh')"
|
||||||
|
:title="'刷新数据'"
|
||||||
|
class="toolbar-btn"
|
||||||
|
v-ripple
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="!isToday"
|
||||||
|
icon="mdi-chevron-right"
|
||||||
|
variant="text"
|
||||||
|
@click="$emit('next-day')"
|
||||||
|
:title="'查看明天'"
|
||||||
|
class="toolbar-btn"
|
||||||
|
v-ripple
|
||||||
|
/>
|
||||||
|
</v-btn-group>
|
||||||
|
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</v-slide-y-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "FloatingToolbar",
|
||||||
|
props: {
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
unreadCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
selectedDate: {
|
||||||
|
type: [String, Date],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isToday: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExpanded: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDateSelect(newDate) {
|
||||||
|
this.$emit("date-select", newDate);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.floating-toolbar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 100;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
background: rgba(255, 255, 255, 0.7) !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-toolbar:hover {
|
||||||
|
transform: translateX(-50%) translateY(-4px);
|
||||||
|
background: rgba(255, 255, 255, 0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3) !important;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.floating-toolbar {
|
||||||
|
bottom: 16px;
|
||||||
|
width: 95%;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-buttons {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode support */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.floating-toolbar {
|
||||||
|
background: rgba(30, 30, 30, 0.7) !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-toolbar:hover {
|
||||||
|
background: rgba(30, 30, 30, 0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-card {
|
||||||
|
background: rgba(30, 30, 30, 0.9) !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -3,7 +3,7 @@
|
|||||||
v-model="snackbar"
|
v-model="snackbar"
|
||||||
:color="colors[message?.type] || colors.info"
|
:color="colors[message?.type] || colors.info"
|
||||||
:timeout="2000"
|
:timeout="2000"
|
||||||
location="bottom"
|
location="top right"
|
||||||
multi-line
|
multi-line
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
>
|
>
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
<v-main>
|
<v-main>
|
||||||
<router-view />
|
<router-view />
|
||||||
</v-main>
|
</v-main>
|
||||||
|
|
||||||
<AppFooter />
|
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app-bar class="no-select">
|
<v-app-bar class="no-select">
|
||||||
|
|
||||||
|
|
||||||
<v-app-bar-title>
|
<v-app-bar-title>
|
||||||
{{ state.classNumber }} - {{ titleText }}
|
{{ state.classNumber }} - {{ titleText }}
|
||||||
</v-app-bar-title>
|
</v-app-bar-title>
|
||||||
@ -9,37 +7,7 @@
|
|||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<namespace-access />
|
<namespace-access /> <v-btn
|
||||||
<v-btn
|
|
||||||
icon="mdi-format-font-size-decrease"
|
|
||||||
variant="text"
|
|
||||||
@click="zoom('out')"
|
|
||||||
/>
|
|
||||||
<v-btn
|
|
||||||
icon="mdi-format-font-size-increase"
|
|
||||||
variant="text"
|
|
||||||
@click="zoom('up')"
|
|
||||||
/>
|
|
||||||
<v-menu v-model="state.datePickerDialog" :close-on-content-click="false">
|
|
||||||
<template #activator="{ props }">
|
|
||||||
<v-btn icon="mdi-calendar" variant="text" v-bind="props" />
|
|
||||||
</template>
|
|
||||||
<v-card border>
|
|
||||||
<v-date-picker
|
|
||||||
v-model="state.selectedDateObj"
|
|
||||||
:model-value="state.selectedDateObj"
|
|
||||||
color="primary"
|
|
||||||
@update:model-value="handleDateSelect"
|
|
||||||
/>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
<v-btn
|
|
||||||
icon="mdi-refresh"
|
|
||||||
variant="text"
|
|
||||||
:loading="loading.download"
|
|
||||||
@click="downloadData"
|
|
||||||
/>
|
|
||||||
<v-btn
|
|
||||||
icon="mdi-bell"
|
icon="mdi-bell"
|
||||||
variant="text"
|
variant="text"
|
||||||
:badge="unreadCount || undefined"
|
:badge="unreadCount || undefined"
|
||||||
@ -559,6 +527,24 @@
|
|||||||
|
|
||||||
<message-log ref="messageLog" />
|
<message-log ref="messageLog" />
|
||||||
|
|
||||||
|
<!-- 添加悬浮工具栏 -->
|
||||||
|
<floating-toolbar
|
||||||
|
:loading="loading.download"
|
||||||
|
:unread-count="unreadCount"
|
||||||
|
:selected-date="state.selectedDateObj"
|
||||||
|
:is-today="isToday"
|
||||||
|
@zoom="zoom"
|
||||||
|
@refresh="downloadData"
|
||||||
|
@open-messages="$refs.messageLog.drawer = true"
|
||||||
|
@open-settings="$router.push('/settings')"
|
||||||
|
@date-select="handleDateSelect"
|
||||||
|
@prev-day="navigateDay(-1)"
|
||||||
|
@next-day="navigateDay(1)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 添加ICP备案悬浮组件 -->
|
||||||
|
<FloatingICP />
|
||||||
|
|
||||||
<!-- 添加确认对话框 -->
|
<!-- 添加确认对话框 -->
|
||||||
<v-dialog v-model="confirmDialog.show" max-width="400">
|
<v-dialog v-model="confirmDialog.show" max-width="400">
|
||||||
<v-card>
|
<v-card>
|
||||||
@ -627,13 +613,15 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog><br/><br/><br/><br/><br/><br/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MessageLog from "@/components/MessageLog.vue";
|
import MessageLog from "@/components/MessageLog.vue";
|
||||||
import RandomPicker from "@/components/RandomPicker.vue"; // 导入随机点名组件
|
import RandomPicker from "@/components/RandomPicker.vue";
|
||||||
import NamespaceAccess from "@/components/NamespaceAccess.vue";
|
import NamespaceAccess from "@/components/NamespaceAccess.vue";
|
||||||
|
import FloatingToolbar from "@/components/FloatingToolbar.vue";
|
||||||
|
import FloatingICP from "@/components/FloatingICP.vue";
|
||||||
import dataProvider from "@/utils/dataProvider";
|
import dataProvider from "@/utils/dataProvider";
|
||||||
import {
|
import {
|
||||||
getSetting,
|
getSetting,
|
||||||
@ -643,7 +631,7 @@ import {
|
|||||||
} from "@/utils/settings";
|
} from "@/utils/settings";
|
||||||
import { useDisplay } from "vuetify";
|
import { useDisplay } from "vuetify";
|
||||||
import "../styles/index.scss";
|
import "../styles/index.scss";
|
||||||
import "../styles/transitions.scss"; // 添加新的样式导入
|
import "../styles/transitions.scss";
|
||||||
import "../styles/global.scss";
|
import "../styles/global.scss";
|
||||||
import { pinyin } from "pinyin-pro";
|
import { pinyin } from "pinyin-pro";
|
||||||
import { debounce, throttle } from "@/utils/debounce";
|
import { debounce, throttle } from "@/utils/debounce";
|
||||||
@ -653,8 +641,10 @@ export default {
|
|||||||
name: "Classworks 作业板",
|
name: "Classworks 作业板",
|
||||||
components: {
|
components: {
|
||||||
MessageLog,
|
MessageLog,
|
||||||
RandomPicker, // 注册随机点名组件
|
RandomPicker,
|
||||||
NamespaceAccess,
|
NamespaceAccess,
|
||||||
|
FloatingToolbar,
|
||||||
|
FloatingICP,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -675,7 +665,7 @@ export default {
|
|||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogTitle: "",
|
dialogTitle: "",
|
||||||
textarea: "",
|
textarea: "",
|
||||||
dateString: "", // 从 state 内统一管理日期
|
dateString: "",
|
||||||
synced: false,
|
synced: false,
|
||||||
attendDialogVisible: false,
|
attendDialogVisible: false,
|
||||||
contentStyle: { "font-size": `${getSetting("font.size")}px` },
|
contentStyle: { "font-size": `${getSetting("font.size")}px` },
|
||||||
@ -736,7 +726,6 @@ export default {
|
|||||||
},
|
},
|
||||||
attendanceSearch: "",
|
attendanceSearch: "",
|
||||||
attendanceFilter: [],
|
attendanceFilter: [],
|
||||||
// 添加URL配置确认对话框
|
|
||||||
urlConfigDialog: {
|
urlConfigDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
config: null,
|
config: null,
|
||||||
@ -744,7 +733,6 @@ export default {
|
|||||||
validSettings: {},
|
validSettings: {},
|
||||||
confirmHandler: null,
|
confirmHandler: null,
|
||||||
cancelHandler: null,
|
cancelHandler: null,
|
||||||
// 添加图标映射数据
|
|
||||||
icons: {},
|
icons: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -832,15 +820,12 @@ export default {
|
|||||||
return this.state.dateString === today;
|
return this.state.dateString === today;
|
||||||
},
|
},
|
||||||
canAutoSave() {
|
canAutoSave() {
|
||||||
// 只考虑自动保存开关和非当天限制
|
|
||||||
return this.autoSave && (!this.blockNonTodayAutoSave || this.isToday);
|
return this.autoSave && (!this.blockNonTodayAutoSave || this.isToday);
|
||||||
},
|
},
|
||||||
needConfirmSave() {
|
needConfirmSave() {
|
||||||
// 只在非今天且开启了确认选项时需要确认
|
|
||||||
return !this.isToday && this.confirmNonTodaySave;
|
return !this.isToday && this.confirmNonTodaySave;
|
||||||
},
|
},
|
||||||
shouldShowBlockedMessage() {
|
shouldShowBlockedMessage() {
|
||||||
// 只在非今天且开启了自动保存和禁止非当天自动保存时提示
|
|
||||||
return !this.isToday && this.autoSave && this.blockNonTodayAutoSave;
|
return !this.isToday && this.autoSave && this.blockNonTodayAutoSave;
|
||||||
},
|
},
|
||||||
refreshBeforeEdit() {
|
refreshBeforeEdit() {
|
||||||
@ -882,7 +867,6 @@ export default {
|
|||||||
filteredStudents() {
|
filteredStudents() {
|
||||||
let students = [...this.state.studentList];
|
let students = [...this.state.studentList];
|
||||||
|
|
||||||
// 应用搜索过滤
|
|
||||||
if (this.attendanceSearch) {
|
if (this.attendanceSearch) {
|
||||||
const searchTerm = this.attendanceSearch.toLowerCase();
|
const searchTerm = this.attendanceSearch.toLowerCase();
|
||||||
students = students.filter((student) =>
|
students = students.filter((student) =>
|
||||||
@ -890,7 +874,6 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用状态过滤
|
|
||||||
if (this.attendanceFilter && this.attendanceFilter.length > 0) {
|
if (this.attendanceFilter && this.attendanceFilter.length > 0) {
|
||||||
students = students.filter((student) => {
|
students = students.filter((student) => {
|
||||||
const index = this.state.studentList.indexOf(student);
|
const index = this.state.studentList.indexOf(student);
|
||||||
@ -915,7 +898,6 @@ export default {
|
|||||||
return students;
|
return students;
|
||||||
},
|
},
|
||||||
extractedSurnames() {
|
extractedSurnames() {
|
||||||
// 从学生名单中提取姓氏并统计数量
|
|
||||||
if (!this.state.studentList || this.state.studentList.length === 0) {
|
if (!this.state.studentList || this.state.studentList.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -924,7 +906,6 @@ export default {
|
|||||||
|
|
||||||
this.state.studentList.forEach((student) => {
|
this.state.studentList.forEach((student) => {
|
||||||
if (student && student.length > 0) {
|
if (student && student.length > 0) {
|
||||||
// 中文姓名通常姓在前,取第一个字作为姓氏
|
|
||||||
const surname = student.charAt(0);
|
const surname = student.charAt(0);
|
||||||
if (surnameMap.has(surname)) {
|
if (surnameMap.has(surname)) {
|
||||||
surnameMap.set(surname, surnameMap.get(surname) + 1);
|
surnameMap.set(surname, surnameMap.get(surname) + 1);
|
||||||
@ -934,7 +915,6 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 转换为数组并按拼音排序
|
|
||||||
return Array.from(surnameMap.entries())
|
return Array.from(surnameMap.entries())
|
||||||
.map(([name, count]) => ({ name, count }))
|
.map(([name, count]) => ({ name, count }))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@ -965,9 +945,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
// 创建防抖的上传函数
|
|
||||||
this.debouncedUpload = debounce(this.uploadData, 2000);
|
this.debouncedUpload = debounce(this.uploadData, 2000);
|
||||||
// 创建节流的重排函数
|
|
||||||
this.throttledReflow = throttle(() => {
|
this.throttledReflow = throttle(() => {
|
||||||
if (this.$refs.gridContainer) {
|
if (this.$refs.gridContainer) {
|
||||||
this.optimizeGridLayout(this.sortedItems);
|
this.optimizeGridLayout(this.sortedItems);
|
||||||
@ -984,7 +962,6 @@ export default {
|
|||||||
this.updateSettings();
|
this.updateSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听全屏变化事件
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"fullscreenchange",
|
"fullscreenchange",
|
||||||
this.fullscreenChangeHandler
|
this.fullscreenChangeHandler
|
||||||
@ -1002,10 +979,8 @@ export default {
|
|||||||
this.fullscreenChangeHandler
|
this.fullscreenChangeHandler
|
||||||
);
|
);
|
||||||
|
|
||||||
// 检查URL哈希值,如果包含#pick则自动打开随机点名
|
|
||||||
this.checkHashForRandomPicker();
|
this.checkHashForRandomPicker();
|
||||||
|
|
||||||
// 添加哈希变化监听器
|
|
||||||
window.addEventListener("hashchange", this.checkHashForRandomPicker);
|
window.addEventListener("hashchange", this.checkHashForRandomPicker);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("初始化失败:", err);
|
console.error("初始化失败:", err);
|
||||||
@ -1018,11 +993,9 @@ export default {
|
|||||||
this.unwatchSettings();
|
this.unwatchSettings();
|
||||||
}
|
}
|
||||||
if (this.state.refreshInterval) {
|
if (this.state.refreshInterval) {
|
||||||
// 注意刷新间隔存放在 state 内
|
|
||||||
clearInterval(this.state.refreshInterval);
|
clearInterval(this.state.refreshInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除全屏变化事件监听
|
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
"fullscreenchange",
|
"fullscreenchange",
|
||||||
this.fullscreenChangeHandler
|
this.fullscreenChangeHandler
|
||||||
@ -1040,12 +1013,10 @@ export default {
|
|||||||
this.fullscreenChangeHandler
|
this.fullscreenChangeHandler
|
||||||
);
|
);
|
||||||
|
|
||||||
// 移除哈希变化监听器
|
|
||||||
window.removeEventListener("hashchange", this.checkHashForRandomPicker);
|
window.removeEventListener("hashchange", this.checkHashForRandomPicker);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
// 添加新的日期辅助方法
|
|
||||||
ensureDate(dateInput) {
|
ensureDate(dateInput) {
|
||||||
if (dateInput instanceof Date) {
|
if (dateInput instanceof Date) {
|
||||||
return dateInput;
|
return dateInput;
|
||||||
@ -1056,7 +1027,7 @@ export default {
|
|||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Date(); // 如果无法解析,返回当前日期
|
return new Date();
|
||||||
},
|
},
|
||||||
|
|
||||||
formatDate(dateInput) {
|
formatDate(dateInput) {
|
||||||
@ -1072,18 +1043,14 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async initializeData() {
|
async initializeData() {
|
||||||
// 尝试从URL读取配置
|
|
||||||
const configApplied = await this.parseUrlConfig();
|
const configApplied = await this.parseUrlConfig();
|
||||||
|
|
||||||
// 从 URL 获取日期,如果没有则使用今天的日期
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const dateFromUrl = urlParams.get("date");
|
const dateFromUrl = urlParams.get("date");
|
||||||
const today = this.getToday();
|
const today = this.getToday();
|
||||||
|
|
||||||
// 确保日期格式正确
|
|
||||||
let currentDate = today;
|
let currentDate = today;
|
||||||
if (dateFromUrl) {
|
if (dateFromUrl) {
|
||||||
// 处理yyyymmdd格式的日期字符串
|
|
||||||
if (/^\d{8}$/.test(dateFromUrl)) {
|
if (/^\d{8}$/.test(dateFromUrl)) {
|
||||||
const year = dateFromUrl.substring(0, 4);
|
const year = dateFromUrl.substring(0, 4);
|
||||||
const month = dateFromUrl.substring(4, 6);
|
const month = dateFromUrl.substring(4, 6);
|
||||||
@ -1092,7 +1059,6 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
currentDate = new Date(dateFromUrl);
|
currentDate = new Date(dateFromUrl);
|
||||||
}
|
}
|
||||||
// 确保日期有效,无效则使用今天的日期
|
|
||||||
if (isNaN(currentDate.getTime())) {
|
if (isNaN(currentDate.getTime())) {
|
||||||
currentDate = today;
|
currentDate = today;
|
||||||
}
|
}
|
||||||
@ -1100,10 +1066,9 @@ export default {
|
|||||||
|
|
||||||
this.state.dateString = this.formatDate(currentDate);
|
this.state.dateString = this.formatDate(currentDate);
|
||||||
this.state.selectedDate = this.state.dateString;
|
this.state.selectedDate = this.state.dateString;
|
||||||
this.state.selectedDateObj = currentDate; // 设置日期对象
|
this.state.selectedDateObj = currentDate;
|
||||||
this.state.isToday =
|
this.state.isToday =
|
||||||
this.formatDate(currentDate) === this.formatDate(today);
|
this.formatDate(currentDate) === this.formatDate(today);
|
||||||
// 如果没有从URL应用配置,使用本地设置
|
|
||||||
if (!configApplied) {
|
if (!configApplied) {
|
||||||
this.provider = getSetting("server.provider");
|
this.provider = getSetting("server.provider");
|
||||||
const classNum = getSetting("server.classNumber");
|
const classNum = getSetting("server.classNumber");
|
||||||
@ -1126,7 +1091,6 @@ export default {
|
|||||||
if (response.error.code === "NOT_FOUND") {
|
if (response.error.code === "NOT_FOUND") {
|
||||||
this.state.showNoDataMessage = true;
|
this.state.showNoDataMessage = true;
|
||||||
this.state.noDataMessage = response.error.message;
|
this.state.noDataMessage = response.error.message;
|
||||||
// 确保数据结构完整
|
|
||||||
this.state.boardData = {
|
this.state.boardData = {
|
||||||
homework: {},
|
homework: {},
|
||||||
attendance: { absent: [], late: [], exclude: [] },
|
attendance: { absent: [], late: [], exclude: [] },
|
||||||
@ -1135,7 +1099,6 @@ export default {
|
|||||||
throw new Error(response.error.message);
|
throw new Error(response.error.message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 确保数据结构完整
|
|
||||||
this.state.boardData = {
|
this.state.boardData = {
|
||||||
homework: response.homework || {},
|
homework: response.homework || {},
|
||||||
attendance: {
|
attendance: {
|
||||||
@ -1149,7 +1112,6 @@ export default {
|
|||||||
this.$message.success("下载成功", "数据已更新");
|
this.$message.success("下载成功", "数据已更新");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 发生错误时也要确保数据结构完整
|
|
||||||
this.state.boardData = {
|
this.state.boardData = {
|
||||||
homework: {},
|
homework: {},
|
||||||
attendance: { absent: [], late: [], exclude: [] },
|
attendance: { absent: [], late: [], exclude: [] },
|
||||||
@ -1161,7 +1123,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async trySave(isAutoSave = false) {
|
async trySave(isAutoSave = false) {
|
||||||
// 如果是自动保存但不满足自动保存条件
|
|
||||||
if (isAutoSave && !this.canAutoSave) {
|
if (isAutoSave && !this.canAutoSave) {
|
||||||
if (this.shouldShowBlockedMessage) {
|
if (this.shouldShowBlockedMessage) {
|
||||||
this.showMessage(
|
this.showMessage(
|
||||||
@ -1173,7 +1134,6 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果需要确认且不是自动保存
|
|
||||||
if (!isAutoSave && this.needConfirmSave) {
|
if (!isAutoSave && this.needConfirmSave) {
|
||||||
try {
|
try {
|
||||||
await this.showConfirmDialog();
|
await this.showConfirmDialog();
|
||||||
@ -1182,7 +1142,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试保存
|
|
||||||
try {
|
try {
|
||||||
await this.uploadData();
|
await this.uploadData();
|
||||||
return true;
|
return true;
|
||||||
@ -1199,16 +1158,13 @@ export default {
|
|||||||
const originalContent =
|
const originalContent =
|
||||||
this.state.boardData.homework[this.currentEditSubject]?.content || "";
|
this.state.boardData.homework[this.currentEditSubject]?.content || "";
|
||||||
|
|
||||||
// 如果内容发生变化(包括清空),就视为修改
|
|
||||||
if (content !== originalContent.trim()) {
|
if (content !== originalContent.trim()) {
|
||||||
// 无论内容是否为空,都保留科目结构
|
|
||||||
this.state.boardData.homework[this.currentEditSubject] = {
|
this.state.boardData.homework[this.currentEditSubject] = {
|
||||||
content: content,
|
content: content,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state.synced = false;
|
this.state.synced = false;
|
||||||
|
|
||||||
// 处理自动保存
|
|
||||||
if (this.autoSave) {
|
if (this.autoSave) {
|
||||||
await this.trySave(true);
|
await this.trySave(true);
|
||||||
}
|
}
|
||||||
@ -1240,11 +1196,9 @@ export default {
|
|||||||
async loadConfig() {
|
async loadConfig() {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
// Try to get student list from the dedicated key
|
|
||||||
const response = await dataProvider.loadData("classworks-list-main");
|
const response = await dataProvider.loadData("classworks-list-main");
|
||||||
|
|
||||||
if (response.success != false && Array.isArray(response)) {
|
if (response.success != false && Array.isArray(response)) {
|
||||||
// Transform the data into a simple list of names
|
|
||||||
this.state.studentList = response.map(
|
this.state.studentList = response.map(
|
||||||
(student) => student.name
|
(student) => student.name
|
||||||
);
|
);
|
||||||
@ -1277,7 +1231,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentEditSubject = subject;
|
this.currentEditSubject = subject;
|
||||||
// 如果是新科目,需要创建对应的精简数据结构
|
|
||||||
if (!this.state.boardData.homework[subject]) {
|
if (!this.state.boardData.homework[subject]) {
|
||||||
this.state.boardData.homework[subject] = {
|
this.state.boardData.homework[subject] = {
|
||||||
content: "",
|
content: "",
|
||||||
@ -1371,7 +1324,6 @@ export default {
|
|||||||
}
|
}
|
||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
this.state.refreshInterval = setInterval(() => {
|
this.state.refreshInterval = setInterval(() => {
|
||||||
// 检查是否应该跳过刷新
|
|
||||||
if (!this.shouldSkipRefresh()) {
|
if (!this.shouldSkipRefresh()) {
|
||||||
this.downloadData();
|
this.downloadData();
|
||||||
}
|
}
|
||||||
@ -1379,27 +1331,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 新增方法:检查是否应该跳过自动刷新
|
|
||||||
shouldSkipRefresh() {
|
shouldSkipRefresh() {
|
||||||
// 如果对话框打开,跳过刷新
|
|
||||||
if (this.state.dialogVisible) return true;
|
if (this.state.dialogVisible) return true;
|
||||||
|
|
||||||
// 如果出勤对话框打开,跳过刷新
|
|
||||||
if (this.state.attendanceDialog) return true;
|
if (this.state.attendanceDialog) return true;
|
||||||
|
|
||||||
// 如果确认对话框打开,跳过刷新
|
|
||||||
if (this.confirmDialog.show) return true;
|
if (this.confirmDialog.show) return true;
|
||||||
|
|
||||||
// 如果日期选择器打开,跳过刷新
|
|
||||||
if (this.state.datePickerDialog) return true;
|
if (this.state.datePickerDialog) return true;
|
||||||
|
|
||||||
// 如果正在上传或下载数据,跳过刷新
|
|
||||||
if (this.loading.upload || this.loading.download) return true;
|
if (this.loading.upload || this.loading.download) return true;
|
||||||
|
|
||||||
// 如果数据未同步(有未保存的更改),跳过刷新
|
|
||||||
if (!this.state.synced) return true;
|
if (!this.state.synced) return true;
|
||||||
|
|
||||||
// 没有特殊状态,可以刷新
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1417,7 +1361,6 @@ export default {
|
|||||||
const selectedDate = this.ensureDate(newDate);
|
const selectedDate = this.ensureDate(newDate);
|
||||||
const formattedDate = this.formatDate(selectedDate);
|
const formattedDate = this.formatDate(selectedDate);
|
||||||
|
|
||||||
// 只有当日期真正改变时才更新
|
|
||||||
if (this.state.dateString !== formattedDate) {
|
if (this.state.dateString !== formattedDate) {
|
||||||
this.state.dateString = formattedDate;
|
this.state.dateString = formattedDate;
|
||||||
this.state.selectedDate = formattedDate;
|
this.state.selectedDate = formattedDate;
|
||||||
@ -1425,7 +1368,6 @@ export default {
|
|||||||
this.state.isToday =
|
this.state.isToday =
|
||||||
formattedDate === this.formatDate(this.getToday());
|
formattedDate === this.formatDate(this.getToday());
|
||||||
|
|
||||||
// 使用 replace 而不是 push 来避免创建新的历史记录
|
|
||||||
this.$router
|
this.$router
|
||||||
.replace({
|
.replace({
|
||||||
query: { date: formattedDate },
|
query: { date: formattedDate },
|
||||||
@ -1440,11 +1382,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
optimizeGridLayout(items) {
|
optimizeGridLayout(items) {
|
||||||
// 设置最大列数
|
|
||||||
const maxColumns = Math.min(3, Math.floor(window.innerWidth / 300));
|
const maxColumns = Math.min(3, Math.floor(window.innerWidth / 300));
|
||||||
if (maxColumns <= 1) return items;
|
if (maxColumns <= 1) return items;
|
||||||
|
|
||||||
// 使用贪心算法分配
|
|
||||||
const columns = Array.from({ length: maxColumns }, () => ({
|
const columns = Array.from({ length: maxColumns }, () => ({
|
||||||
height: 0,
|
height: 0,
|
||||||
items: [],
|
items: [],
|
||||||
@ -1459,7 +1399,6 @@ export default {
|
|||||||
columns[shortestColumn].height += item.rowSpan;
|
columns[shortestColumn].height += item.rowSpan;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 展平结果并添加顺序
|
|
||||||
return columns
|
return columns
|
||||||
.flatMap((col) => col.items)
|
.flatMap((col) => col.items)
|
||||||
.map((item, index) => ({
|
.map((item, index) => ({
|
||||||
@ -1566,7 +1505,6 @@ export default {
|
|||||||
|
|
||||||
setPresent(index) {
|
setPresent(index) {
|
||||||
const student = this.state.studentList[index];
|
const student = this.state.studentList[index];
|
||||||
// 从所有状态列表中移除该学生
|
|
||||||
this.state.boardData.attendance.absent =
|
this.state.boardData.attendance.absent =
|
||||||
this.state.boardData.attendance.absent.filter(
|
this.state.boardData.attendance.absent.filter(
|
||||||
(name) => name !== student
|
(name) => name !== student
|
||||||
@ -1582,27 +1520,21 @@ export default {
|
|||||||
|
|
||||||
setAbsent(index) {
|
setAbsent(index) {
|
||||||
const student = this.state.studentList[index];
|
const student = this.state.studentList[index];
|
||||||
// 先从所有状态列表中移除该学生
|
|
||||||
this.setPresent(index);
|
this.setPresent(index);
|
||||||
// 然后添加到请假列表
|
|
||||||
this.state.boardData.attendance.absent.push(student);
|
this.state.boardData.attendance.absent.push(student);
|
||||||
this.state.synced = false;
|
this.state.synced = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
setLate(index) {
|
setLate(index) {
|
||||||
const student = this.state.studentList[index];
|
const student = this.state.studentList[index];
|
||||||
// 先从所有状态列表中移除该学生
|
|
||||||
this.setPresent(index);
|
this.setPresent(index);
|
||||||
// 然后添加到迟到列表
|
|
||||||
this.state.boardData.attendance.late.push(student);
|
this.state.boardData.attendance.late.push(student);
|
||||||
this.state.synced = false;
|
this.state.synced = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
setExclude(index) {
|
setExclude(index) {
|
||||||
const student = this.state.studentList[index];
|
const student = this.state.studentList[index];
|
||||||
// 先从所有状态列表中移除该学生
|
|
||||||
this.setPresent(index);
|
this.setPresent(index);
|
||||||
// 然后添加到不参与列表
|
|
||||||
this.state.boardData.attendance.exclude.push(student);
|
this.state.boardData.attendance.exclude.push(student);
|
||||||
this.state.synced = false;
|
this.state.synced = false;
|
||||||
},
|
},
|
||||||
@ -1679,19 +1611,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 点击上传按钮时调用
|
|
||||||
async manualUpload() {
|
async manualUpload() {
|
||||||
return this.trySave(false);
|
return this.trySave(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleAttendanceDialogClose(newValue) {
|
async handleAttendanceDialogClose(newValue) {
|
||||||
if (!newValue && !this.state.synced) {
|
if (!newValue && !this.state.synced) {
|
||||||
// 对话框关闭且数据未同步时尝试保存
|
|
||||||
await this.trySave(true);
|
await this.trySave(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 全屏相关方法
|
|
||||||
toggleFullscreen() {
|
toggleFullscreen() {
|
||||||
if (!this.state.isFullscreen) {
|
if (!this.state.isFullscreen) {
|
||||||
this.enterFullscreen();
|
this.enterFullscreen();
|
||||||
@ -1805,7 +1734,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 添加URL配置解析功能
|
|
||||||
parseUrlConfig() {
|
parseUrlConfig() {
|
||||||
try {
|
try {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@ -1814,24 +1742,18 @@ export default {
|
|||||||
if (!configParam) return false;
|
if (!configParam) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 解码base64为二进制字符串
|
|
||||||
const binaryString = atob(configParam);
|
const binaryString = atob(configParam);
|
||||||
// 将二进制字符串转换为Uint8Array
|
|
||||||
const bytes = Uint8Array.from(binaryString, c => c.charCodeAt(0));
|
const bytes = Uint8Array.from(binaryString, c => c.charCodeAt(0));
|
||||||
// 将Uint8Array解码为UTF-8字符串
|
|
||||||
const decodedString = new TextDecoder().decode(bytes);
|
const decodedString = new TextDecoder().decode(bytes);
|
||||||
const decodedConfig = JSON.parse(decodedString);
|
const decodedConfig = JSON.parse(decodedString);
|
||||||
console.log("从URL读取配置:", decodedConfig);
|
console.log("从URL读取配置:", decodedConfig);
|
||||||
|
|
||||||
// 准备配置变更列表和有效配置项
|
|
||||||
const changes = [];
|
const changes = [];
|
||||||
const validSettings = {};
|
const validSettings = {};
|
||||||
const icons = {};
|
const icons = {};
|
||||||
|
|
||||||
// 处理特殊配置项
|
|
||||||
this.processSpecialSettings(decodedConfig, changes, validSettings);
|
this.processSpecialSettings(decodedConfig, changes, validSettings);
|
||||||
|
|
||||||
// 处理标准配置项
|
|
||||||
this.processStandardSettings(
|
this.processStandardSettings(
|
||||||
decodedConfig,
|
decodedConfig,
|
||||||
changes,
|
changes,
|
||||||
@ -1839,13 +1761,11 @@ export default {
|
|||||||
icons
|
icons
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果没有有效变更,直接返回
|
|
||||||
if (Object.keys(validSettings).length === 0) {
|
if (Object.keys(validSettings).length === 0) {
|
||||||
console.log("URL配置与当前配置相同,无需应用");
|
console.log("URL配置与当前配置相同,无需应用");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示确认对话框
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.urlConfigDialog = {
|
this.urlConfigDialog = {
|
||||||
show: true,
|
show: true,
|
||||||
@ -1875,9 +1795,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理特殊配置项
|
|
||||||
processSpecialSettings(decodedConfig, changes, validSettings) {
|
processSpecialSettings(decodedConfig, changes, validSettings) {
|
||||||
// 处理班级号配置
|
|
||||||
if (decodedConfig.classNumber !== undefined) {
|
if (decodedConfig.classNumber !== undefined) {
|
||||||
const current = getSetting("server.classNumber");
|
const current = getSetting("server.classNumber");
|
||||||
if (decodedConfig.classNumber !== current) {
|
if (decodedConfig.classNumber !== current) {
|
||||||
@ -1897,7 +1815,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理日期配置
|
|
||||||
if (decodedConfig.date !== undefined) {
|
if (decodedConfig.date !== undefined) {
|
||||||
if (decodedConfig.date !== this.state.dateString) {
|
if (decodedConfig.date !== this.state.dateString) {
|
||||||
changes.push({
|
changes.push({
|
||||||
@ -1912,7 +1829,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理科目列表配置
|
|
||||||
if (decodedConfig.subjects && Array.isArray(decodedConfig.subjects)) {
|
if (decodedConfig.subjects && Array.isArray(decodedConfig.subjects)) {
|
||||||
changes.push({
|
changes.push({
|
||||||
key: "subjects",
|
key: "subjects",
|
||||||
@ -1926,21 +1842,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理标准配置项
|
|
||||||
processStandardSettings(decodedConfig, changes, validSettings, icons) {
|
processStandardSettings(decodedConfig, changes, validSettings, icons) {
|
||||||
Object.entries(decodedConfig).forEach(([key, value]) => {
|
Object.entries(decodedConfig).forEach(([key, value]) => {
|
||||||
// 跳过已处理的特殊配置项
|
|
||||||
if (["classNumber", "date", "subjects"].includes(key)) {
|
if (["classNumber", "date", "subjects"].includes(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试查找设置定义
|
|
||||||
let settingKey = key;
|
let settingKey = key;
|
||||||
let definition = settingsDefinitions[key];
|
let definition = settingsDefinitions[key];
|
||||||
|
|
||||||
// 如果没有找到定义,尝试添加前缀
|
|
||||||
if (!definition && !key.includes(".")) {
|
if (!definition && !key.includes(".")) {
|
||||||
// 常见前缀列表
|
|
||||||
const prefixes = [
|
const prefixes = [
|
||||||
"server.",
|
"server.",
|
||||||
"display.",
|
"display.",
|
||||||
@ -1960,15 +1871,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果找到了定义
|
|
||||||
if (definition) {
|
if (definition) {
|
||||||
// 验证类型和值
|
|
||||||
let typedValue = this.convertValueToCorrectType(
|
let typedValue = this.convertValueToCorrectType(
|
||||||
value,
|
value,
|
||||||
definition.type
|
definition.type
|
||||||
);
|
);
|
||||||
|
|
||||||
// 验证值是否有效
|
|
||||||
if (definition.validate && !definition.validate(typedValue)) {
|
if (definition.validate && !definition.validate(typedValue)) {
|
||||||
console.warn(`URL配置项 ${settingKey} 的值无效: ${value}`);
|
console.warn(`URL配置项 ${settingKey} 的值无效: ${value}`);
|
||||||
return;
|
return;
|
||||||
@ -1988,7 +1896,6 @@ export default {
|
|||||||
icons[settingKey] = definition.icon || "mdi-cog";
|
icons[settingKey] = definition.icon || "mdi-cog";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 未知配置项,尝试作为自定义配置处理
|
|
||||||
changes.push({
|
changes.push({
|
||||||
key: key,
|
key: key,
|
||||||
name: this.getSettingDisplayName(key),
|
name: this.getSettingDisplayName(key),
|
||||||
@ -2003,7 +1910,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 将值转换为正确类型
|
|
||||||
convertValueToCorrectType(value, type) {
|
convertValueToCorrectType(value, type) {
|
||||||
if (type === "boolean") {
|
if (type === "boolean") {
|
||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
@ -2014,7 +1920,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 格式化设置值显示
|
|
||||||
formatSettingValue(value) {
|
formatSettingValue(value) {
|
||||||
if (typeof value === "boolean") {
|
if (typeof value === "boolean") {
|
||||||
return value ? "开启" : "关闭";
|
return value ? "开启" : "关闭";
|
||||||
@ -2024,20 +1929,15 @@ export default {
|
|||||||
return value.toString();
|
return value.toString();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取设置显示名称
|
|
||||||
getSettingDisplayName(key) {
|
getSettingDisplayName(key) {
|
||||||
// 从key中提取最后一部分作为显示名
|
|
||||||
const parts = key.split(".");
|
const parts = key.split(".");
|
||||||
const lastPart = parts[parts.length - 1];
|
const lastPart = parts[parts.length - 1];
|
||||||
|
|
||||||
// 根据常见key返回中文名
|
|
||||||
const nameMap = {
|
const nameMap = {
|
||||||
// 服务器设置
|
|
||||||
provider: "数据提供方",
|
provider: "数据提供方",
|
||||||
domain: "服务器域名",
|
domain: "服务器域名",
|
||||||
classNumber: "班级编号",
|
classNumber: "班级编号",
|
||||||
|
|
||||||
// 显示设置
|
|
||||||
emptySubjectDisplay: "空科目显示方式",
|
emptySubjectDisplay: "空科目显示方式",
|
||||||
dynamicSort: "动态排序",
|
dynamicSort: "动态排序",
|
||||||
showRandomButton: "随机按钮",
|
showRandomButton: "随机按钮",
|
||||||
@ -2046,19 +1946,15 @@ export default {
|
|||||||
enhancedTouchMode: "增强触摸模式",
|
enhancedTouchMode: "增强触摸模式",
|
||||||
showAntiScreenBurnCard: "防烧屏卡片",
|
showAntiScreenBurnCard: "防烧屏卡片",
|
||||||
|
|
||||||
// 主题设置
|
|
||||||
mode: "主题模式",
|
mode: "主题模式",
|
||||||
|
|
||||||
// 字体设置
|
|
||||||
size: "字体大小",
|
size: "字体大小",
|
||||||
|
|
||||||
// 编辑设置
|
|
||||||
autoSave: "自动保存",
|
autoSave: "自动保存",
|
||||||
blockNonTodayAutoSave: "禁止自动保存非当日",
|
blockNonTodayAutoSave: "禁止自动保存非当日",
|
||||||
refreshBeforeEdit: "编辑前刷新",
|
refreshBeforeEdit: "编辑前刷新",
|
||||||
confirmNonTodaySave: "非当日保存确认",
|
confirmNonTodaySave: "非当日保存确认",
|
||||||
|
|
||||||
// 刷新设置
|
|
||||||
auto: "自动刷新",
|
auto: "自动刷新",
|
||||||
interval: "刷新间隔",
|
interval: "刷新间隔",
|
||||||
};
|
};
|
||||||
@ -2066,7 +1962,6 @@ export default {
|
|||||||
return nameMap[lastPart] || lastPart;
|
return nameMap[lastPart] || lastPart;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 安全的Base64解码函数,支持UTF-8字符(包括中文)
|
|
||||||
safeBase64Decode(base64String) {
|
safeBase64Decode(base64String) {
|
||||||
try {
|
try {
|
||||||
return Base64.decode(base64String);
|
return Base64.decode(base64String);
|
||||||
@ -2077,7 +1972,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
applyUrlConfig(validSettings) {
|
applyUrlConfig(validSettings) {
|
||||||
// 应用所有有效的配置项
|
|
||||||
for (const [key, value] of Object.entries(validSettings)) {
|
for (const [key, value] of Object.entries(validSettings)) {
|
||||||
if (key === "date") {
|
if (key === "date") {
|
||||||
this.handleDateSelect(value);
|
this.handleDateSelect(value);
|
||||||
@ -2089,20 +1983,23 @@ export default {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用标准设置项
|
|
||||||
setSetting(key, value);
|
setSetting(key, value);
|
||||||
|
|
||||||
// 更新相关状态
|
|
||||||
if (key === "server.classNumber") {
|
if (key === "server.classNumber") {
|
||||||
this.state.classNumber = value;
|
this.state.classNumber = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新后端URL和其他可能受到配置影响的属性
|
|
||||||
this.updateBackendUrl();
|
this.updateBackendUrl();
|
||||||
this.$message.success("URL配置已应用", "已从URL加载配置");
|
this.$message.success("URL配置已应用", "已从URL加载配置");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
navigateDay(offset) {
|
||||||
|
const currentDate = new Date(this.state.selectedDateObj);
|
||||||
|
currentDate.setDate(currentDate.getDate() + offset);
|
||||||
|
this.handleDateSelect(currentDate);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -232,7 +232,6 @@ import AboutCard from "@/components/settings/AboutCard.vue";
|
|||||||
import "../styles/settings.scss";
|
import "../styles/settings.scss";
|
||||||
import SettingsExplorer from "@/components/settings/SettingsExplorer.vue";
|
import SettingsExplorer from "@/components/settings/SettingsExplorer.vue";
|
||||||
import SettingsLinkGenerator from "@/components/SettingsLinkGenerator.vue";
|
import SettingsLinkGenerator from "@/components/SettingsLinkGenerator.vue";
|
||||||
import dataProvider from "@/utils/dataProvider";
|
|
||||||
import NamespaceSettingsCard from "@/components/settings/cards/NamespaceSettingsCard.vue";
|
import NamespaceSettingsCard from "@/components/settings/cards/NamespaceSettingsCard.vue";
|
||||||
import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
|
import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
|
||||||
export default {
|
export default {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user