import { v4 as uuidv4 } from "uuid"; // 请求通知权限 async function requestNotificationPermission() { if (Notification && Notification.requestPermission) { const permission = await Notification.requestPermission(); if (permission === "granted") { console.log("通知权限已授予"); return true; } else { console.warn("通知权限被拒绝"); return false; } } else { console.warn("浏览器不支持通知权限请求"); return false; } } /** * 请求持久性存储权限 * @returns {Promise} 是否成功启用持久性存储 */ async function requestPersistentStorage() { try { if (navigator.storage?.persist) { return await navigator.storage.persist(); } return false; } catch (error) { console.warn("请求持久性存储失败:", error); return false; } } /** * 初始化存储权限 */ async function initializeStorage() { const notificationGranted = await requestNotificationPermission(); if (notificationGranted && SettingsManager.getSetting("storage.persistOnLoad")) { const persisted = await requestPersistentStorage(); console.log(`持久性存储状态: ${persisted ? "已启用" : "未启用"}`); } } // 在页面加载时初始化 if (typeof window !== 'undefined') { window.addEventListener("load", initializeStorage); } /** * 配置项定义 * @typedef {Object} SettingDefinition * @property {string} type - 配置项类型 ('boolean' | 'number' | 'string') * @property {any} default - 默认值 * @property {Function} [validate] - 可选的验证函数 * @property {string} [description] - 配置项描述 * @property {string} [legacyKey] - 旧版本localStorage键名(用于迁移) * @property {boolean} [requireDeveloper] - 是否需要开发者选项启用 * @property {string} [icon] - 设置项的图标 */ // 存储所有设置的localStorage键名 const SETTINGS_STORAGE_KEY = "Classworks_settings"; /** * 生成UUID v4 * @returns {string} 生成的UUID字符串 */ function generateUUID() { return uuidv4(); } // 新增: Classworks云端存储的默认设置 const classworksCloudDefaults = { "server.domain": "https://kv.wuyuan.dev", "server.siteKey": "", }; /** * 所有配置项的定义 * @type {Object.} */ const settingsDefinitions = { // 设备标识 "device.uuid": { type: "string", default: generateUUID(), description: "设备唯一标识符", icon: "mdi-identifier", }, // 存储设置 "storage.persistOnLoad": { type: "boolean", default: true, description: "是否在页面加载时自动请求持久性存储", icon: "mdi-database-sync", }, // 显示设置 "display.emptySubjectDisplay": { type: "string", default: "card", // 修改默认值为 'button' validate: (value) => ["card", "button"].includes(value), description: "空科目的显示方式", icon: "mdi-card-outline", }, "display.dynamicSort": { type: "boolean", default: true, description: "是否启用动态排序", icon: "mdi-sort-variant", // 启用后会根据内容自动调整卡片顺序,提供更好的视觉体验 }, "display.showRandomButton": { type: "boolean", default: false, description: "是否显示随机点人按钮", icon: "mdi-shuffle-variant", // 控制是否显示随机排序按钮,可用于随机调整卡片顺序 }, "display.showFullscreenButton": { type: "boolean", default: true, description: "是否显示全屏按钮", icon: "mdi-fullscreen", // 控制是否显示进入全屏模式的按钮 }, "display.cardHoverEffect": { type: "boolean", default: true, description: "是否启用卡片悬浮效果", icon: "mdi-gesture-tap", // 启用后鼠标悬停在卡片上时会显示视觉反馈效果 }, "display.enhancedTouchMode": { type: "boolean", default: true, description: "是否启用增强触摸模式", icon: "mdi-gesture-tap-button", }, "display.showAntiScreenBurnCard": { type: "boolean", default: false, description: "是否显示防烧屏忽悠卡片", icon: "mdi-monitor-shimmer", }, // 服务器设置(合并了数据提供者设置) "server.domain": { type: "string", default: "", validate: (value) => { // 如果不是服务器模式或值为空,直接通过 if (!value) return true; // 验证URL格式 try { new URL(value); return true; } catch (e) { console.error("域名格式无效:", e); return false; } }, description: "后端服务器域名", icon: "mdi-web", // 设置后端服务器的域名,用于从远程服务器获取数据 }, "server.classNumber": { type: "string", default: "高三八班", //validate: (value) => /^[A-Za-z0-9]*$/.test(value), validate: (value) => /.*/.test(value), description: "班级编号", icon: "mdi-account-group", // 设置班级标识,用于区分不同班级的数据 }, "server.siteKey": { type: "string", default: "", description: "网站令牌", icon: "mdi-key-chain", // 用于后端验证请求的令牌,将作为请求头 x-site-key 发送 }, "server.provider": { type: "string", default: "kv-local", validate: (value) => ["kv-local", "kv-server", "classworkscloud"].includes(value), description: "数据提供者", icon: "mdi-database", // 选择数据存储方式:使用本地存储或远程服务器 }, // 刷新设置 "refresh.auto": { type: "boolean", default: false, description: "是否启用自动刷新", icon: "mdi-refresh-auto", // 启用后将按设定的时间间隔自动刷新数据 }, "refresh.interval": { type: "number", default: 300, validate: (value) => value >= 10 && value <= 3600, description: "自动刷新间隔(秒)", icon: "mdi-timer-outline", // 设置自动刷新的时间间隔,范围10-3600秒 }, // 字体设置 "font.size": { type: "number", default: 28, validate: (value) => value >= 16 && value <= 100, description: "字体大小", icon: "mdi-format-size", }, // 编辑设置 "edit.autoSave": { type: "boolean", default: true, description: "是否启用自动保存", icon: "mdi-content-save-outline", // 启用后编辑内容时会自动保存更改,无需手动点击保存按钮 }, "edit.blockNonTodayAutoSave": { // 添加新选项 type: "boolean", default: true, description: "禁止自动保存非当天数据", icon: "mdi-calendar-lock", // 启用后只有当天的数据会自动保存,防止意外修改历史数据 }, "edit.refreshBeforeEdit": { type: "boolean", default: true, description: "编辑前是否自动刷新", icon: "mdi-refresh", // 启用后在开始编辑前会自动刷新数据,确保编辑的是最新内容 }, "edit.confirmNonTodaySave": { // 添加新选项 type: "boolean", default: true, description: "保存非当天数据需确认", icon: "mdi-calendar-alert", }, // 开发者选项 "developer.enabled": { type: "boolean", default: false, description: "是否启用开发者选项", icon: "mdi-developer-board", // 启用后可以访问高级开发者功能和设置项 }, "developer.showDebugConfig": { type: "boolean", default: false, description: "是否显示调试配置", icon: "mdi-bug-outline", // 启用后在控制台显示详细的配置信息和设置变更日志 }, "developer.disableMessageLog": { // 添加新的设置项 type: "boolean", default: false, description: "禁用消息日志记录", requireDeveloper: true, icon: "mdi-message-off-outline", // 启用后将不再记录应用消息到日志,可减少内存占用 }, // 消息设置 "message.showSidebar": { type: "boolean", default: true, description: "是否显示消息记录侧栏", requireDeveloper: true, // 添加标记 icon: "mdi-message-text-outline", // 控制是否显示消息历史记录侧栏,需要开发者模式 }, "message.maxActiveMessages": { type: "number", default: 5, validate: (value) => value >= 1 && value <= 10, description: "同时显示的最大消息数量", requireDeveloper: true, icon: "mdi-message-badge-outline", // 控制界面上同时显示的最大消息数量,范围1-10条 }, "message.timeout": { type: "number", default: 5000, validate: (value) => value >= 1000 && value <= 30000, description: "消息自动关闭时间(毫秒)", requireDeveloper: true, icon: "mdi-timer-sand", // 设置消息自动消失的时间,范围1000-30000毫秒 }, "message.saveHistory": { type: "boolean", default: true, description: "是否保存消息历史记录", requireDeveloper: true, icon: "mdi-history", // 启用后将保存消息历史记录,可在侧栏中查看 }, // 主题设置 "theme.mode": { type: "string", default: "dark", validate: (value) => ["light", "dark"].includes(value), description: "主题模式", icon: "mdi-theme-light-dark", // 设置应用的主题模式,可选亮色或暗色主题 }, // 随机点名设置 "randomPicker.enabled": { type: "boolean", default: true, description: "是否启用随机点名功能", icon: "mdi-account-question", }, "randomPicker.animation": { type: "boolean", default: true, description: "是否启用随机点名动画效果", icon: "mdi-animation-play", }, "randomPicker.defaultCount": { type: "number", default: 1, validate: (value) => value >= 1 && value <= 10, description: "默认抽取人数", icon: "mdi-counter", }, "randomPicker.excludeAbsent": { type: "boolean", default: true, description: "是否排除请假学生", icon: "mdi-account-off", }, "randomPicker.excludeLate": { type: "boolean", default: false, description: "是否排除迟到学生", icon: "mdi-clock-alert", }, "randomPicker.excludeExcluded": { type: "boolean", default: true, description: "是否排除不参与学生", icon: "mdi-account-cancel", }, }; /** * 设置管理器单例类 */ class SettingsManagerClass { constructor() { this.settingsCache = null; this.isInitialized = false; } /** * 初始化设置管理器 */ init() { if (this.isInitialized) return; this.loadSettings(); this.isInitialized = true; } /** * 从localStorage加载所有设置 * @returns {Object} 所有设置的值 */ loadSettings() { try { const stored = typeof localStorage !== 'undefined' ? localStorage.getItem(SETTINGS_STORAGE_KEY) : null; if (stored) { this.settingsCache = JSON.parse(stored); } else { // 首次使用或迁移旧数据 this.settingsCache = this.migrateFromLegacy(); } } catch (error) { console.error("加载设置失败:", error); this.settingsCache = {}; } // 确保所有设置项都有值(使用默认值填充) for (const [key, definition] of Object.entries(settingsDefinitions)) { if (!(key in this.settingsCache)) { this.settingsCache[key] = definition.default; } } return this.settingsCache; } /** * 从旧版本的localStorage迁移数据 */ migrateFromLegacy() { if (typeof localStorage === 'undefined') return {}; const LEGACY_SETTINGS_KEY = "homeworkpage_settings"; const LEGACY_MESSAGE_KEY = "homeworkpage_messages"; // 尝试从旧版本的设置中迁移 const legacySettings = localStorage.getItem(LEGACY_SETTINGS_KEY); if (legacySettings) { try { const settings = JSON.parse(legacySettings); localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings)); // 可选:删除旧的设置 localStorage.removeItem(LEGACY_SETTINGS_KEY); return settings; } catch (error) { console.error("迁移旧设置失败:", error); } } // 尝试从旧版本的message中迁移 const legacyMessages = localStorage.getItem(LEGACY_MESSAGE_KEY); if (legacyMessages) { try { const messages = JSON.parse(legacyMessages); localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(messages)); // 可选:删除旧的message localStorage.removeItem(LEGACY_MESSAGE_KEY); return messages; // 返回迁移后的消息 } catch (error) { console.error("迁移旧消息失败:", error); } } // 如果没有旧设置或迁移失败,返回空对象 return {}; } /** * 保存所有设置到localStorage */ saveSettings() { if (typeof localStorage === 'undefined') return; try { localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(this.settingsCache)); } catch (error) { console.error("保存设置失败:", error); } } /** * 获取设置项的值 * @param {string} key - 设置项键名 * @returns {any} 设置项的值 */ getSetting(key) { if (!this.isInitialized) { this.init(); } const definition = settingsDefinitions[key]; if (!definition) { console.warn(`未定义的设置项: ${key}`); return null; } // 确保开发者相关设置正确处理 if (definition.requireDeveloper) { const devEnabled = this.settingsCache["developer.enabled"]; if (!devEnabled) { return definition.default; } } // 检查是否使用Classworks云端存储,并覆盖特定设置 if (this.settingsCache["server.provider"] === "classworkscloud") { if (classworksCloudDefaults[key] !== undefined) { return classworksCloudDefaults[key]; } } const value = this.settingsCache[key]; return value !== undefined ? value : definition.default; } /** * 设置配置项的值 * @param {string} key - 设置项键名 * @param {any} value - 要设置的值 * @returns {boolean} 是否设置成功 */ setSetting(key, value) { if (!this.isInitialized) { this.init(); } const definition = settingsDefinitions[key]; if (!definition) { console.warn(`未定义的设置项: ${key}`); return false; } // 添加对开发者选项依赖的检查 if (definition.requireDeveloper && !this.settingsCache["developer.enabled"]) { console.warn(`设置项 ${key} 需要启用开发者选项`); return false; } try { const oldValue = this.settingsCache[key]; // 类型转换 if (typeof value !== definition.type) { value = definition.type === "boolean" ? Boolean(value) : definition.type === "number" ? Number(value) : String(value); } // 验证 if (definition.validate && !definition.validate(value)) { console.warn(`设置项 ${key} 的值无效`); return false; } this.settingsCache[key] = value; this.saveSettings(); this.logSettingsChange(key, oldValue, value); // 为了保持向后兼容,同时更新旧的localStorage键 const legacyKey = definition.legacyKey; if (legacyKey && typeof localStorage !== 'undefined') { localStorage.setItem(legacyKey, value.toString()); } return true; } catch (error) { console.error(`设置配置项 ${key} 失败:`, error); return false; } } /** * 记录设置变更 */ logSettingsChange(key, oldValue, newValue) { const shouldLog = this.settingsCache["developer.enabled"] && this.settingsCache["developer.showDebugConfig"]; if (shouldLog) { console.log(`[Settings] ${key}:`, { old: oldValue, new: newValue, time: new Date().toLocaleTimeString(), }); } } /** * 重置指定设置项到默认值 * @param {string} key - 设置项键名 */ resetSetting(key) { if (!this.isInitialized) { this.init(); } const definition = settingsDefinitions[key]; if (!definition) { console.warn(`未定义的设置项: ${key}`); return; } this.settingsCache[key] = definition.default; this.saveSettings(); } /** * 重置所有设置项到默认值 */ resetAllSettings() { this.settingsCache = {}; for (const [key, definition] of Object.entries(settingsDefinitions)) { this.settingsCache[key] = definition.default; } this.saveSettings(); } /** * 监听设置变化 * @param {Function} callback - 当设置改变时调用的回调函数 * @returns {Function} 取消监听的函数 */ watchSettings(callback) { if (typeof window === 'undefined') return () => {}; const handler = (event) => { if (event.key === SETTINGS_STORAGE_KEY) { this.settingsCache = JSON.parse(event.newValue); callback(this.settingsCache); } }; window.addEventListener("storage", handler); return () => window.removeEventListener("storage", handler); } /** * 获取设置项的定义 * @param {string} key - 设置项键名 * @returns {SettingDefinition|null} 设置项的定义或null */ getSettingDefinition(key) { return settingsDefinitions[key] || null; } /** * 将当前配置导出为简单的键值对对象 * @returns {Object} 包含所有设置的键值对对象 */ exportSettingsAsKeyValue() { if (!this.isInitialized) { this.init(); } // 创建一个新对象,避免直接返回引用 const exportedSettings = {}; // 遍历所有设置项 for (const key in settingsDefinitions) { // 获取当前值(确保使用getSetting以应用所有规则,如开发者选项依赖) exportedSettings[key] = this.getSetting(key); } return exportedSettings; } } // 创建单例实例 const SettingsManager = new SettingsManagerClass(); // 在服务器端和客户端都能正常工作的初始化 if (typeof window !== 'undefined') { SettingsManager.init(); } // 为了向后兼容性,提供与原来相同的函数接口 const getSetting = (key) => SettingsManager.getSetting(key); const setSetting = (key, value) => SettingsManager.setSetting(key, value); const resetSetting = (key) => SettingsManager.resetSetting(key); const resetAllSettings = () => SettingsManager.resetAllSettings(); const watchSettings = (callback) => SettingsManager.watchSettings(callback); const getSettingDefinition = (key) => SettingsManager.getSettingDefinition(key); const exportSettingsAsKeyValue = () => SettingsManager.exportSettingsAsKeyValue(); // 导出单例和直接方法 export { settingsDefinitions, SettingsManager, getSetting, setSetting, resetSetting, resetAllSettings, watchSettings, getSettingDefinition, exportSettingsAsKeyValue };