From bbd9cbccb71aac8a684943c00ec50c028ac264fa Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 19 May 2026 01:17:58 +0800 Subject: [PATCH] feat(account): add user management view --- src/App.vue | 63 ++++-- src/__tests__/unit/UserManagementView.test.ts | 48 +++++ src/components/UserManagementView.vue | 184 ++++++++++++++++++ src/global/accountSyncState.ts | 26 +++ 4 files changed, 309 insertions(+), 12 deletions(-) create mode 100644 src/__tests__/unit/UserManagementView.test.ts create mode 100644 src/components/UserManagementView.vue create mode 100644 src/global/accountSyncState.ts diff --git a/src/App.vue b/src/App.vue index ff021ac3..e2c98bb3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -84,17 +84,19 @@ @check-install="checkAppInstalled" /> -
-

- 用户管理 -

-

- 账号资料与安全设置功能即将开放。 -

-
+ (null); const favoriteLoading = ref(false); const favoriteError = ref(""); const favoriteRequestGeneration = ref(0); +const downloadedApps = ref([]); +const downloadedLoading = ref(false); +const downloadedError = ref(""); const systemInfo = ref({ distro: "unknown" }); type PendingDownloadRecord = Omit< DownloadedAppRecord, @@ -1399,6 +1410,12 @@ const clearFavoriteState = () => { favoriteError.value = ""; }; +const clearDownloadedState = () => { + downloadedApps.value = []; + downloadedLoading.value = false; + downloadedError.value = ""; +}; + const isCurrentFavoriteRequest = (generation: number): boolean => favoriteRequestGeneration.value === generation && isLoggedIn.value; @@ -1406,6 +1423,7 @@ const handleLogout = () => { logout(); pendingDownloadRecords.clear(); clearFavoriteState(); + clearDownloadedState(); showLoginModal.value = false; showLoginPrompt.value = false; isSidebarOpen.value = false; @@ -1435,12 +1453,33 @@ const handleFlarumLogin = async (payload: FlarumLoginPayload) => { } }; -const openUserManagement = () => { +const loadDownloadedHistory = async (): Promise => { + if (!requireLogin("请登录后查看和管理账号信息。")) return; + + downloadedLoading.value = true; + downloadedError.value = ""; + try { + const result = await listDownloadedApps(1, 50); + downloadedApps.value = result.items; + } catch (error: unknown) { + downloadedApps.value = []; + downloadedError.value = (error as Error)?.message || "读取下载历史失败"; + } finally { + downloadedLoading.value = false; + } +}; + +const syncInstalledAppsNow = () => { + logger.warn("已安装应用同步将在后续任务中启用"); +}; + +const openUserManagement = async () => { if (!requireLogin("请登录后查看和管理账号信息。")) return; currentView.value = "account"; activeTab.value = "account"; isSidebarOpen.value = false; showLoginPrompt.value = false; + await loadDownloadedHistory(); }; const loadFavoriteFolders = async ( diff --git a/src/__tests__/unit/UserManagementView.test.ts b/src/__tests__/unit/UserManagementView.test.ts new file mode 100644 index 00000000..d9811cb9 --- /dev/null +++ b/src/__tests__/unit/UserManagementView.test.ts @@ -0,0 +1,48 @@ +import { render, screen } from "@testing-library/vue"; +import { describe, expect, it } from "vitest"; + +import UserManagementView from "@/components/UserManagementView.vue"; +import type { DownloadedAppRecord, SparkUser } from "@/global/typedefinition"; + +const user: SparkUser = { + id: 1, + flarumUserId: "123", + username: "momen", + displayName: "Momen", + avatarUrl: "https://bbs.spark-app.store/avatar.png", + forumLevel: "管理员", + forumGroups: ["管理员"], +}; + +const download: DownloadedAppRecord = { + id: 1, + appKey: "app:office:wps", + pkgname: "wps", + name: "WPS", + category: "office", + selectedOrigin: "apm", + version: "1.0.0", + packageArch: "amd64", + downloadedAt: "2026-05-18T00:00:00Z", +}; + +describe("UserManagementView", () => { + it("renders profile, forum level, links, downloads, and sync preference", () => { + render(UserManagementView, { + props: { + user, + downloadedApps: [download], + syncEnabled: true, + loading: false, + error: "", + }, + }); + + expect(screen.getByText("Momen")).toBeTruthy(); + expect(screen.getByText("管理员")).toBeTruthy(); + expect(screen.getByText("论坛首页")).toBeTruthy(); + expect(screen.getByText("修改论坛资料")).toBeTruthy(); + expect(screen.getByText("WPS")).toBeTruthy(); + expect(screen.getByLabelText("自动同步已安装应用")).toBeChecked(); + }); +}); diff --git a/src/components/UserManagementView.vue b/src/components/UserManagementView.vue new file mode 100644 index 00000000..0f7711c8 --- /dev/null +++ b/src/components/UserManagementView.vue @@ -0,0 +1,184 @@ + + + diff --git a/src/global/accountSyncState.ts b/src/global/accountSyncState.ts new file mode 100644 index 00000000..acb0846e --- /dev/null +++ b/src/global/accountSyncState.ts @@ -0,0 +1,26 @@ +import { ref, watch } from "vue"; + +const INSTALLED_SYNC_STORAGE_KEY = "spark-store-installed-sync-enabled"; + +const readSyncEnabled = (): boolean | null => { + const savedValue = localStorage.getItem(INSTALLED_SYNC_STORAGE_KEY); + if (savedValue === "true") return true; + if (savedValue === "false") return false; + return null; +}; + +export const installedSyncEnabled = ref(readSyncEnabled()); + +export const setInstalledSyncEnabled = (enabled: boolean): void => { + installedSyncEnabled.value = enabled; + localStorage.setItem(INSTALLED_SYNC_STORAGE_KEY, String(enabled)); +}; + +watch(installedSyncEnabled, (enabled) => { + if (enabled === null) { + localStorage.removeItem(INSTALLED_SYNC_STORAGE_KEY); + return; + } + + localStorage.setItem(INSTALLED_SYNC_STORAGE_KEY, String(enabled)); +});