diff --git a/src/App.vue b/src/App.vue index 2caa78e..2c216f9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,11 @@ - + + + + + @@ -9,3 +13,19 @@ + diff --git a/src/components/MessageLog.vue b/src/components/MessageLog.vue new file mode 100644 index 0000000..f6ac0de --- /dev/null +++ b/src/components/MessageLog.vue @@ -0,0 +1,268 @@ + + + + + + 消息记录 + + {{ unreadCount }} + + + + + + + + + + + + + + {{ msg.title }} + + {{ msg.content }} + + + {{ formatTime(msg.timestamp) }} + + + + + + + + + + + 加载更多 + + + + + + + + + + {{ msg.title }} + + {{ formatTime(msg.timestamp) }} + + + {{ msg.content }} + + + + + + + + + diff --git a/src/components/SettingsCard.vue b/src/components/SettingsCard.vue new file mode 100644 index 0000000..12ec440 --- /dev/null +++ b/src/components/SettingsCard.vue @@ -0,0 +1,54 @@ + + + + + + + {{ title }} + + + + + + + + + + + + + + + + diff --git a/src/components/settings/AboutCard.vue b/src/components/settings/AboutCard.vue new file mode 100644 index 0000000..6d3984c --- /dev/null +++ b/src/components/settings/AboutCard.vue @@ -0,0 +1,72 @@ + + + + + + + 关于 + + + + + + + + + + HomeworkPage + + 由 Sunwuyuan 开发 + + + + + 前端 GitHub + + + 后端 GitHub + + + 报告问题 + + + + + GPL License © 2024 + + + + + + + + diff --git a/src/components/settings/StudentListCard.vue b/src/components/settings/StudentListCard.vue new file mode 100644 index 0000000..b680810 --- /dev/null +++ b/src/components/settings/StudentListCard.vue @@ -0,0 +1,344 @@ + + + + + + + 学生列表 + + + {{ modelValue.advanced ? '返回基础编辑' : '高级编辑' }} + + + + + + + + + {{ error }} + + + + + + + + + + + + + + + + + + + + + + + + {{ index + 1 }} + + + + + + 置顶 + + + + 上移 + + + 下移 + + + + + + + {{ student }} + + + + + + + + + + + + + + + + + + + + + + + 保存名单 + + + 重载名单 + + + + + + + + + + diff --git a/src/main.js b/src/main.js index d31eee2..c369c30 100644 --- a/src/main.js +++ b/src/main.js @@ -13,8 +13,12 @@ import App from './App.vue' // Composables import { createApp } from 'vue' +import messageService from './utils/message'; + const app = createApp(App) registerPlugins(app) +app.use(messageService); + app.mount('#app') diff --git a/src/pages/index.vue b/src/pages/index.vue index 5a119ac..32ed20e 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -5,7 +5,7 @@ - {{ classNumber }}班 - {{ titleText }} + {{ state.classNumber }}班 - {{ titleText }} @@ -22,10 +22,10 @@ @click="zoom('up')" /> - + @@ -42,7 +42,7 @@ + @@ -58,102 +65,140 @@ class="main-window flex-grow-1" fluid > - - - 别看未来的作业了 + + + + 别看未来的作业了 + - {{ noDataMessage }} + + {{ state.noDataMessage }} + - + + - - - {{ item.name }} - - - - - - {{ text }} - - - - - - mdi-plus - 点击添加作业 - - + + {{ item.name }} + + + + {{ text }} + + - - - + + + + + + mdi-plus + + {{ subject.name }} + + + + - mdi-plus - {{ subject.name }} - - + + + {{ subject.name }} + + + + mdi-plus + + + 点击添加作业 + + + + + 出勤 - 应到: {{ studentList.length }}人 - 实到: {{ studentList.length - selectedSet.size }}人 - 请假: {{ selectedSet.size }}人 - - {{ `${index + 1}. ${studentList[i]}` }} + 应到: {{ state.studentList.length }}人 + 实到: {{ state.studentList.length - state.selectedSet.size }}人 + 请假: {{ state.selectedSet.size }}人 + + {{ `${index + 1}. ${state.studentList[i]}` }} - 迟到: {{ lateSet.size }}人 - - {{ `${index + 1}. ${studentList[i]}` }} + 迟到: {{ state.lateSet.size }}人 + + {{ `${index + 1}. ${state.studentList[i]}` }} @@ -169,19 +214,19 @@ - {{ dialogTitle }} + {{ state.dialogTitle }} {{ autoSave ? "喵?喵呜!" : "写完后点击上传谢谢喵" }} @@ -203,7 +248,7 @@ - {{ snackbarText }} + {{ state.snackbarText }} @@ -249,20 +296,9 @@ - - - - 批量操作 - + 批量操作 学生列表 - + + + - - diff --git a/src/pages/settings.vue b/src/pages/settings.vue index 423bfbd..92f1d09 100644 --- a/src/pages/settings.vue +++ b/src/pages/settings.vue @@ -1,411 +1,288 @@ - - - - - 设置 - - - - - - - - - - - 服务器设置 - - - - - - - - - - 保存设置 - - - - - - - - - - - - - - 自动刷新设置(不建议启用) - - - - - - - - - - 保存设置 - - - - - - - - - - - - 字体设置 - - - - - - - - - 重置 - - - 保存设置 - - - - - - - - - - - - 编辑设置 - - - - - - - - - - 编辑完成后自动上传到服务器 - - - - - - - - - - - 打开编辑框前自动从服务器获取最新数据 - - - - - - - - 保存设置 - - - - - - - - - - 显示设置 - - - - - - - - - - - 启用:根据内容长度动态调整位置 - 关闭:按语数英/物化生/政史地固定排列 - - - - - - - - - - - - 显示为空卡片 - - - - - 在主界面中显示为可点击的空白卡片 - - - - - - - - 显示为按钮组 - - - - - 在主界面底部显示为一组添加按钮 - - - - - - - - - - 保存设置 - - - - - - - - - - - 学生列表设置 - - - {{ showAdvancedEdit ? '返回基础编辑' : '高级编辑' }} - - - - - - - - - {{ studentsError }} - + + + + + + 设置 + + + + + + + - - - - - - - - - - - - - - - - - - - - {{ index + 1 }} - - - - - 置顶 - - - - 上移 - - - 下移 - - - - 设置序号 - - - - - - - {{ student }} - - - - - - - - - - - + + - - - - - + + + - - - - 保存学生列表 - - - 重置列表 - - - - - - - - - - - - - 关于 - + + + + + + + + + 自动保存 + 在编辑完成后自动保存到服务器 + + + + - - - - - - - HomeworkPage - - 由 Sunwuyuan 开发 - - - - 前端 GitHub - - - 后端 GitHub - - - 报告问题 - - - - GPL License © 2024 - - - - - - - - + - - {{ snackbarText }} - + + + + + 编辑前刷新 + 在打开编辑框前从服务器获取最新数据 + + + + + + + - - - 确认删除 - - 确定要删除学生 "{{ studentToDelete?.name }}" 吗? - - - - - 取消 - - - 删除 - - - - + + + + + + + + + 自动刷新 + 在后台自动刷新数据 + + + + - - - 设置序号 - - - - - - - 取消 - - - 确定 - - - - + + + + + + + 刷新间隔 + 设置自动刷新的时间间隔(分钟) + + + + + + + + + + + + + + + + + 空科目显示 + 选择空科目的显示方式 + + + + 按钮 + + + 卡片 + + + + + + + + + + + + 动态排序 + 根据科目动态排序 + + + + + + + + + + + + + + + + + 启用开发者选项 + 启用后可以查看和修改开发者设置 + + + + + + + + + + + + + 显示调试配置 + 显示当前的调试配置信息 + + + + + + + + + + + + 刷新 + + + 复制 + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..469cc4f --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,176 @@ + +.grid-masonry { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + padding: 8px; + grid-auto-flow: dense; + } + + .grid-item { + width: 100%; + transition: all 0.2s ease; + } + + .empty-card { + transform: scale(0.9); + opacity: 0.8; + grid-row-end: span 1 !important; + } + + .empty-card:hover { + transform: scale(0.95); + opacity: 1; + } + + .empty-subjects-container { + display: flex; + flex-wrap: wrap; + } + + @media (max-width: 1199px) { + .grid-masonry { + grid-template-columns: repeat(2, 1fr); + } + } + + @media (max-width: 799px) { + .grid-masonry { + grid-template-columns: 1fr; + } + + .empty-card { + transform: scale(0.95); + } + } + + /* 确保容器高度不超过视口 */ + .main-window { + max-height: calc(100vh - 180px); + overflow-y: auto; + } + + /* 优化滚动条样式 */ + .main-window::-webkit-scrollbar { + width: 8px; + } + + .main-window::-webkit-scrollbar-track { + background: transparent; + } + + .main-window::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; + } + + .main-window::-webkit-scrollbar-thumb:hover { + background-color: rgba(0, 0, 0, 0.3); + } + + .no-data-message { + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; + margin: 20px 0; + } + + .attendance-drawer { + border-left: 1px solid rgba(0, 0, 0, 0.12); + } + + .attendance-drawer :deep(.v-navigation-drawer__content) { + overflow-y: auto; + } + + /* 优化滚动条样式 */ + .attendance-drawer :deep(.v-navigation-drawer__content::-webkit-scrollbar) { + width: 8px; + } + + .attendance-drawer + :deep(.v-navigation-drawer__content::-webkit-scrollbar-track) { + background: transparent; + } + + .attendance-drawer + :deep(.v-navigation-drawer__content::-webkit-scrollbar-thumb) { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; + } + + .attendance-drawer + :deep(.v-navigation-drawer__content::-webkit-scrollbar-thumb:hover) { + background-color: rgba(0, 0, 0, 0.3); + } + + /* 响应式调整 */ + @media (max-width: 960px) { + .attendance-drawer { + display: none; + } + } + + .text-success { + color: rgb(var(--v-theme-success)); + } + + .text-error { + color: rgb(var(--v-theme-error)); + } + + .text-warning { + color: rgb(var(--v-theme-warning)); + } + + .attendance-card { + display: flex; + flex-direction: column; + } + + .attendance-numbers { + padding: 20px 0; + } + + .total-number { + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + padding-bottom: 20px; + } + + .status-number { + flex: 1; + } + + .text-h2, + .text-h3 { + line-height: 1.2; + } + + .empty-subjects-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; + padding: 8px; + } + + .empty-subject-card { + cursor: pointer; + transition: all 0.2s ease; + opacity: 0.8; + } + + .empty-subject-card:hover { + transform: scale(1.02); + opacity: 1; + } + + .empty-subjects { + border-top: 1px solid rgba(0, 0, 0, 0.12); + padding-top: 1rem; + } + + .empty-subject-card:not(:disabled):hover { + opacity: 1; + transform: scale(1.02); + } \ No newline at end of file diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 3e36a27..d92984f 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -8,3 +8,74 @@ // @use 'vuetify/settings' with ( // $color-pack: false // ); + +.student-card { + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + + .bg-primary-subtle { + background-color: rgb(var(--v-theme-primary), 0.05); + } + + .action-buttons { + transition: opacity 0.2s ease; + opacity: 0; + } + + .gap-1 { + gap: 4px; + } + + .gap-2 { + gap: 8px; + } + + .student-card .v-text-field { + margin: 0; + padding: 0; + } + + @media (max-width: 600px) { + .v-container { + padding: 12px; + } + + .v-col { + padding: 8px; + } + } + + .student-card.mobile { + margin-bottom: 8px; + } + + .student-card.mobile .v-btn { + min-width: 40px; + min-height: 40px; + } + + .student-card.mobile .v-text-field { + font-size: 16px; + } + + @media (max-width: 600px) { + .v-col { + padding: 6px !important; + } + + .student-card { + margin-bottom: 4px; + } + + .action-buttons { + opacity: 1; + } + } + + .student-card { + -webkit-tap-highlight-color: transparent; + } + + .student-card:active { + background-color: rgb(var(--v-theme-primary), 0.05); + } \ No newline at end of file diff --git a/src/utils/dataProvider.js b/src/utils/dataProvider.js new file mode 100644 index 0000000..85c48a3 --- /dev/null +++ b/src/utils/dataProvider.js @@ -0,0 +1,177 @@ +import axios from 'axios'; + +const formatResponse = (data, message = null) => ({ + success: true, + data, + message +}); + +const formatError = (message, code = 'UNKNOWN_ERROR') => ({ + success: false, + error: { code, message } +}); + +const providers = { + localStorage: { + 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) { + // 如果是今天的数据且没有找到,返回空结构而不是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('读取本地数据失败'); + } + }, + + async saveData(key, data, date) { + try { + // 检查是否设置了班号 + const classNumber = key.split('/').pop(); + if (!classNumber) { + return formatError('请先设置班号', 'CONFIG_ERROR'); + } + + // 使用班号作为本地存储的前缀 + const storageKey = `homework_${classNumber}_${date}`; + localStorage.setItem(storageKey, JSON.stringify(data)); + return formatResponse(null, '保存成功'); + } catch (error) { + return formatError('保存本地数据失败'); + } + }, + + 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('读取本地配置失败'); + } + }, + + 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('保存本地配置失败'); + } + } + }, + + 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) { + try { + await axios.post(`${key}/homework`, 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.post(`${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' + ); + } + } + } +}; + +export default { + loadData: (provider, key, date) => providers[provider]?.loadData(key, date), + saveData: (provider, key, data, date) => providers[provider]?.saveData(key, data, date), + loadConfig: (provider, key) => providers[provider]?.loadConfig(key), + saveConfig: (provider, key, config) => providers[provider]?.saveConfig(key, config) +}; + +export const ErrorCodes = { + NOT_FOUND: '数据不存在', + NETWORK_ERROR: '网络连接失败', + SERVER_ERROR: '服务器错误', + SAVE_ERROR: '保存失败', + CONFIG_ERROR: '配置错误', + UNKNOWN_ERROR: '未知错误' +}; diff --git a/src/utils/debounce.js b/src/utils/debounce.js new file mode 100644 index 0000000..e0b1fc2 --- /dev/null +++ b/src/utils/debounce.js @@ -0,0 +1,27 @@ +export function debounce(fn, delay) { + let timer = null; + return function (...args) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + fn.apply(this, args); + }, delay); + }; +} + +export function throttle(fn, delay) { + let timer = null; + let last = 0; + return function (...args) { + const now = Date.now(); + if (now - last < delay) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + last = now; + fn.apply(this, args); + }, delay); + } else { + last = now; + fn.apply(this, args); + } + }; +} diff --git a/src/utils/message.js b/src/utils/message.js new file mode 100644 index 0000000..4d73f98 --- /dev/null +++ b/src/utils/message.js @@ -0,0 +1,153 @@ +const messages = []; +let snackbarCallback = null; +let logCallback = null; + +const MessageType = { + SUCCESS: 'success', + ERROR: 'error', + INFO: 'info', + WARNING: 'warning' +}; + +const defaultOptions = { + timeout: 3000, + showSnackbar: true, + addToLog: true +}; + +const STORAGE_KEY = 'homeworkpage_messages'; +const MAX_MESSAGES = 100; // 最大消息数量 +const MAX_STORAGE_SIZE = 1024 * 1024; // 1MB 存储限制 + +// 加载保存的消息 +function loadStoredMessages() { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + messages.push(...JSON.parse(stored)); + } + } catch (error) { + console.error('加载消息历史失败:', error); + } +} + +// 清理旧消息 +function cleanOldMessages() { + if (messages.length > MAX_MESSAGES) { + messages.splice(MAX_MESSAGES); + } +} + +// 检查存储大小 +function checkStorageSize(data) { + try { + const size = new Blob([data]).size; + return size <= MAX_STORAGE_SIZE; + } catch (error) { + console.error('检查存储大小失败:', error); + return false; + } +} + +// 保存消息到localStorage +function saveMessages() { + try { + cleanOldMessages(); + const data = JSON.stringify(messages); + + if (!checkStorageSize(data)) { + // 如果数据太大,删除一半的旧消息 + messages.splice(Math.floor(messages.length / 2)); + return saveMessages(); + } + + localStorage.setItem(STORAGE_KEY, data); + } catch (error) { + if (error.name === 'QuotaExceededError') { + // 如果存储空间不足,清理一些旧消息再试 + messages.splice(Math.floor(messages.length / 2)); + return saveMessages(); + } + console.error('保存消息历史失败:', error); + } +} + +function createMessage(type, title, content = '', options = {}) { + const msgOptions = { ...defaultOptions, ...options }; + const message = { + id: Date.now() + Math.random(), + type, + title, + content: content.substring(0, 500), // 限制内容长度 + timestamp: new Date(), + read: false + }; + + if (msgOptions.addToLog) { + messages.unshift(message); // 新消息添加到开头 + cleanOldMessages(); + saveMessages(); + logCallback?.(messages); + } + + if (msgOptions.showSnackbar) { + snackbarCallback?.(message); + } + + return message; +} + +// 添加防抖函数实现 +function debounce(fn, delay) { + let timer = null; + return function (...args) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + fn.apply(this, args); + }, delay); + }; +} + +export default { + install: (app) => { + app.config.globalProperties.$message = { + success: (title, content, options) => createMessage(MessageType.SUCCESS, title, content, options), + error: (title, content, options) => createMessage(MessageType.ERROR, title, content, options), + info: (title, content, options) => createMessage(MessageType.INFO, title, content, options), + warning: (title, content, options) => createMessage(MessageType.WARNING, title, content, options), + }; + }, + onSnackbar: (callback) => { snackbarCallback = callback; }, + onLog: (callback) => { logCallback = callback; }, + getMessages: () => [...messages], + clearMessages: () => { + messages.length = 0; + try { + localStorage.removeItem(STORAGE_KEY); + } catch (error) { + console.error('清除消息历史失败:', error); + } + }, + MessageType, + markAsRead: (messageId) => { + const message = messages.find(m => m.id === messageId); + if (message) { + message.read = true; + saveMessages(); + logCallback?.(messages); + } + }, + deleteMessage: (messageId) => { + const index = messages.findIndex(m => m.id === messageId); + if (index !== -1) { + messages.splice(index, 1); + saveMessages(); + logCallback?.(messages); + } + }, + getUnreadCount: () => messages.filter(m => !m.read).length, + initialize: () => { + loadStoredMessages(); + }, + debounce, // 导出防抖函数 +}; diff --git a/src/utils/settings.js b/src/utils/settings.js index aacc3b4..eff982b 100644 --- a/src/utils/settings.js +++ b/src/utils/settings.js @@ -6,6 +6,7 @@ * @property {Function} [validate] - 可选的验证函数 * @property {string} [description] - 配置项描述 * @property {string} [legacyKey] - 旧版本localStorage键名(用于迁移) + * @property {boolean} [requireDeveloper] - 是否需要开发者选项启用 */ // 存储所有设置的localStorage键名 @@ -17,14 +18,9 @@ const SETTINGS_STORAGE_KEY = 'homeworkpage_settings'; */ const settingsDefinitions = { // 显示设置 - // 'display.showEmptySubjects': { - // type: 'boolean', - // default: true, - // description: '是否在主界面显示没有作业内容的科目' - // }, 'display.emptySubjectDisplay': { type: 'string', - default: 'card', + default: 'button', // 修改默认值为 'button' validate: value => ['card', 'button'].includes(value), description: '空科目的显示方式:卡片或按钮' }, @@ -33,8 +29,8 @@ const settingsDefinitions = { default: true, description: '是否启用动态排序以优化显示效果' }, - - // 服务器设置 + + // 服务器设置(合并了数据提供者设置) 'server.domain': { type: 'string', default: '', @@ -45,7 +41,13 @@ const settingsDefinitions = { type: 'string', default: '', validate: value => /^[A-Za-z0-9]*$/.test(value), - description: '班级编号' + description: '班级编号(无论使用哪种存储方式都需要设置)' + }, + 'server.provider': { // 新增项 + type: 'string', + default: 'localStorage', + validate: value => ['server', 'localStorage'].includes(value), + description: '数据提供者,用于决定数据存储方式' }, // 刷新设置 @@ -79,6 +81,46 @@ const settingsDefinitions = { type: 'boolean', default: true, description: '编辑前是否自动刷新' + }, + + // 开发者选项 + 'developer.enabled': { + type: 'boolean', + default: false, + description: '是否启用开发者选项' + }, + 'developer.showDebugConfig': { + type: 'boolean', + default: false, + description: '是否显示调试配置' + }, + + // 消息设置 + 'message.showSidebar': { + type: 'boolean', + default: true, + description: '是否显示消息记录侧栏', + requireDeveloper: true // 添加标记 + }, + 'message.maxActiveMessages': { + type: 'number', + default: 5, + validate: value => value >= 1 && value <= 10, + description: '同时显示的最大消息数量', + requireDeveloper: true + }, + 'message.timeout': { + type: 'number', + default: 5000, + validate: value => value >= 1000 && value <= 30000, + description: '消息自动关闭时间(毫秒)', + requireDeveloper: true + }, + 'message.saveHistory': { + type: 'boolean', + default: true, + description: '是否保存消息历史记录', + requireDeveloper: true } }; @@ -102,14 +144,14 @@ function loadSettings() { console.error('加载设置失败:', error); settingsCache = {}; } - + // 确保所有设置项都有值(使用默认值填充) for (const [key, definition] of Object.entries(settingsDefinitions)) { if (!(key in settingsCache)) { settingsCache[key] = definition.default; } } - + return settingsCache; } @@ -175,17 +217,33 @@ function getSetting(key) { if (!settingsCache) { loadSettings(); } - + const definition = settingsDefinitions[key]; if (!definition) { console.warn(`未定义的设置项: ${key}`); return null; } + // 添加对开发者选项依赖的检查 + if (definition.requireDeveloper && !settingsCache['developer.enabled']) { + return definition.default; + } + const value = settingsCache[key]; return value !== undefined ? value : definition.default; } +// 添加设置变更日志函数 +function logSettingsChange(key, oldValue, newValue) { + if (settingsCache['developer.enabled'] && settingsCache['developer.showDebugConfig']) { + console.log(`[Settings] ${key}:`, { + old: oldValue, + new: newValue, + time: new Date().toLocaleTimeString() + }); + } +} + /** * 设置配置项的值 * @param {string} key - 设置项键名 @@ -199,7 +257,14 @@ function setSetting(key, value) { return false; } + // 添加对开发者选项依赖的检查 + if (definition.requireDeveloper && !settingsCache['developer.enabled']) { + console.warn(`设置项 ${key} 需要启用开发者选项`); + return false; + } + try { + const oldValue = settingsCache[key]; // 类型转换 if (typeof value !== definition.type) { value = definition.type === 'boolean' ? Boolean(value) : @@ -218,6 +283,7 @@ function setSetting(key, value) { settingsCache[key] = value; saveSettings(); + logSettingsChange(key, oldValue, value); // 为了保持向后兼容,同时更新旧的localStorage键 const legacyKey = definition.legacyKey; @@ -274,7 +340,7 @@ function watchSettings(callback) { callback(settingsCache); } }; - + window.addEventListener('storage', handler); return () => window.removeEventListener('storage', handler); } @@ -289,4 +355,4 @@ export { resetSetting, resetAllSettings, watchSettings -}; \ No newline at end of file +};
+ 由 Sunwuyuan 开发 +
+ GPL License © 2024 +
启用:根据内容长度动态调整位置
关闭:按语数英/物化生/政史地固定排列
- 由 Sunwuyuan 开发 -
- GPL License © 2024 -