From cd10d0f49ad8a771b2d30cc1378f829a6881ec98 Mon Sep 17 00:00:00 2001 From: SunWuyuan Date: Fri, 29 Aug 2025 21:24:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0key=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/cards/KvDatabaseCard.vue | 616 ++++++++++++++++++ src/pages/index.vue | 34 +- src/pages/settings.vue | 3 + src/utils/dataProvider.js | 53 ++ src/utils/providers/kvLocalProvider.js | 68 ++ src/utils/providers/kvServerProvider.js | 65 ++ 6 files changed, 826 insertions(+), 13 deletions(-) create mode 100644 src/components/settings/cards/KvDatabaseCard.vue diff --git a/src/components/settings/cards/KvDatabaseCard.vue b/src/components/settings/cards/KvDatabaseCard.vue new file mode 100644 index 0000000..bf46db5 --- /dev/null +++ b/src/components/settings/cards/KvDatabaseCard.vue @@ -0,0 +1,616 @@ + + + + + diff --git a/src/pages/index.vue b/src/pages/index.vue index cf70d52..cb032d3 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -1194,23 +1194,26 @@ export default { ); } - // 加载科目配置 - try { - const subjectsResponse = await dataProvider.loadData("classworks-config-subject"); - if (subjectsResponse && Array.isArray(subjectsResponse)) { - // 更新科目列表 - this.state.availableSubjects = subjectsResponse; - } - } catch (error) { - console.warn("Failed to load subject configuration:", error); - // 保持默认科目列表 - } + await this.loadSubjects(); } catch (error) { console.error("加载配置失败:", error); this.$message.error("加载配置失败", error.message); } }, + async loadSubjects() { + try { + const subjectsResponse = await dataProvider.loadData("classworks-config-subject"); + if (subjectsResponse && Array.isArray(subjectsResponse)) { + // 更新科目列表 + this.state.availableSubjects = subjectsResponse; + } + } catch (error) { + console.warn("Failed to load subject configuration:", error); + // 保持默认科目列表 + } + }, + showSyncMessage() { this.$message.success("数据已同步", "数据已完成与服务器同步"); }, @@ -1358,7 +1361,7 @@ export default { this.updateBackendUrl(); }, - handleDateSelect(newDate) { + async handleDateSelect(newDate) { if (!newDate) return; try { @@ -1377,7 +1380,12 @@ export default { query: { date: formattedDate }, }) .catch(() => {}); - this.downloadData(); + + // Load both data and subjects in parallel + await Promise.all([ + this.downloadData(), + this.loadSubjects() + ]); } } catch (error) { console.error("Date processing error:", error); diff --git a/src/pages/settings.vue b/src/pages/settings.vue index 233baa0..cd3559f 100644 --- a/src/pages/settings.vue +++ b/src/pages/settings.vue @@ -104,6 +104,7 @@ @saved="onSettingsSaved" /> + @@ -241,6 +242,7 @@ import NamespaceSettingsCard from "@/components/settings/cards/NamespaceSettings import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue"; import HomeworkTemplateCard from "@/components/settings/cards/HomeworkTemplateCard.vue"; import SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue"; +import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue"; export default { name: "Settings", components: { @@ -261,6 +263,7 @@ export default { RandomPickerCard, HomeworkTemplateCard, SubjectManagementCard, + KvDatabaseCard, }, setup() { const { mobile } = useDisplay(); diff --git a/src/utils/dataProvider.js b/src/utils/dataProvider.js index 34795b3..43629e4 100644 --- a/src/utils/dataProvider.js +++ b/src/utils/dataProvider.js @@ -35,6 +35,57 @@ export default { return kvLocalProvider.saveData(key, data); } }, + + /** + * 获取键名列表 + * @param {Object} options - 查询选项 + * @param {string} options.sortBy - 排序字段,默认为 "key" + * @param {string} options.sortDir - 排序方向,"asc" 或 "desc",默认为 "asc" + * @param {number} options.limit - 每页返回的记录数,默认为 100 + * @param {number} options.skip - 跳过的记录数,默认为 0 + * @returns {Promise} 包含键名列表和分页信息的响应对象 + * + * 使用示例: + * ```javascript + * // 获取前10个键名 + * const result = await dataProvider.loadKeys({ limit: 10 }); + * if (result.success !== false) { + * console.log('键名列表:', result.keys); + * console.log('总数:', result.total_rows); + * } + * + * // 获取第二页数据(跳过前10个) + * const page2 = await dataProvider.loadKeys({ limit: 10, skip: 10 }); + * + * // 按键名降序排列 + * const sorted = await dataProvider.loadKeys({ sortDir: 'desc' }); + * ``` + * + * 返回值格式: + * ```javascript + * { + * keys: ["key1", "key2", "key3"], + * total_rows: 150, + * current_page: { + * limit: 10, + * skip: 0, + * count: 10 + * }, + * load_more: "/api/kv/namespace/_keys?..." // 仅服务器模式 + * } + * ``` + */ + loadKeys: async (options = {}) => { + const provider = getSetting("server.provider"); + const useServer = + provider === "kv-server" || provider === "classworkscloud"; + + if (useServer) { + return kvServerProvider.loadKeys(options); + } else { + return kvLocalProvider.loadKeys(options); + } + }, }; export const ErrorCodes = { @@ -43,5 +94,7 @@ export const ErrorCodes = { SERVER_ERROR: "服务器错误", SAVE_ERROR: "保存失败", CONFIG_ERROR: "配置错误", + PERMISSION_DENIED: "无权限访问", + UNAUTHORIZED: "认证失败", UNKNOWN_ERROR: "未知错误", }; diff --git a/src/utils/providers/kvLocalProvider.js b/src/utils/providers/kvLocalProvider.js index 719b24d..b1dd3ff 100644 --- a/src/utils/providers/kvLocalProvider.js +++ b/src/utils/providers/kvLocalProvider.js @@ -46,4 +46,72 @@ export const kvLocalProvider = { return formatError("保存本地数据失败:" + error); } }, + + /** + * 获取本地存储的键名列表 + * @param {Object} options - 查询选项 + * @param {string} options.sortBy - 排序字段,默认为 "key" + * @param {string} options.sortDir - 排序方向,"asc" 或 "desc",默认为 "asc" + * @param {number} options.limit - 每页返回的记录数,默认为 100 + * @param {number} options.skip - 跳过的记录数,默认为 0 + * @returns {Promise} 包含键名列表和分页信息的响应对象 + * + * 返回值示例: + * { + * keys: ["key1", "key2", "key3"], + * total_rows: 150, + * current_page: { + * limit: 10, + * skip: 0, + * count: 10 + * }, + * load_more: null // 本地存储不需要分页URL + * } + */ + async loadKeys(options = {}) { + try { + const db = await initDB(); + const transaction = db.transaction(["kv"], "readonly"); + const store = transaction.objectStore("kv"); + + // 获取所有键名 + const allKeys = await store.getAllKeys(); + + // 设置默认参数 + const { + sortBy = "key", + sortDir = "asc", + limit = 100, + skip = 0 + } = options; + + // 排序键名(本地存储只支持按键名排序) + const sortedKeys = allKeys.sort((a, b) => { + if (sortDir === "desc") { + return b.localeCompare(a); + } + return a.localeCompare(b); + }); + + // 应用分页 + const totalRows = sortedKeys.length; + const paginatedKeys = sortedKeys.slice(skip, skip + limit); + + // 构建响应数据 + const responseData = { + keys: paginatedKeys, + total_rows: totalRows, + current_page: { + limit, + skip, + count: paginatedKeys.length + }, + load_more: null // 本地存储不需要分页URL + }; + + return formatResponse(responseData); + } catch (error) { + return formatError("获取本地键名列表失败:" + error.message); + } + }, }; \ No newline at end of file diff --git a/src/utils/providers/kvServerProvider.js b/src/utils/providers/kvServerProvider.js index 8fcec6c..e240b27 100644 --- a/src/utils/providers/kvServerProvider.js +++ b/src/utils/providers/kvServerProvider.js @@ -157,4 +157,69 @@ export const kvServerProvider = { ); } }, + + /** + * 获取键名列表 + * @param {Object} options - 查询选项 + * @param {string} options.sortBy - 排序字段,默认为 "key" + * @param {string} options.sortDir - 排序方向,"asc" 或 "desc",默认为 "asc" + * @param {number} options.limit - 每页返回的记录数,默认为 100 + * @param {number} options.skip - 跳过的记录数,默认为 0 + * @returns {Promise} 包含键名列表和分页信息的响应对象 + * + * 返回值示例: + * { + * keys: ["key1", "key2", "key3"], + * total_rows: 150, + * current_page: { + * limit: 10, + * skip: 0, + * count: 10 + * }, + * load_more: "/api/kv/namespace/_keys?sortBy=key&sortDir=asc&limit=10&skip=10" + * } + */ + async loadKeys(options = {}) { + try { + const serverUrl = getSetting("server.domain"); + const machineId = getSetting("device.uuid"); + + // 设置默认参数 + const { + sortBy = "key", + sortDir = "asc", + limit = 100, + skip = 0 + } = options; + + // 构建查询参数 + const params = new URLSearchParams({ + sortBy, + sortDir, + limit: limit.toString(), + skip: skip.toString() + }); + + const res = await axios.get(`${serverUrl}/${machineId}/_keys?${params}`, { + headers: getHeaders(), + }); + + return formatResponse(res.data); + } catch (error) { + if (error.response?.status === 404) { + return formatError("命名空间不存在", "NOT_FOUND"); + } + if (error.response?.status === 403) { + return formatError("无权限访问此命名空间", "PERMISSION_DENIED"); + } + if (error.response?.status === 401) { + return formatError("认证失败", "UNAUTHORIZED"); + } + console.log(error); + return formatError( + error.response?.data?.message || "获取键名列表失败", + "NETWORK_ERROR" + ); + } + }, }; \ No newline at end of file