Enhance App.vue, axios.js, MigrationTool.vue, index.vue, settings.vue, and related utilities to support namespace management and improve configuration handling. Add namespace password to axios request headers, load namespace info on App mount, and update settings structure to include namespace settings. Refactor dataProvider to utilize new kvLocalProvider and kvServerProvider for data operations, ensuring better separation of concerns and improved error handling.

This commit is contained in:
SunWuyuan
2025-05-18 10:54:39 +08:00
parent 6d6e4a27a1
commit c42c878ac8
12 changed files with 1249 additions and 441 deletions
+7 -6
View File
@@ -1,7 +1,8 @@
import { kvProvider } from "./providers/kvProvider";
import { kvLocalProvider } from "./providers/kvLocalProvider";
import { kvServerProvider } from "./providers/kvServerProvider";
import { getSetting } from "./settings";
export const formatResponse = (data, message = null) => (data);
export const formatResponse = (data) => data;
export const formatError = (message, code = "UNKNOWN_ERROR") => ({
success: false,
@@ -17,9 +18,9 @@ export default {
provider === "kv-server" || provider === "classworkscloud";
if (useServer) {
return kvProvider.server.loadData(key);
return kvServerProvider.loadData(key);
} else {
return kvProvider.local.loadData(key);
return kvLocalProvider.loadData(key);
}
},
@@ -29,9 +30,9 @@ export default {
provider === "kv-server" || provider === "classworkscloud";
if (useServer) {
return kvProvider.server.saveData(key, data);
return kvServerProvider.saveData(key, data);
} else {
return kvProvider.local.saveData(key, data);
return kvLocalProvider.saveData(key, data);
}
},
};
+49
View File
@@ -0,0 +1,49 @@
import { openDB } from "idb";
import { formatResponse, formatError } from "../dataProvider";
// Database initialization for local storage
const DB_NAME = "ClassworksDB";
const DB_VERSION = 2;
const initDB = async () => {
return openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
// Create or update stores as needed
if (!db.objectStoreNames.contains("kv")) {
db.createObjectStore("kv");
}
// Add a system store for machine ID and other system settings
if (!db.objectStoreNames.contains("system")) {
db.createObjectStore("system");
}
},
});
};
export const kvLocalProvider = {
async loadData(key) {
try {
const db = await initDB();
const data = await db.get("kv", key);
if (!data) {
return formatError("数据不存在", "NOT_FOUND");
}
return formatResponse(JSON.parse(data));
} catch (error) {
return formatError("读取本地数据失败:" + error);
}
},
async saveData(key, data) {
try {
const db = await initDB();
await db.put("kv", JSON.stringify(data), key);
return formatResponse(true);
} catch (error) {
return formatError("保存本地数据失败:" + error);
}
},
};
-112
View File
@@ -1,112 +0,0 @@
import axios from "@/axios/axios";
import { formatResponse, formatError } from "../dataProvider";
import { openDB } from "idb";
import { getSetting } from "../settings";
// Database initialization for local storage
const DB_NAME = "ClassworksDB";
const DB_VERSION = 2;
// Helper function to get request headers with site key if available
const getHeaders = () => {
const headers = { Accept: "application/json" };
const siteKey = getSetting("server.siteKey");
if (siteKey) {
headers["x-site-key"] = siteKey;
}
return headers;
};
// Removed migrateToKvStorage function - now handled by the dedicated migration tool
const initDB = async () => {
return openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
// Create or update stores as needed
if (!db.objectStoreNames.contains("kv")) {
db.createObjectStore("kv");
}
// Add a system store for machine ID and other system settings
if (!db.objectStoreNames.contains("system")) {
db.createObjectStore("system");
}
},
});
};
export const kvProvider = {
// Local storage provider
local: {
async loadData(key) {
try {
const db = await initDB();
const data = await db.get("kv", key);
if (!data) {
return formatError("数据不存在", "NOT_FOUND");
}
return formatResponse(JSON.parse(data));
} catch (error) {
return formatError("读取本地数据失败:" + error);
}
},
async saveData(data, key) {
try {
//const formattedDate = formatDateForKey(date);
//const key = `${DATA_KEY_PREFIX}${formattedDate}`;
const db = await initDB();
await db.put("kv", JSON.stringify(data), key);
return formatResponse(true, "保存成功");
} catch (error) {
return formatError("保存本地数据失败:" + error);
}
},
},
// Server storage provider
server: {
async loadData(key) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.get(`${serverUrl}/${machineId}/${key}`, {
headers: getHeaders(),
});
return formatResponse(res.data);
} catch (error) {
if (error.response?.status === 404) {
return formatError("数据不存在", "NOT_FOUND");
}
return formatError(
error.response?.data?.message || "服务器连接失败",
"NETWORK_ERROR"
);
}
},
async saveData(key, data) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
await axios.post(`${serverUrl}/${machineId}/${key}`, data, {
headers: getHeaders(),
});
return formatResponse(true);
} catch (error) {
return formatError(
error.response?.data?.message || "保存失败",
"SAVE_ERROR"
);
}
},
},
};
+161
View File
@@ -0,0 +1,161 @@
import axios from "@/axios/axios";
import { formatResponse, formatError } from "../dataProvider";
import { getSetting, setSetting } from "../settings";
// Helper function to get request headers with site key and password if available
const getHeaders = () => {
const headers = { Accept: "application/json" };
const siteKey = getSetting("server.siteKey");
const password = getSetting("namespace.password");
if (siteKey) {
headers["x-site-key"] = siteKey;
}
if (password) {
headers["x-namespace-password"] = password;
}
return headers;
};
export const kvServerProvider = {
async loadNamespaceInfo() {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.get(`${serverUrl}/${machineId}/_info`, {
headers: getHeaders(),
});
const { name, accessType } = res.data;
// 如果name为null,使用班级号作为名称并更新
if (name === null) {
const classNumber = getSetting("server.classNumber");
await this.updateNamespaceInfo({ name: classNumber });
// 重新加载命名空间信息
return await this.loadNamespaceInfo();
}
// 更新本地访问权限设置
if (accessType) {
setSetting("namespace.accessType", accessType);
}
return formatResponse(res);
} catch (error) {
return formatError(
error.response?.data?.message || "获取命名空间信息失败",
"NAMESPACE_ERROR"
);
}
},
async updateNamespaceInfo(data) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.put(`${serverUrl}/${machineId}/_info`, data, {
headers: getHeaders(),
});
return res;
} catch (error) {
return formatError(
error.response?.data?.message || "更新命名空间信息失败",
"NAMESPACE_ERROR"
);
}
},
async updatePassword(newPassword, oldPassword) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.post(
`${serverUrl}/${machineId}/_password`,
{
newPassword,
oldPassword,
},
{
headers: getHeaders(),
}
);
if (res.status == 200) {
setSetting("namespace.password", newPassword);
}
return formatResponse(res);
} catch (error) {
return formatError(
error.response?.data?.message || "更新密码失败",
"PASSWORD_ERROR"
);
}
},
async deletePassword() {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.delete(`${serverUrl}/${machineId}/_password`, {
headers: getHeaders(),
});
if (res.status === 200) {
setSetting("namespace.password", null); // 清除本地存储的密码
}
return formatResponse(res);
} catch (error) {
return formatError(
error.response?.data?.message || "删除密码失败",
"PASSWORD_ERROR"
);
}
},
async loadData(key) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.get(`${serverUrl}/${machineId}/${key}`, {
headers: getHeaders(),
});
return formatResponse(res.data);
} catch (error) {
if (error.response?.status === 404) {
return formatError("数据不存在", "NOT_FOUND");
}
return formatError(
error.response?.data?.message || "服务器连接失败",
"NETWORK_ERROR"
);
}
},
async saveData(key, data) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
await axios.post(`${serverUrl}/${machineId}/${key}`, data, {
headers: getHeaders(),
});
return formatResponse(true);
} catch (error) {
return formatError(
error.response?.data?.message || "保存失败",
"SAVE_ERROR"
);
}
},
};
+45 -56
View File
@@ -37,14 +37,17 @@ async function requestPersistentStorage() {
*/
async function initializeStorage() {
const notificationGranted = await requestNotificationPermission();
if (notificationGranted && SettingsManager.getSetting("storage.persistOnLoad")) {
if (
notificationGranted &&
SettingsManager.getSetting("storage.persistOnLoad")
) {
const persisted = await requestPersistentStorage();
console.log(`持久性存储状态: ${persisted ? "已启用" : "未启用"}`);
}
}
// 在页面加载时初始化
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
window.addEventListener("load", initializeStorage);
}
@@ -90,6 +93,21 @@ const settingsDefinitions = {
icon: "mdi-identifier",
},
// 命名空间设置
"namespace.password": {
type: "string",
default: "",
description: "命名空间访问密码",
icon: "mdi-key",
},
"namespace.accessType": {
type: "string",
default: "readwrite",
description: "访问权限类型",
icon: "mdi-shield-lock",
validate: (value) => ["readonly", "readwrite"].includes(value),
},
// 存储设置
"storage.persistOnLoad": {
type: "boolean",
@@ -191,7 +209,8 @@ const settingsDefinitions = {
"server.provider": {
type: "string",
default: "kv-local",
validate: (value) => ["kv-local", "kv-server", "classworkscloud"].includes(value),
validate: (value) =>
["kv-local", "kv-server", "classworkscloud"].includes(value),
description: "数据提供者",
icon: "mdi-database",
// 选择数据存储方式:使用本地存储或远程服务器
@@ -388,17 +407,20 @@ class SettingsManagerClass {
* @returns {Object} 所有设置的值
*/
loadSettings() {
// Initialize settingsCache as an empty object first
this.settingsCache = {};
try {
const stored = typeof localStorage !== 'undefined' ? localStorage.getItem(SETTINGS_STORAGE_KEY) : null;
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 = {};
// settingsCache is already an empty object, no need to reinitialize
}
// 确保所有设置项都有值(使用默认值填充)
@@ -411,54 +433,17 @@ class SettingsManagerClass {
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;
if (typeof localStorage === "undefined") return;
try {
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(this.settingsCache));
localStorage.setItem(
SETTINGS_STORAGE_KEY,
JSON.stringify(this.settingsCache)
);
} catch (error) {
console.error("保存设置失败:", error);
}
@@ -517,7 +502,10 @@ class SettingsManagerClass {
}
// 添加对开发者选项依赖的检查
if (definition.requireDeveloper && !this.settingsCache["developer.enabled"]) {
if (
definition.requireDeveloper &&
!this.settingsCache["developer.enabled"]
) {
console.warn(`设置项 ${key} 需要启用开发者选项`);
return false;
}
@@ -546,7 +534,7 @@ class SettingsManagerClass {
// 为了保持向后兼容,同时更新旧的localStorage键
const legacyKey = definition.legacyKey;
if (legacyKey && typeof localStorage !== 'undefined') {
if (legacyKey && typeof localStorage !== "undefined") {
localStorage.setItem(legacyKey, value.toString());
}
@@ -610,7 +598,7 @@ class SettingsManagerClass {
* @returns {Function} 取消监听的函数
*/
watchSettings(callback) {
if (typeof window === 'undefined') return () => {};
if (typeof window === "undefined") return () => {};
const handler = (event) => {
if (event.key === SETTINGS_STORAGE_KEY) {
@@ -658,7 +646,7 @@ class SettingsManagerClass {
const SettingsManager = new SettingsManagerClass();
// 在服务器端和客户端都能正常工作的初始化
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
SettingsManager.init();
}
@@ -669,7 +657,8 @@ 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();
const exportSettingsAsKeyValue = () =>
SettingsManager.exportSettingsAsKeyValue();
// 导出单例和直接方法
export {
@@ -681,5 +670,5 @@ export {
resetAllSettings,
watchSettings,
getSettingDefinition,
exportSettingsAsKeyValue
exportSettingsAsKeyValue,
};