feat:针对弱网环境,侧边栏添加并行加载,重试机制,初始化优化

This commit is contained in:
2026-02-27 23:18:06 +08:00
parent 6ea628d869
commit 88670be15e

View File

@@ -179,6 +179,24 @@ const axiosInstance = axios.create({
baseURL: APM_STORE_BASE_URL, baseURL: APM_STORE_BASE_URL,
timeout: 5000, // 增加到 5 秒,避免网络波动导致的超时 timeout: 5000, // 增加到 5 秒,避免网络波动导致的超时
}); });
const fetchWithRetry = async <T>(
url: string,
retries = 3,
delay = 1000,
): Promise<T> => {
try {
const response = await axiosInstance.get<T>(url);
return response.data;
} catch (error) {
if (retries > 0) {
await new Promise((resolve) => setTimeout(resolve, delay));
return fetchWithRetry(url, retries - 1, delay * 2);
}
throw error;
}
};
const cacheBuster = (url: string) => `${url}?cb=${Date.now()}`; const cacheBuster = (url: string) => `${url}?cb=${Date.now()}`;
// 响应式状态 // 响应式状态
@@ -739,56 +757,62 @@ const loadCategories = async () => {
const loadApps = async (onFirstBatch?: () => void) => { const loadApps = async (onFirstBatch?: () => void) => {
try { try {
logger.info("开始加载应用数据(并发分批..."); logger.info("开始加载应用数据(并发带重试...");
const categoriesList = Object.keys(categories.value || {}); const categoriesList = Object.keys(categories.value || {});
const concurrency = 4; // 同时并发请求数量,可根据网络条件调整 let firstBatchCallDone = false;
for (let i = 0; i < categoriesList.length; i += concurrency) { // 并发加载所有分类,每个分类自带重试机制
const batch = categoriesList.slice(i, i + concurrency); await Promise.all(
await Promise.all( categoriesList.map(async (category) => {
batch.map(async (category) => { try {
try { logger.info(`加载分类: ${category}`);
logger.info(`加载分类: ${category}`); const categoryApps = await fetchWithRetry<AppJson[]>(
const response = await axiosInstance.get<AppJson[]>( cacheBuster(`/${window.apm_store.arch}/${category}/applist.json`),
cacheBuster(`/${window.apm_store.arch}/${category}/applist.json`), );
);
const categoryApps = response.status === 200 ? response.data : []; const normalizedApps = (categoryApps || []).map((appJson) => ({
categoryApps.forEach((appJson) => { name: appJson.Name,
const normalizedApp: App = { pkgname: appJson.Pkgname,
name: appJson.Name, version: appJson.Version,
pkgname: appJson.Pkgname, filename: appJson.Filename,
version: appJson.Version, torrent_address: appJson.Torrent_address,
filename: appJson.Filename, author: appJson.Author,
torrent_address: appJson.Torrent_address, contributor: appJson.Contributor,
author: appJson.Author, website: appJson.Website,
contributor: appJson.Contributor, update: appJson.Update,
website: appJson.Website, size: appJson.Size,
update: appJson.Update, more: appJson.More,
size: appJson.Size, tags: appJson.Tags,
more: appJson.More, img_urls:
tags: appJson.Tags, typeof appJson.img_urls === "string"
img_urls: ? JSON.parse(appJson.img_urls)
typeof appJson.img_urls === "string" : appJson.img_urls,
? JSON.parse(appJson.img_urls) icons: appJson.icons,
: appJson.img_urls, category: category,
icons: appJson.icons, currentStatus: "not-installed" as const,
category: category, }));
currentStatus: "not-installed",
}; // 增量式更新,让用户尽快看到部分数据
apps.value.push(normalizedApp); apps.value.push(...normalizedApps);
});
} catch (error) { // 只要有一个分类加载成功,就可以考虑关闭整体 loading如果是首批逻辑
logger.warn(`加载分类 ${category} 失败: ${error}`); if (!firstBatchCallDone && typeof onFirstBatch === "function") {
firstBatchCallDone = true;
onFirstBatch();
} }
}), } catch (error) {
); logger.warn(`加载分类 ${category} 最终失败: ${error}`);
}
}),
);
// 首批完成回调(用于隐藏首屏 loading // 确保即使全部失败也结束 loading
if (i === 0 && typeof onFirstBatch === "function") onFirstBatch(); if (!firstBatchCallDone && typeof onFirstBatch === "function") {
onFirstBatch();
} }
} catch (error) { } catch (error) {
logger.error(`加载应用数据失败: ${error}`); logger.error(`加载应用数据流程异常: ${error}`);
} }
}; };
@@ -805,14 +829,18 @@ onMounted(async () => {
initTheme(); initTheme();
await loadCategories(); await loadCategories();
// 默认加载主页数据
await loadHome(); // 分类目录加载后,并行加载主页数据和所有应用列表
// 先显示 loading并异步开始分批加载应用列表。
loading.value = true; loading.value = true;
loadApps(() => { await Promise.all([
// 当第一批分类加载完成后,隐藏首屏 loading loadHome(),
loading.value = false; new Promise<void>((resolve) => {
}); loadApps(() => {
loading.value = false;
resolve();
});
}),
]);
// 设置键盘导航 // 设置键盘导航
document.addEventListener("keydown", (e) => { document.addEventListener("keydown", (e) => {