mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 06:03:49 +08:00
fix(favorites): ignore stale account requests
This commit is contained in:
+53
-15
@@ -402,6 +402,7 @@ const showFavoriteSelector = ref(false);
|
|||||||
const favoriteTargetApp = ref<App | null>(null);
|
const favoriteTargetApp = ref<App | null>(null);
|
||||||
const favoriteLoading = ref(false);
|
const favoriteLoading = ref(false);
|
||||||
const favoriteError = ref("");
|
const favoriteError = ref("");
|
||||||
|
const favoriteRequestGeneration = ref(0);
|
||||||
|
|
||||||
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
||||||
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
||||||
@@ -1319,6 +1320,7 @@ const openLoginFromPrompt = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clearFavoriteState = () => {
|
const clearFavoriteState = () => {
|
||||||
|
favoriteRequestGeneration.value += 1;
|
||||||
favoriteFolders.value = [];
|
favoriteFolders.value = [];
|
||||||
activeFavoriteFolderId.value = null;
|
activeFavoriteFolderId.value = null;
|
||||||
favoriteItems.value = [];
|
favoriteItems.value = [];
|
||||||
@@ -1328,6 +1330,9 @@ const clearFavoriteState = () => {
|
|||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCurrentFavoriteRequest = (generation: number): boolean =>
|
||||||
|
favoriteRequestGeneration.value === generation && isLoggedIn.value;
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
clearFavoriteState();
|
clearFavoriteState();
|
||||||
@@ -1368,50 +1373,73 @@ const openUserManagement = () => {
|
|||||||
showLoginPrompt.value = false;
|
showLoginPrompt.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadFavoriteFolders = async (): Promise<void> => {
|
const loadFavoriteFolders = async (
|
||||||
favoriteFolders.value = await listFavoriteFolders();
|
generation = favoriteRequestGeneration.value,
|
||||||
const activeFolderExists = favoriteFolders.value.some(
|
): Promise<boolean> => {
|
||||||
|
const folders = await listFavoriteFolders();
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return false;
|
||||||
|
|
||||||
|
favoriteFolders.value = folders;
|
||||||
|
const activeFolderExists = folders.some(
|
||||||
(folder) => folder.id === activeFavoriteFolderId.value,
|
(folder) => folder.id === activeFavoriteFolderId.value,
|
||||||
);
|
);
|
||||||
if (!activeFolderExists) {
|
if (!activeFolderExists) {
|
||||||
activeFavoriteFolderId.value = favoriteFolders.value[0]?.id ?? null;
|
activeFavoriteFolderId.value = folders[0]?.id ?? null;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadActiveFavoriteItems = async (): Promise<void> => {
|
const loadActiveFavoriteItems = async (
|
||||||
|
generation = favoriteRequestGeneration.value,
|
||||||
|
): Promise<boolean> => {
|
||||||
if (!activeFavoriteFolderId.value) {
|
if (!activeFavoriteFolderId.value) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return false;
|
||||||
favoriteItems.value = [];
|
favoriteItems.value = [];
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
favoriteItems.value = await listFavoriteItems(activeFavoriteFolderId.value);
|
const items = await listFavoriteItems(activeFavoriteFolderId.value);
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return false;
|
||||||
|
|
||||||
|
favoriteItems.value = items;
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshFavorites = async (): Promise<void> => {
|
const refreshFavorites = async (): Promise<void> => {
|
||||||
|
const generation = favoriteRequestGeneration.value;
|
||||||
favoriteLoading.value = true;
|
favoriteLoading.value = true;
|
||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
try {
|
try {
|
||||||
await Promise.all([refreshFavoriteInstalledApps(), loadFavoriteFolders()]);
|
await Promise.all([
|
||||||
await loadActiveFavoriteItems();
|
refreshFavoriteInstalledApps(),
|
||||||
|
loadFavoriteFolders(generation),
|
||||||
|
]);
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
|
await loadActiveFavoriteItems(generation);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
favoriteError.value = (error as Error)?.message || "读取收藏夹失败";
|
favoriteError.value = (error as Error)?.message || "读取收藏夹失败";
|
||||||
} finally {
|
} finally {
|
||||||
favoriteLoading.value = false;
|
if (isCurrentFavoriteRequest(generation)) favoriteLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openFavoriteSelector = async (app: App) => {
|
const openFavoriteSelector = async (app: App) => {
|
||||||
if (!requireLogin("收藏应用需要登录星火账号。")) return;
|
if (!requireLogin("收藏应用需要登录星火账号。")) return;
|
||||||
|
const generation = favoriteRequestGeneration.value;
|
||||||
favoriteTargetApp.value = app;
|
favoriteTargetApp.value = app;
|
||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
try {
|
try {
|
||||||
await loadFavoriteFolders();
|
const foldersLoaded = await loadFavoriteFolders(generation);
|
||||||
|
if (!foldersLoaded || !isCurrentFavoriteRequest(generation)) return;
|
||||||
showFavoriteSelector.value = true;
|
showFavoriteSelector.value = true;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
favoriteError.value = (error as Error)?.message || "读取收藏夹失败";
|
favoriteError.value = (error as Error)?.message || "读取收藏夹失败";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addCurrentFavoriteToFolder = async (folderId: number | "default") => {
|
const addCurrentFavoriteToFolder = async (folderId: number | "default") => {
|
||||||
|
const generation = favoriteRequestGeneration.value;
|
||||||
const app = favoriteTargetApp.value;
|
const app = favoriteTargetApp.value;
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
try {
|
try {
|
||||||
@@ -1422,10 +1450,12 @@ const addCurrentFavoriteToFolder = async (folderId: number | "default") => {
|
|||||||
category: app.category,
|
category: app.category,
|
||||||
iconUrl: app.icons,
|
iconUrl: app.icons,
|
||||||
});
|
});
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
showFavoriteSelector.value = false;
|
showFavoriteSelector.value = false;
|
||||||
favoriteTargetApp.value = null;
|
favoriteTargetApp.value = null;
|
||||||
await refreshFavorites();
|
await refreshFavorites();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
favoriteError.value = (error as Error)?.message || "添加收藏失败";
|
favoriteError.value = (error as Error)?.message || "添加收藏失败";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1440,15 +1470,17 @@ const openFavoriteManagement = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectFavoriteFolder = async (folderId: number) => {
|
const selectFavoriteFolder = async (folderId: number) => {
|
||||||
|
const generation = favoriteRequestGeneration.value;
|
||||||
activeFavoriteFolderId.value = folderId;
|
activeFavoriteFolderId.value = folderId;
|
||||||
favoriteLoading.value = true;
|
favoriteLoading.value = true;
|
||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
try {
|
try {
|
||||||
await loadActiveFavoriteItems();
|
await loadActiveFavoriteItems(generation);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
favoriteError.value = (error as Error)?.message || "读取收藏应用失败";
|
favoriteError.value = (error as Error)?.message || "读取收藏应用失败";
|
||||||
} finally {
|
} finally {
|
||||||
favoriteLoading.value = false;
|
if (isCurrentFavoriteRequest(generation)) favoriteLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1456,30 +1488,36 @@ const createFavoriteFolderFromPrompt = async () => {
|
|||||||
const name = window.prompt("请输入收藏夹名称");
|
const name = window.prompt("请输入收藏夹名称");
|
||||||
const folderName = name?.trim();
|
const folderName = name?.trim();
|
||||||
if (!folderName) return;
|
if (!folderName) return;
|
||||||
|
const generation = favoriteRequestGeneration.value;
|
||||||
favoriteLoading.value = true;
|
favoriteLoading.value = true;
|
||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
try {
|
try {
|
||||||
const folder = await createFavoriteFolder(folderName);
|
const folder = await createFavoriteFolder(folderName);
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
activeFavoriteFolderId.value = folder.id;
|
activeFavoriteFolderId.value = folder.id;
|
||||||
await refreshFavorites();
|
await refreshFavorites();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
favoriteError.value = (error as Error)?.message || "创建收藏夹失败";
|
favoriteError.value = (error as Error)?.message || "创建收藏夹失败";
|
||||||
} finally {
|
} finally {
|
||||||
favoriteLoading.value = false;
|
if (isCurrentFavoriteRequest(generation)) favoriteLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeSelectedFavorites = async (ids: number[]) => {
|
const removeSelectedFavorites = async (ids: number[]) => {
|
||||||
if (!activeFavoriteFolderId.value || ids.length === 0) return;
|
if (!activeFavoriteFolderId.value || ids.length === 0) return;
|
||||||
|
const generation = favoriteRequestGeneration.value;
|
||||||
favoriteLoading.value = true;
|
favoriteLoading.value = true;
|
||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
try {
|
try {
|
||||||
await bulkDeleteFavoriteItems(activeFavoriteFolderId.value, ids);
|
await bulkDeleteFavoriteItems(activeFavoriteFolderId.value, ids);
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
await refreshFavorites();
|
await refreshFavorites();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (!isCurrentFavoriteRequest(generation)) return;
|
||||||
favoriteError.value = (error as Error)?.message || "移除收藏失败";
|
favoriteError.value = (error as Error)?.message || "移除收藏失败";
|
||||||
} finally {
|
} finally {
|
||||||
favoriteLoading.value = false;
|
if (isCurrentFavoriteRequest(generation)) favoriteLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/vue";
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import App from "@/App.vue";
|
import App from "@/App.vue";
|
||||||
|
import { listFavoriteFolders } from "@/modules/backendApi";
|
||||||
import { setAuthSession } from "@/global/authState";
|
import { setAuthSession } from "@/global/authState";
|
||||||
import type { FavoriteFolder, FavoriteItem } from "@/global/typedefinition";
|
import type { FavoriteFolder, FavoriteItem } from "@/global/typedefinition";
|
||||||
|
|
||||||
@@ -29,6 +30,15 @@ const favoriteItems: FavoriteItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const createDeferred = <T>() => {
|
||||||
|
let resolve!: (value: T) => void;
|
||||||
|
const promise = new Promise<T>((promiseResolve) => {
|
||||||
|
resolve = promiseResolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { promise, resolve };
|
||||||
|
};
|
||||||
|
|
||||||
vi.mock("axios", () => {
|
vi.mock("axios", () => {
|
||||||
const get = vi.fn(async (url: string) => {
|
const get = vi.fn(async (url: string) => {
|
||||||
if (url.includes("categories.json")) {
|
if (url.includes("categories.json")) {
|
||||||
@@ -146,6 +156,13 @@ describe("App account placeholders", () => {
|
|||||||
removeEventListener: vi.fn(),
|
removeEventListener: vi.fn(),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
vi.stubGlobal("scrollTo", vi.fn());
|
||||||
|
class MockIntersectionObserver {
|
||||||
|
observe = vi.fn();
|
||||||
|
disconnect = vi.fn();
|
||||||
|
unobserve = vi.fn();
|
||||||
|
}
|
||||||
|
vi.stubGlobal("IntersectionObserver", MockIntersectionObserver);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the user management placeholder from the logged-in quick menu", async () => {
|
it("shows the user management placeholder from the logged-in quick menu", async () => {
|
||||||
@@ -291,4 +308,47 @@ describe("App account placeholders", () => {
|
|||||||
expect(screen.queryByText("wps · office")).toBeNull();
|
expect(screen.queryByText("wps · office")).toBeNull();
|
||||||
expect(screen.queryByRole("heading", { name: "我的收藏" })).toBeNull();
|
expect(screen.queryByRole("heading", { name: "我的收藏" })).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not reopen the favorite selector when folder loading resolves after logout", async () => {
|
||||||
|
const slowFolders = createDeferred<FavoriteFolder[]>();
|
||||||
|
vi.mocked(listFavoriteFolders).mockReturnValueOnce(slowFolders.promise);
|
||||||
|
render(App);
|
||||||
|
|
||||||
|
await fireEvent.click(await screen.findByText("全部应用"));
|
||||||
|
await fireEvent.click(await screen.findByText("wps · 1.0.0"));
|
||||||
|
expect(await screen.findByRole("heading", { name: "WPS" })).toBeTruthy();
|
||||||
|
await fireEvent.click(screen.getByRole("button", { name: "收藏" }));
|
||||||
|
|
||||||
|
await fireEvent.click(
|
||||||
|
await screen.findByRole("button", { name: /^Momen$/ }),
|
||||||
|
);
|
||||||
|
if (!screen.queryByText("退出登录")) {
|
||||||
|
await fireEvent.click(
|
||||||
|
await screen.findByRole("button", { name: /^Momen$/ }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await fireEvent.click(screen.getByText("退出登录"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "登录 / 注册" })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
slowFolders.resolve([
|
||||||
|
{
|
||||||
|
id: 42,
|
||||||
|
name: "旧账号收藏夹",
|
||||||
|
itemCount: 1,
|
||||||
|
createdAt: "2026-05-18T00:00:00Z",
|
||||||
|
updatedAt: "2026-05-18T00:00:00Z",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await slowFolders.promise;
|
||||||
|
await Promise.resolve();
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(screen.queryByText("旧账号收藏夹 (1)")).toBeNull();
|
||||||
|
expect(screen.queryByText("wps · office")).toBeNull();
|
||||||
|
expect(screen.queryByText("旧账号收藏夹")).toBeNull();
|
||||||
|
expect(screen.queryByRole("dialog", { name: "选择收藏夹" })).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user