mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
feat:针对弱网环境,侧边栏添加并行加载,重试机制,初始化优化
This commit is contained in:
128
src/App.vue
128
src/App.vue
@@ -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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user