|
@@ -1,4 +1,5 @@
|
|
|
import { apiPost } from "@/lib/api";
|
|
import { apiPost } from "@/lib/api";
|
|
|
|
|
+import { getApiBaseUrl } from "@/lib/env";
|
|
|
|
|
|
|
|
export type GoodsType = 1 | 2 | 3 | 4 | 5;
|
|
export type GoodsType = 1 | 2 | 3 | 4 | 5;
|
|
|
export type CustomType = 0 | 1 | 2;
|
|
export type CustomType = 0 | 1 | 2;
|
|
@@ -373,7 +374,7 @@ function normalizeVideo(item: GoodsVideoListItem): CourseVideo | null {
|
|
|
title: title || videoName || "未命名视频",
|
|
title: title || videoName || "未命名视频",
|
|
|
introduction,
|
|
introduction,
|
|
|
frontUrl,
|
|
frontUrl,
|
|
|
- playUrl: linkUrl ?? fileUrl ?? playUrl,
|
|
|
|
|
|
|
+ playUrl: toAbsoluteFileUrl(linkUrl ?? fileUrl ?? playUrl ?? ""),
|
|
|
payType,
|
|
payType,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -451,15 +452,52 @@ function parseDownloadUrls(rawDownload: unknown): string[] {
|
|
|
.filter(Boolean);
|
|
.filter(Boolean);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function normalizeFile(item: GoodsSearchListItem): CourseFile[] {
|
|
|
|
|
- const id = String(item.goodsId ?? item.id ?? "").trim();
|
|
|
|
|
|
|
+function toAbsoluteFileUrl(input: string): string {
|
|
|
|
|
+ const value = input.trim();
|
|
|
|
|
+ if (!value) return "";
|
|
|
|
|
+ const apiBaseUrl = getApiBaseUrl().replace(/\/$/, "");
|
|
|
|
|
+ const preferredOrigin = (() => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (/^https?:\/\//i.test(apiBaseUrl)) return new URL(apiBaseUrl).origin;
|
|
|
|
|
+ return "";
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ if (/^https?:\/\//i.test(value)) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const current = new URL(value);
|
|
|
|
|
+ const isLocalHost = current.hostname === "localhost" || current.hostname === "127.0.0.1";
|
|
|
|
|
+ if (isLocalHost && preferredOrigin) {
|
|
|
|
|
+ return `${preferredOrigin}${current.pathname}${current.search}${current.hash}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ return value;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.startsWith("//")) {
|
|
|
|
|
+ if (typeof window !== "undefined" && window.location?.protocol) {
|
|
|
|
|
+ return `${window.location.protocol}${value}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ return `https:${value}`;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const path = value.startsWith("/") ? value : `/${value}`;
|
|
|
|
|
+ // 优先走同源反向代理,避免本地/线上域名不一致导致资源不可达。
|
|
|
|
|
+ return `/api-backend${path}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function normalizeFileFromVideo(item: GoodsVideoListItem): CourseFile[] {
|
|
|
|
|
+ const id = String(item.videoId ?? item.id ?? "").trim();
|
|
|
if (!id) return [];
|
|
if (!id) return [];
|
|
|
- const title = String(item.title ?? item.goodsName ?? item.name ?? "").trim();
|
|
|
|
|
|
|
+ const title = String(item.title ?? item.videoName ?? item.videoTitle ?? item.name ?? "").trim();
|
|
|
if (!title) return [];
|
|
if (!title) return [];
|
|
|
- const downloadUrls = parseDownloadUrls(item.download);
|
|
|
|
|
|
|
+ const downloadUrls = parseDownloadUrls(item.fileUrl);
|
|
|
if (downloadUrls.length === 0) return [];
|
|
if (downloadUrls.length === 0) return [];
|
|
|
const introduction = String(
|
|
const introduction = String(
|
|
|
- item.introduction ?? item.subtitle ?? item.subTitle ?? item.intro ?? "",
|
|
|
|
|
|
|
+ item.introduction ?? item.description ?? item.intro ?? "",
|
|
|
).trim();
|
|
).trim();
|
|
|
const frontUrl =
|
|
const frontUrl =
|
|
|
typeof item.frontUrl === "string" && item.frontUrl.trim()
|
|
typeof item.frontUrl === "string" && item.frontUrl.trim()
|
|
@@ -470,7 +508,7 @@ function normalizeFile(item: GoodsSearchListItem): CourseFile[] {
|
|
|
title,
|
|
title,
|
|
|
introduction,
|
|
introduction,
|
|
|
frontUrl,
|
|
frontUrl,
|
|
|
- downloadUrl,
|
|
|
|
|
|
|
+ downloadUrl: toAbsoluteFileUrl(downloadUrl),
|
|
|
}));
|
|
}));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -543,26 +581,25 @@ export async function fetchCourseFiles(
|
|
|
const normalizedGoodsId = goodsId.trim();
|
|
const normalizedGoodsId = goodsId.trim();
|
|
|
const current = Number.isFinite(page.current) ? Math.max(1, page.current) : 1;
|
|
const current = Number.isFinite(page.current) ? Math.max(1, page.current) : 1;
|
|
|
const row = Number.isFinite(page.row) ? Math.max(1, page.row) : 10;
|
|
const row = Number.isFinite(page.row) ? Math.max(1, page.row) : 10;
|
|
|
|
|
+ if (!normalizedGoodsId) {
|
|
|
|
|
+ return { list: [], page: { current, row, total: 0 } };
|
|
|
|
|
+ }
|
|
|
try {
|
|
try {
|
|
|
const payload = await apiPost<
|
|
const payload = await apiPost<
|
|
|
- GoodsSearchListResponse,
|
|
|
|
|
- { goodsType: GoodsType; page: { current: number; row: number } }
|
|
|
|
|
- >("/goods/search/list", {
|
|
|
|
|
- goodsType: 5,
|
|
|
|
|
|
|
+ GoodsVideoListResponse,
|
|
|
|
|
+ { goodsId: string; page: { current: number; row: number } }
|
|
|
|
|
+ >("/goods/video/search/list", {
|
|
|
|
|
+ goodsId: normalizedGoodsId,
|
|
|
page: { current, row },
|
|
page: { current, row },
|
|
|
});
|
|
});
|
|
|
- const list = extractList(payload).flatMap(normalizeFile);
|
|
|
|
|
- const matched = normalizedGoodsId
|
|
|
|
|
- ? list.filter((f) => f.id.startsWith(`${normalizedGoodsId}-`))
|
|
|
|
|
- : [];
|
|
|
|
|
- const finalList = matched.length > 0 ? matched : list;
|
|
|
|
|
- const serverPage = extractSearchPage(payload);
|
|
|
|
|
|
|
+ const finalList = extractVideoList(payload).flatMap(normalizeFileFromVideo);
|
|
|
|
|
+ const serverPage = extractVideoPage(payload);
|
|
|
return {
|
|
return {
|
|
|
list: finalList,
|
|
list: finalList,
|
|
|
page: {
|
|
page: {
|
|
|
current: serverPage.current || current,
|
|
current: serverPage.current || current,
|
|
|
row: serverPage.row || row,
|
|
row: serverPage.row || row,
|
|
|
- total: matched.length > 0 ? matched.length : serverPage.total || finalList.length,
|
|
|
|
|
|
|
+ total: serverPage.total || finalList.length,
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
} catch {
|
|
} catch {
|