fix: 标签策略启动初始化加固 + 已安装应用过滤/统计性能优化

- 标签优先显示策略 initTagPriorityStrategy 提到 App.vue onMounted 统一调用,
  消除组件挂载顺序导致先读内存默认 auto 的竞态窗口(AppDetailModal 移除重复调用)
- InstalledAppsModal 过滤逻辑合并为单次遍历(保留 hasOrigin 双来源语义)
- 统计 apm/spark/total 改为基于 searchFilteredApps 单次遍历
- canOpenDetail 补充业务含义注释与可读性变量
- 将 App.vue 重构的 9 个 composable 纳入版本管理
- 验证:vue-tsc 0 / eslint 0 / dpkg-buildpackage 打包 spark-store_5.2.1.24-test 成功
This commit is contained in:
xiyidaiwa
2026-08-12 23:16:17 +08:00
parent 5e7d88ecc5
commit d7bdcf3680
13 changed files with 3367 additions and 2672 deletions
+167
View File
@@ -0,0 +1,167 @@
/**
* useAppState —— 跨 composable 共享的全局原始响应式状态(单例)。
*
* 设计说明(避免循环依赖与重复实例化):
* App.vue 原文件中的逻辑被拆分到多个 composableuseCatalog / useRanking /
* useAppDetail / useInstalledApps / useFavorites / useAccountSync / useDownloads)。
* 这些 composable 之间存在大量共享状态(apps / currentApp / installedApps /
* favorite* / 各类弹窗开关等)。若各自持有会出现多实例与状态不同步,并引发循环依赖。
*
* 故把"贯穿整个应用生命周期的全局原始状态"集中在本模块以单例形式导出,
* 所有 composable 与 App.vue 均从本模块取用同一份 ref 实例。
* 这与现有 global/authState.ts、global/storeConfig.ts 的单例风格一致。
*
* 本模块**只持有状态与极少量纯派生 computed**,不含任何业务逻辑/副作用,
* 确保拆分前后运行时行为完全一致(7 维度审计:功能/状态一致)。
*
* 注意:currentApp / currentAppSparkInstalled / currentAppApmInstalled 同时在
* storeConfig 中导出(供其它模块直接引用)。本模块从 storeConfig 再导出以保持单一实例。
*/
import { ref, computed } from "vue";
import type { Ref } from "vue";
import type {
App,
AppJson,
CategoryInfo,
SidebarEntry,
HomeLink,
FavoriteFolder,
FavoriteItem,
InstalledAppInfo,
ResolvedFavoriteItem,
SystemInfo,
DownloadedAppRecord,
SyncedAppListItem,
AppReview,
ReviewUserProfile,
DownloadItem,
} from "../global/typedefinition";
// ===== 目录数据 =====
export const apps: Ref<App[]> = ref([]);
export const categories: Ref<Record<string, CategoryInfo>> = ref({});
export const tabCategories: Ref<Record<string, Record<string, CategoryInfo>>> =
ref({});
export const tabApps: Ref<Record<string, App[]>> = ref({});
export const loadingTabs = ref<Set<string>>(new Set());
export const homeListUrls = ref<
Record<string, { spark?: string; apm?: string }>
>({});
export const sidebarEntries: Ref<SidebarEntry[]> = ref([]);
export const sparkAvailable = ref(false);
export const apmAvailable = ref(false);
export const storeFilter = ref<"spark" | "apm" | "both">("both");
export const initialCatalogLoaded = ref(false);
export const loading = ref(true);
// ===== 首页 / 排行 =====
export const homeLinks = ref<HomeLink[]>([]);
export const homeLoading = ref(false);
export const homeError = ref("");
export const apmRanking = ref<App[]>([]);
export const sparkRanking = ref<App[]>([]);
export const rankingLoading = ref(false);
// ===== 导航 =====
export type MainView = "default" | "favorites";
export const currentView = ref<MainView>("default");
export const activeTab = ref("home");
export const selectedCategory = ref("all");
export const searchQuery = ref("");
export const isSidebarOpen = ref(false);
// ===== 详情弹窗 =====
export {
currentApp,
currentAppSparkInstalled,
currentAppApmInstalled,
} from "../global/storeConfig";
export const showModal = ref(false);
export const showPreview = ref(false);
export const currentScreenIndex = ref(0);
export const screenshots = ref<string[]>([]);
// ===== 已安装应用 =====
export const showInstalledModal = ref(false);
export const installedApps = ref<App[]>([]);
export const installedLoading = ref(false);
export const installedError = ref("");
export const installedWarning = ref("");
export const installedRefreshGeneration = ref(0);
export const installedSyncPromptShown = ref(false);
// ===== 收藏夹 =====
export const favoriteFolders = ref<FavoriteFolder[]>([]);
export const activeFavoriteFolderId = ref<number | null>(null);
export const favoriteItems = ref<FavoriteItem[]>([]);
export const favoriteItemsByFolder = ref<Record<number, FavoriteItem[]>>({});
export const showFavoriteSelector = ref(false);
export const favoriteTargetApp = ref<App | null>(null);
export const favoriteSelectorDraftFolderIds = ref<Array<
number | "default"
> | null>(null);
export const favoriteLoading = ref(false);
export const favoriteError = ref("");
export const favoriteRequestGeneration = ref(0);
// ===== 账号 / 下载历史 / 云端同步 / 恢复 =====
export const showUserManagementModal = ref(false);
export const downloadedApps = ref<DownloadedAppRecord[]>([]);
export const downloadedLoading = ref(false);
export const downloadedError = ref("");
export const downloadedRequestGeneration = ref(0);
export const syncLoading = ref(false);
export const syncStatusMessage = ref("");
export const syncRequestGeneration = ref(0);
export const syncCandidateApps = ref<App[]>([]);
export const restoreLoading = ref(false);
export const restoreError = ref("");
export const showRestoreModal = ref(false);
export const restoreItems = ref<SyncedAppListItem[]>([]);
export const restoreRequestGeneration = ref(0);
export const showLoginModal = ref(false);
export const loginLoading = ref(false);
export const loginError = ref("");
export const showLoginPrompt = ref(false);
export const loginPromptMessage = ref("请登录星火账号后继续操作。");
export const showLoginPromptMessage = loginPromptMessage;
export const showReviewUserProfileModal = ref(false);
export const selectedReviewUserProfile = ref<ReviewUserProfile | null>(null);
export const systemInfo = ref<SystemInfo>({ distro: "unknown" });
// ===== 下载队列 =====
export { downloads } from "../global/downloadStatus";
export const showDownloadDetailModal = ref(false);
export const currentDownload: Ref<DownloadItem | null> = ref(null);
// ===== 其它弹窗 / 卸载 / 更新中心编排 =====
export const showUninstallModal = ref(false);
export const uninstallTargetApp: Ref<App | null> = ref(null);
export const showAboutModal = ref(false);
export const showSettingsModal = ref(false);
export const showApmInstallDialog = ref(false);
// 可用来源 computed(供 storeFilter 相关判断复用)
export const availableSources = computed(() => ({
spark: sparkAvailable.value,
apm: apmAvailable.value,
}));
// 运算符别名(仅导出类型引用,避免重复定义)
export type {
App,
AppJson,
CategoryInfo,
SidebarEntry,
HomeLink,
FavoriteFolder,
FavoriteItem,
InstalledAppInfo,
ResolvedFavoriteItem,
SystemInfo,
DownloadedAppRecord,
SyncedAppListItem,
AppReview,
ReviewUserProfile,
DownloadItem,
};