1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-09-03 16:19:22 +00:00

Refactor MigrationTool and data handling to support student list migration. Update index.vue and settings.vue to streamline configuration loading and improve data provider integration. Remove deprecated local and server storage providers for cleaner architecture.

This commit is contained in:
SunWuyuan 2025-05-10 21:49:18 +08:00
parent a542e9b91f
commit 3c722875dd
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
8 changed files with 209 additions and 544 deletions

View File

@ -624,8 +624,27 @@ export default {
const itemType = this.getItemType(item);
if (itemType === 'config') {
// : classNumber/classworks-config
await db.put("kv", JSON.stringify(value), `classworks-config`);
// Handle student list migration
if (value.studentList && Array.isArray(value.studentList)) {
// Extract studentList from config and save it separately
const formattedStudentList = value.studentList.map((name, index) => ({
id: index + 1,
name
}));
// Store the student list under the new key
await db.put("kv", JSON.stringify(formattedStudentList), 'classworks-list-main');
// Remove studentList from config
const configWithoutStudentList = { ...value };
delete configWithoutStudentList.studentList;
// Save the modified config
await db.put("kv", JSON.stringify(configWithoutStudentList), `classworks-config`);
} else {
// Just store the config as is
await db.put("kv", JSON.stringify(value), `classworks-config`);
}
return { success: true, message: '配置已迁移' };
} else {
// : classNumber/classworks-data-YYYYMMDD
@ -661,10 +680,33 @@ export default {
const itemType = this.getItemType(item);
if (itemType === 'config') {
//
await axios.post(`${this.targetServerUrl}/${this.machineId}/classworks-config`, value, {
headers: this.getRequestHeaders()
});
// Handle student list migration
if (value.studentList && Array.isArray(value.studentList)) {
// Extract studentList from config
const formattedStudentList = value.studentList.map((name, index) => ({
id: index + 1,
name
}));
// Store the student list under the new key
await axios.post(`${this.targetServerUrl}/${this.machineId}/classworks-list-main`, formattedStudentList, {
headers: this.getRequestHeaders()
});
// Remove studentList from config
const configWithoutStudentList = { ...value };
delete configWithoutStudentList.studentList;
// Save the modified config
await axios.post(`${this.targetServerUrl}/${this.machineId}/classworks-config`, configWithoutStudentList, {
headers: this.getRequestHeaders()
});
} else {
// Just store the config as is
await axios.post(`${this.targetServerUrl}/${this.machineId}/classworks-config`, value, {
headers: this.getRequestHeaders()
});
}
return { success: true, message: '配置已迁移到服务器' };
} else {
//

View File

@ -625,7 +625,6 @@
import MessageLog from "@/components/MessageLog.vue";
import RandomPicker from "@/components/RandomPicker.vue"; //
import dataProvider from "@/utils/dataProvider";
import { kvProvider } from "@/utils/providers/kvProvider";
import {
getSetting,
watchSettings,
@ -635,9 +634,9 @@ import {
import { useDisplay } from "vuetify";
import "../styles/index.scss";
import "../styles/transitions.scss"; //
import { debounce, throttle } from "@/utils/debounce";
import "../styles/global.scss";
import { pinyin } from "pinyin-pro";
import { debounce, throttle } from "@/utils/debounce";
export default {
name: "Classworks 作业板",
@ -811,7 +810,13 @@ export default {
return getSetting("edit.blockNonTodayAutoSave");
},
isToday() {
const today = new Date().toISOString().split("T")[0];
const today = (() => {
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, "0");
const dd = String(now.getDate()).padStart(2, "0");
return `${yyyy}${mm}${dd}`;
})();
return this.state.dateString === today;
},
canAutoSave() {
@ -1044,7 +1049,7 @@ export default {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
return `${year}${month}${day}`;
},
getToday() {
@ -1055,17 +1060,6 @@ export default {
// URL
const configApplied = await this.parseUrlConfig();
// URL使
if (!configApplied) {
this.provider = getSetting("server.provider");
const domain = getSetting("server.domain");
const classNum = getSetting("server.classNumber");
this.dataKey =
this.provider === "server" ? `${domain}/${classNum}` : classNum;
this.state.classNumber = classNum;
}
// URL 使
const urlParams = new URLSearchParams(window.location.search);
const dateFromUrl = urlParams.get("date");
@ -1077,7 +1071,13 @@ export default {
this.state.selectedDate = this.state.dateString;
this.state.isToday =
this.formatDate(currentDate) === this.formatDate(today);
// URL使
if (!configApplied) {
this.provider = getSetting("server.provider");
const classNum = getSetting("server.classNumber");
this.state.classNumber = classNum;
}
await Promise.all([this.downloadData(), this.loadConfig()]);
},
@ -1087,8 +1087,7 @@ export default {
try {
this.loading.download = true;
const response = await dataProvider.loadData(
this.dataKey,
this.state.dateString
"classworks-data-" + this.state.dateString
);
if (!response.success) {
@ -1192,9 +1191,8 @@ export default {
try {
this.loading.upload = true;
const response = await dataProvider.saveData(
this.dataKey,
this.state.boardData,
this.state.dateString
"classworks-data-" + this.state.dateString,
this.state.boardData
);
if (!response.success) {
@ -1210,22 +1208,23 @@ export default {
async loadConfig() {
try {
// 使kvProvider
const provider = getSetting("server.provider");
const useServer = provider === "kv-server" || provider === "classworkscloud";
let response;
try {
// Try to get student list from the dedicated key
const response = await dataProvider.loadData("classworks-list-main");
if (useServer) {
response = await kvProvider.server.loadConfig();
} else {
response = await kvProvider.local.loadConfig();
if (response.success && Array.isArray(response.data)) {
// Transform the data into a simple list of names
this.state.studentList = response.data.map(
(student) => student.name
);
return;
}
} catch (error) {
console.warn(
"Failed to load student list from dedicated key, falling back to config",
error
);
}
if (!response.success) {
throw new Error(response.error.message);
}
this.state.studentList = response.data.studentList || [];
} catch (error) {
console.error("加载配置失败:", error);
this.$message.error("加载配置失败", error.message);
@ -1326,11 +1325,10 @@ export default {
updateBackendUrl() {
const provider = getSetting("server.provider");
const domain = getSetting("server.domain");
const classNum = getSetting("server.classNumber");
this.provider = provider;
this.dataKey = provider === "server" || provider === "classworkscloud" ? `${domain}/${classNum}` : classNum;
this.state.classNumber = classNum;
},

View File

@ -190,6 +190,8 @@ import '../styles/settings.scss';
import { kvProvider } from '@/utils/providers/kvProvider';
import SettingsExplorer from '@/components/settings/SettingsExplorer.vue';
import SettingsLinkGenerator from '@/components/SettingsLinkGenerator.vue';
import dataProvider from '@/utils/dataProvider';
export default {
name: 'Settings',
components: {
@ -376,25 +378,36 @@ export default {
throw new Error('请先设置班号');
}
const provider = getSetting('server.provider');
const useServer = provider === 'kv-server' || provider === 'classworkscloud';
let response;
if (useServer) {
response = await kvProvider.server.loadConfig();
} else {
response = await kvProvider.local.loadConfig();
try {
// Try to get student list from the dedicated key
const response = await dataProvider.loadData('classworks-list-main');
if (response.success && Array.isArray(response.data)) {
// Transform the data into a simple list of names
this.studentData.list = response.data.map(student => student.name);
this.studentData.text = this.studentData.list.join('\n');
this.lastSavedData = [...this.studentData.list];
this.hasUnsavedChanges = false;
return;
}
} catch (error) {
console.warn('Failed to load student list from dedicated key, falling back to config', error);
}
if (!response.success) {
throw new Error(response.error.message);
}
// Fall back to retrieving from config if the dedicated key is not available
const response = await kvProvider.local.loadConfig();
if (response.data && Array.isArray(response.data.studentList)) {
if (response.success && response.data && Array.isArray(response.data.studentList)) {
this.studentData.list = response.data.studentList;
this.studentData.text = response.data.studentList.join('\n');
this.lastSavedData = [...response.data.studentList];
this.hasUnsavedChanges = false;
} else {
// If no student list is found anywhere, initialize with empty list
this.studentData.list = [];
this.studentData.text = '';
this.lastSavedData = [];
}
} catch (error) {
console.error('加载学生列表失败:', error);
@ -413,22 +426,18 @@ export default {
throw new Error('请先设置班号');
}
const provider = getSetting('server.provider');
const useServer = provider === 'kv-server' || provider === 'classworkscloud';
let response;
if (useServer) {
response = await kvProvider.server.saveConfig({
studentList: this.studentData.list,
});
} else {
response = await kvProvider.local.saveConfig({
studentList: this.studentData.list,
});
}
// Convert the list of names to the new format with IDs
const formattedStudentList = this.studentData.list.map((name, index) => ({
id: index + 1,
name
}));
// Save the student list to the dedicated key
const response = await dataProvider.saveData("classworks-list-main", formattedStudentList);
if (!response.success) {
throw new Error(response.error.message);
throw new Error(response.error?.message || "保存失败");
}
//

View File

@ -15,25 +15,27 @@ export const formatError = (message, code = "UNKNOWN_ERROR") => ({
// Main data provider with simplified API
export default {
// Provider API methods
loadData: async (key, date) => {
loadData: async (key) => {
const provider = getSetting("server.provider");
const useServer = provider === "kv-server" || provider === "classworkscloud";
const useServer =
provider === "kv-server" || provider === "classworkscloud";
if (useServer) {
return kvProvider.server.loadData(key, date);
return kvProvider.server.loadData(key);
} else {
return kvProvider.local.loadData(date);
return kvProvider.local.loadData(key);
}
},
saveData: async (key, data, date) => {
saveData: async (key, data) => {
const provider = getSetting("server.provider");
const useServer = provider === "kv-server" || provider === "classworkscloud";
const useServer =
provider === "kv-server" || provider === "classworkscloud";
if (useServer) {
return kvProvider.server.saveData(key, data, date);
return kvProvider.server.saveData(key, data);
} else {
return kvProvider.local.saveData(data, date);
return kvProvider.local.saveData(key, data);
}
},
};

View File

@ -1,99 +0,0 @@
import { openDB } from 'idb';
import { formatResponse, formatError } from '../dataProvider';
import { defaultConfig, defaultHomework } from '../defaults/defaultData';
const DB_NAME = "ClassworksDB";
const DB_VERSION = 1;
const initDB = async () => {
return openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
if (!db.objectStoreNames.contains("homework")) {
db.createObjectStore("homework");
}
if (!db.objectStoreNames.contains("config")) {
db.createObjectStore("config");
}
},
});
};
export const indexedDBProvider = {
async loadData(key, date) {
try {
const classNumber = key.split("/").pop();
if (!classNumber) {
return formatError("请先设置班号", "CONFIG_ERROR");
}
const db = await initDB();
const storageKey = `homework_${classNumber}_${date}`;
const data = await db.get("homework", storageKey);
if (!data) {
const today = new Date().toISOString().split("T")[0];
if (date === today) {
return formatResponse(defaultHomework);
}
return formatError("数据不存在", "NOT_FOUND");
}
return formatResponse(JSON.parse(data));
} catch (error) {
return formatError("读取IndexedDB数据失败" + error);
}
},
async saveData(key, data, date) {
try {
const classNumber = key.split("/").pop();
if (!classNumber) {
return formatError("请先设置班号", "CONFIG_ERROR");
}
const db = await initDB();
const storageKey = `homework_${classNumber}_${date}`;
await db.put("homework", JSON.stringify(data), storageKey);
return formatResponse(null, "保存成功");
} catch (error) {
return formatError("保存IndexedDB数据失败" + error);
}
},
async loadConfig(key) {
try {
const classNumber = key.split("/").pop();
if (!classNumber) {
return formatError("请先设置班号", "CONFIG_ERROR");
}
const db = await initDB();
const storageKey = `config_${classNumber}`;
const config = await db.get("config", storageKey);
if (!config) {
return formatResponse(defaultConfig);
}
return formatResponse(JSON.parse(config));
} catch (error) {
return formatError("读取IndexedDB配置失败" + error);
}
},
async saveConfig(key, config) {
try {
const classNumber = key.split("/").pop();
if (!classNumber) {
return formatError("请先设置班号", "CONFIG_ERROR");
}
const db = await initDB();
const storageKey = `config_${classNumber}`;
await db.put("config", JSON.stringify(config), storageKey);
return formatResponse(null, "保存成功");
} catch (error) {
return formatError("保存IndexedDB配置失败" + error);
}
}
};

View File

@ -1,11 +1,7 @@
import axios from '@/axios/axios';
import { formatResponse, formatError } from '../dataProvider';
import { openDB } from 'idb';
import { getSetting } from '../settings';
// Constants for key names
const CONFIG_KEY = 'classworks-config';
const DATA_KEY_PREFIX = 'classworks-data-';
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";
@ -13,220 +9,104 @@ 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");
const headers = { Accept: "application/json" };
const siteKey = getSetting("server.siteKey");
if (siteKey) {
headers['x-site-key'] = siteKey;
}
if (siteKey) {
headers["x-site-key"] = siteKey;
}
return headers;
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");
}
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");
}
},
});
};
// Format date as YYYYMMDD for keys
const formatDateForKey = (date) => {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
// 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(date) {
try {
const formattedDate = formatDateForKey(date);
const key = `${DATA_KEY_PREFIX}${formattedDate}`;
// Local storage provider
local: {
async loadData(key) {
try {
const db = await initDB();
const data = await db.get("kv", key);
const db = await initDB();
const data = await db.get("kv", key);
if (!data) {
const today = new Date().toISOString().split("T")[0];
if (date === today) {
// Return default data for today
return formatResponse({
homework: {},
attendance: {
absent: [],
late: []
}
});
}
return formatError("数据不存在", "NOT_FOUND");
}
return formatResponse(JSON.parse(data));
} catch (error) {
return formatError("读取本地数据失败:" + error);
}
},
async saveData(data, date) {
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(null, "保存成功");
} catch (error) {
return formatError("保存本地数据失败:" + error);
}
},
async loadConfig() {
try {
const db = await initDB();
const config = await db.get("kv", CONFIG_KEY);
if (!config) {
return formatResponse({
studentList: [
"Classworks可以管理学生列表",
'你可以点击设置,在其中找到"学生列表"',
"在添加学生处输入学生姓名,点击添加",
"或者点击高级编辑从Excel表格中复制数据并粘贴进来",
],
});
}
return formatResponse(JSON.parse(config));
} catch (error) {
return formatError("读取本地配置失败:" + error);
}
},
async saveConfig(config) {
try {
const db = await initDB();
await db.put("kv", JSON.stringify(config), CONFIG_KEY);
return formatResponse(null, "保存成功");
} catch (error) {
return formatError("保存本地配置失败:" + error);
}
if (!data) {
return formatError("数据不存在", "NOT_FOUND");
}
return formatResponse(JSON.parse(data));
} catch (error) {
return formatError("读取本地数据失败:" + error);
}
},
// Server storage provider
server: {
async loadData(classNumber, date) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const formattedDate = formatDateForKey(date);
const key = `${DATA_KEY_PREFIX}${formattedDate}`;
async saveData(data, key) {
try {
//const formattedDate = formatDateForKey(date);
//const key = `${DATA_KEY_PREFIX}${formattedDate}`;
const res = await axios.get(`${serverUrl}/${machineId}/${key}`, {
headers: getHeaders()
});
const db = await initDB();
await db.put("kv", JSON.stringify(data), key);
return formatResponse(null, "保存成功");
} catch (error) {
return formatError("保存本地数据失败:" + error);
}
},
},
return formatResponse(res.data);
} catch (error) {
if (error.response?.status === 404) {
const today = new Date().toISOString().split("T")[0];
if (date === today) {
// Return default data for today
return formatResponse({
homework: {},
attendance: {
absent: [],
late: []
}
});
}
return formatError("数据不存在", "NOT_FOUND");
}
// Server storage provider
server: {
async loadData(key) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
return formatError(
error.response?.data?.message || "服务器连接失败",
"NETWORK_ERROR"
);
}
},
const res = await axios.get(`${serverUrl}/${machineId}/${key}`, {
headers: getHeaders(),
});
async saveData(classNumber, data, date) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const formattedDate = formatDateForKey(date);
const key = `${DATA_KEY_PREFIX}${formattedDate}`;
await axios.post(`${serverUrl}/${machineId}/${key}`, data, {
headers: getHeaders()
});
return formatResponse(null, "保存成功");
} catch (error) {
return formatError(
error.response?.data?.message || "保存失败",
"SAVE_ERROR"
);
}
},
async loadConfig() {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
const res = await axios.get(`${serverUrl}/${machineId}/${CONFIG_KEY}`, {
headers: getHeaders()
});
return formatResponse(res.data);
} catch (error) {
if (error.response?.status === 404) {
return formatResponse({
studentList: [
"Classworks可以管理学生列表",
'你可以点击设置,在其中找到"学生列表"',
"在添加学生处输入学生姓名,点击添加",
"或者点击高级编辑从Excel表格中复制数据并粘贴进来",
],
});
}
return formatError(
error.response?.data?.message || "服务器连接失败",
"NETWORK_ERROR"
);
}
},
async saveConfig(config) {
try {
const serverUrl = getSetting("server.domain");
const machineId = getSetting("device.uuid");
await axios.post(`${serverUrl}/${machineId}/${CONFIG_KEY}`, config, {
headers: getHeaders()
});
return formatResponse(null, "保存成功");
} catch (error) {
return formatError(
error.response?.data?.message || "保存失败",
"SAVE_ERROR"
);
}
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(null, "保存成功");
} catch (error) {
return formatError(
error.response?.data?.message || "保存失败",
"SAVE_ERROR"
);
}
},
},
};

View File

@ -1,83 +0,0 @@
import { formatResponse, formatError } from '../dataProvider';
export const localStorageProvider = {
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) {
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("读取本地数据失败:" + error);
}
},
async saveData(key, data, date) {
try {
const classNumber = key.split("/").pop();
if (!classNumber) {
return formatError("请先设置班号", "CONFIG_ERROR");
}
const storageKey = `homework_${classNumber}_${date}`; // 使用传入的date参数
localStorage.setItem(storageKey, JSON.stringify(data));
return formatResponse(null, "保存成功");
} catch (error) {
return formatError("保存本地数据失败:" + error);
}
},
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("读取本地配置失败:" + error);
}
},
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("保存本地配置失败:" + error);
}
}
};

View File

@ -1,84 +0,0 @@
import axios from '@/axios/axios';
import { formatResponse, formatError } from '../dataProvider';
import { getSetting } from '../settings';
// 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;
};
export const serverProvider = {
async loadData(key, date) {
try {
const res = await axios.get(`${key}/homework?date=${date}`, {
headers: getHeaders()
});
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, date) {
try {
// 添加date参数到URL
const url = date ? `${key}/homework?date=${date}` : `${key}/homework`;
await axios.post(url, data, {
headers: getHeaders()
});
return formatResponse(null, "保存成功");
} catch (error) {
return formatError(
error.response?.data?.message || "保存失败",
"SAVE_ERROR"
);
}
},
async loadConfig(key) {
try {
const res = await axios.get(`${key}/config`, {
headers: getHeaders()
});
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.put(`${key}/config`, config, {
headers: getHeaders()
});
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"
);
}
}
};