1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-06 20:19:22 +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:
SunWuyuan 2025-07-05 10:05:52 +08:00
parent 965f36cbf5
commit 53ed1f556f
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
8 changed files with 322 additions and 144 deletions

View File

@ -1,5 +1,8 @@
{ {
"extends": [ "extends": [
"development" "development"
] ],
"hints": {
"no-inline-styles": "off"
}
} }

View File

@ -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>

View 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>

View 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>

View File

@ -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"
> >

View File

@ -3,8 +3,6 @@
<v-main> <v-main>
<router-view /> <router-view />
</v-main> </v-main>
<AppFooter />
</v-app> </v-app>
</template> </template>

View File

@ -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));
// Uint8ArrayUTF-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;
}, },
// Base64UTF-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>

View File

@ -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 {