1
This commit is contained in:
@@ -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: '未知错误'
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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, // 导出防抖函数
|
||||
};
|
||||
+80
-14
@@ -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: 'server',
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user