fix: 截图直接采用后端 img_urls,移除 HEAD 探测

- 实测后端 app.json 的 img_urls 为真实权威截图列表(微信 3 张即返回 3 个 URL,
  spk-json 与 erotica 两域名同路径 screen_1~3=200、screen_4~5=404)
- loadScreenshots 改为直接读取 app.img_urls(兼容 string[] 与字符串形式 JSON),
  由 ScreenPreview 的 screenshots.length 自动收敛预览数量与边界
- 彻底移除每次打开详情页的 5 个并发 HEAD 探测请求,消除服务器压力隐患
- 移除 checkScreenshotExists / screenshotAbortController / axiosInstance 依赖
- debian/changelog 测试版本号 5.2.1.4-test
- 验证:curl 实测 img_urls 真实存在;vue-tsc 0 / eslint 0 / 打包成功
This commit is contained in:
xiyidaiwa
2026-08-13 22:15:46 +08:00
parent d8bea4ac23
commit 8e1de74cab
2 changed files with 22 additions and 57 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
spark-store (5.2.1.3-test) UNRELEASED; urgency=medium spark-store (5.2.1.4-test) UNRELEASED; urgency=medium
* Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP> * Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP>
+17 -52
View File
@@ -34,7 +34,6 @@ import {
buildReviewTags, buildReviewTags,
} from "../modules/appIdentity"; } from "../modules/appIdentity";
import { loadFavoriteMetadataForDetail } from "./useFavorites"; import { loadFavoriteMetadataForDetail } from "./useFavorites";
import { axiosInstance } from "./useHttp";
import type { ReviewTags } from "../global/typedefinition"; import type { ReviewTags } from "../global/typedefinition";
import type { Ref } from "vue"; import type { Ref } from "vue";
@@ -55,9 +54,6 @@ const currentReviewTags = computed<ReviewTags | null>(() => {
}); });
}); });
// 截图探测请求的中断控制器:每次打开新详情前取消旧探测,避免结果覆盖当前应用。
let screenshotAbortController: AbortController | null = null;
// 从仓库获取应用详细信息的辅助函数 // 从仓库获取应用详细信息的辅助函数
const fetchAppFromStore = async ( const fetchAppFromStore = async (
pkgname: string, pkgname: string,
@@ -333,10 +329,8 @@ const openDetail = async (app: App | OpenDetailInput) => {
currentApp.value = finalApp; currentApp.value = finalApp;
currentScreenIndex.value = 0; currentScreenIndex.value = 0;
// 截图异步探测,详情页先弹出不阻塞;探测完成后截图区域自动刷新 // 截图直接采用后端 img_urls(已解析),同步赋值,详情页打开即显示正确数量
loadScreenshots(displayAppForScreenshots).catch(() => { loadScreenshots(displayAppForScreenshots);
// 异常(含取消)已在 loadScreenshots 内处理,此处仅避免未捕获 promise
});
showModal.value = true; showModal.value = true;
currentAppSparkInstalled.value = false; currentAppSparkInstalled.value = false;
@@ -393,60 +387,31 @@ const checkAppInstalled = (app: App) => {
} }
}; };
// 探测单张截图是否真实存在:用 HEAD 请求,超时 3s,仅当返回 2xx 时视为有效 // 截图来源直接采用后端 app.img_urls(权威真实列表,微信 3 张即返回 3 个 URL)
// 使用 axiosInstance 以便复用同源配置,但 HEAD PNG 不会触发 .json 缓存穿透拦截器。 // 无需前端再 HEAD 探测 screen_1~5.png —— 既消除每次打开详情的额外请求压力,
const checkScreenshotExists = async ( // 又保证"实际有几张就预览几张",边界由 ScreenPreview 的 length 自动收敛。
url: string, // 兼容 img_urls 可能为字符串形式 JSON(类型声明为 string[],运行时偶发字符串)。
signal?: AbortSignal, const loadScreenshots = (app: App) => {
): Promise<boolean> => { let urls: string[] = [];
const raw = app.img_urls as unknown;
if (Array.isArray(raw)) {
urls = raw as string[];
} else if (typeof raw === "string" && raw.length > 0) {
try { try {
await axiosInstance.head(url, { timeout: 3000, signal }); const parsed = JSON.parse(raw);
return true; if (Array.isArray(parsed)) urls = parsed as string[];
} catch { } catch {
return false; urls = [];
} }
};
const loadScreenshots = async (app: App) => {
// 取消上一应用的截图探测,避免结果覆盖到当前应用
if (screenshotAbortController) {
screenshotAbortController.abort();
} }
const controller = new AbortController(); screenshots.value = urls.filter(
screenshotAbortController = controller; (u): u is string => typeof u === "string" && u.length > 0,
screenshots.value = [];
if (!app.category || app.category === "unknown") return;
const arch = window.apm_store.arch || "amd64";
const finalArch = app.origin === "spark" ? `${arch}-store` : `${arch}-apm`;
const candidates: string[] = [];
for (let i = 1; i <= 5; i++) {
candidates.push(
`${APM_STORE_BASE_URL}/${finalArch}/${app.category}/${app.pkgname}/screen_${i}.png`,
); );
}
try {
// 并发探测 5 张候选截图,只保留真实存在的;失败视为无该图,不阻塞详情页打开。
const results = await Promise.all(
candidates.map((url) => checkScreenshotExists(url, controller.signal)),
);
const valid = candidates.filter((_, index) => results[index]);
screenshots.value = valid;
} catch {
// 当前轮次被新详情页取消或异常:不写入旧结果
}
}; };
const closeDetail = () => { const closeDetail = () => {
showModal.value = false; showModal.value = false;
currentApp.value = null; currentApp.value = null;
// 关闭详情时取消正在进行的截图探测,避免结果写入已关闭的旧应用
if (screenshotAbortController) {
screenshotAbortController.abort();
screenshotAbortController = null;
}
}; };
const openScreenPreview = (index: number) => { const openScreenPreview = (index: number) => {