|
|
@@ -1,5 +1,5 @@
|
|
|
import { apiPost } from "@/lib/api";
|
|
|
-import { getApiBaseUrl } from "@/lib/env";
|
|
|
+import { normalizeAssetUrl, toAbsoluteFileUrl } from "@/lib/media-url";
|
|
|
|
|
|
export type GoodsType = 1 | 2 | 3 | 4 | 5;
|
|
|
export type CustomType = 0 | 1 | 2;
|
|
|
@@ -296,10 +296,11 @@ function normalizeCourse(item: GoodsSearchListItem): Course | null {
|
|
|
goodsPriceParsed !== undefined && Number.isFinite(goodsPriceParsed)
|
|
|
? goodsPriceParsed
|
|
|
: undefined;
|
|
|
- const downloadUrl =
|
|
|
+ const rawDownload =
|
|
|
typeof item.download === "string" && item.download.trim()
|
|
|
? item.download.trim()
|
|
|
: undefined;
|
|
|
+ const downloadUrl = normalizeAssetUrl(rawDownload);
|
|
|
|
|
|
return {
|
|
|
id,
|
|
|
@@ -313,7 +314,9 @@ function normalizeCourse(item: GoodsSearchListItem): Course | null {
|
|
|
goodsPrice,
|
|
|
price: Number.isFinite(price) ? price : 0,
|
|
|
currency,
|
|
|
- coverUrl: typeof item.frontUrl === "string" ? item.frontUrl : undefined,
|
|
|
+ coverUrl: normalizeAssetUrl(
|
|
|
+ typeof item.frontUrl === "string" ? item.frontUrl : undefined,
|
|
|
+ ),
|
|
|
downloadUrl,
|
|
|
coverGradient: item.coverGradient ?? "from-slate-700 via-slate-800 to-slate-900",
|
|
|
};
|
|
|
@@ -370,7 +373,7 @@ function normalizeVideo(item: GoodsVideoListItem): CourseVideo | null {
|
|
|
typeof item.fileUrl === "string" && item.fileUrl.trim()
|
|
|
? item.fileUrl.trim()
|
|
|
: undefined;
|
|
|
- const playUrl =
|
|
|
+ const playUrlRaw =
|
|
|
typeof item.playUrl === "string" && item.playUrl.trim()
|
|
|
? item.playUrl.trim()
|
|
|
: undefined;
|
|
|
@@ -378,13 +381,17 @@ function normalizeVideo(item: GoodsVideoListItem): CourseVideo | null {
|
|
|
|
|
|
if (!videoName && !title && !introduction) return null;
|
|
|
|
|
|
+ const resolvedPlay = normalizeAssetUrl(
|
|
|
+ toAbsoluteFileUrl(linkUrl ?? fileUrl ?? playUrlRaw ?? ""),
|
|
|
+ );
|
|
|
+
|
|
|
return {
|
|
|
id,
|
|
|
videoName: videoName || title || "未命名视频",
|
|
|
title: title || videoName || "未命名视频",
|
|
|
introduction,
|
|
|
- frontUrl,
|
|
|
- playUrl: toAbsoluteFileUrl(linkUrl ?? fileUrl ?? playUrl ?? ""),
|
|
|
+ frontUrl: normalizeAssetUrl(frontUrl),
|
|
|
+ playUrl: resolvedPlay ?? "",
|
|
|
payType,
|
|
|
};
|
|
|
}
|
|
|
@@ -462,56 +469,23 @@ function parseDownloadUrls(rawDownload: unknown): string[] {
|
|
|
.filter(Boolean);
|
|
|
}
|
|
|
|
|
|
-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 downloadUrls = parseDownloadUrls(item.fileUrl);
|
|
|
- const normalizedUrls = downloadUrls.map(toAbsoluteFileUrl).filter(Boolean);
|
|
|
+ const normalizedUrls = downloadUrls
|
|
|
+ .map((u) => toAbsoluteFileUrl(u))
|
|
|
+ .filter(Boolean)
|
|
|
+ .map((u) => normalizeAssetUrl(u) ?? u);
|
|
|
const title = String(item.title ?? item.videoName ?? item.videoTitle ?? item.name ?? "").trim() || "未命名文件";
|
|
|
const rawId = String(item.videoId ?? item.id ?? "").trim();
|
|
|
const idBase = rawId || normalizedUrls[0] || title;
|
|
|
const introduction = String(
|
|
|
item.introduction ?? item.description ?? item.intro ?? "",
|
|
|
).trim();
|
|
|
- const frontUrl =
|
|
|
+ const frontUrl = normalizeAssetUrl(
|
|
|
typeof item.frontUrl === "string" && item.frontUrl.trim()
|
|
|
? item.frontUrl.trim()
|
|
|
- : undefined;
|
|
|
+ : undefined,
|
|
|
+ );
|
|
|
const payType: 0 | 1 = item.payType === 1 ? 1 : 0;
|
|
|
const finalUrls = normalizedUrls.length > 0 ? normalizedUrls : [""];
|
|
|
return finalUrls.map((downloadUrl, index) => ({
|