Quellcode durchsuchen

修改页面显示

ChenYu vor 7 Monaten
Ursprung
Commit
e83bfcb728
2 geänderte Dateien mit 138 neuen und 39 gelöschten Zeilen
  1. 1 1
      .env.development
  2. 137 38
      src/pages/ai-order-v2/AiOrderV2Page.tsx

+ 1 - 1
.env.development

@@ -1,2 +1,2 @@
 # 开发环境
-VITE_API_BASE_URL=/prod-api
+VITE_API_BASE_URL=http://localhost:9901

+ 137 - 38
src/pages/ai-order-v2/AiOrderV2Page.tsx

@@ -25,6 +25,7 @@ import {
   CopyOutlined,
   EyeOutlined,
   PictureOutlined,
+  PlayCircleOutlined,
   MobileOutlined,
 } from "@ant-design/icons";
 import type { AiOrderV2Item, AiOrderV2Params } from "@/api/aiOrderV2";
@@ -78,6 +79,16 @@ function normalizeResultText(val?: string) {
   }
 }
 
+function isVideoUrl(url?: string): boolean {
+  if (!url) return false;
+  try {
+    const pathname = new URL(url).pathname;
+    return /\.(mp4|webm|mov|avi|mkv|flv|m4v|3gp)$/i.test(pathname);
+  } catch {
+    return /\.(mp4|webm|mov|avi|mkv|flv|m4v|3gp)([?#]|$)/i.test(url);
+  }
+}
+
 function normalizeUrl(raw: string) {
   let val = raw.trim();
   val = val.replace(/\\+/g, "");
@@ -116,6 +127,33 @@ function parseResultImages(result?: string): string[] {
   }
 }
 
+interface VideoItem {
+  url: string;
+  thumbnailUrl?: string;
+}
+
+function parseResultVideos(result?: string): VideoItem[] {
+  if (!result) return [];
+  try {
+    const payload = JSON.parse(result) as Record<string, unknown>;
+    if (!payload || typeof payload !== "object") return [];
+    let videosRaw = payload.videos;
+    if (typeof videosRaw === "string") {
+      videosRaw = JSON.parse(videosRaw) as unknown;
+    }
+    if (!Array.isArray(videosRaw)) return [];
+    return videosRaw
+      .filter((v): v is Record<string, unknown> => v && typeof v === "object" && typeof v.url === "string")
+      .map((v) => ({
+        url: normalizeUrl(v.url as string),
+        thumbnailUrl: typeof v.thumbnailUrl === "string" ? normalizeUrl(v.thumbnailUrl) : undefined,
+      }))
+      .filter((v) => /^https?:\/\//i.test(v.url));
+  } catch {
+    return [];
+  }
+}
+
 function CopyableId({
   text,
   label,
@@ -278,13 +316,30 @@ export function AiOrderV2Page() {
       width: 260,
       render: (_, r) => (
         <div className="flex items-center gap-2 min-w-0">
-          <Avatar
-            shape="square"
-            size={40}
-            src={r.styleCoverImg}
-            icon={<PictureOutlined />}
-            style={{ flexShrink: 0 }}
-          />
+          {isVideoUrl(r.styleCoverImg) ? (
+            <div
+              className="relative shrink-0 overflow-hidden rounded bg-black"
+              style={{ width: 40, height: 40 }}
+            >
+              <video
+                src={r.styleCoverImg}
+                muted
+                preload="metadata"
+                className="h-full w-full object-cover"
+              />
+              <div className="absolute inset-0 flex items-center justify-center bg-black/30">
+                <PlayCircleOutlined className="text-white" style={{ fontSize: 16 }} />
+              </div>
+            </div>
+          ) : (
+            <Avatar
+              shape="square"
+              size={40}
+              src={r.styleCoverImg}
+              icon={<PictureOutlined />}
+              style={{ flexShrink: 0 }}
+            />
+          )}
           <div className="min-w-0">
             <div className="text-sm truncate" title={r.styleName}>
               {r.styleName || "—"}
@@ -433,24 +488,46 @@ export function AiOrderV2Page() {
       render: (val?: string) => {
         if (!showVal(val)) return <span className="text-gray-300">—</span>;
         const images = parseResultImages(val);
-        if (images.length > 0) {
+        const videos = parseResultVideos(val);
+        if (images.length > 0 || videos.length > 0) {
           return (
-            <Image.PreviewGroup>
-              <div className="flex items-center gap-1">
-                {images.slice(0, 3).map((url, idx) => (
-                  <Image
-                    key={`${url}-${idx}`}
-                    src={url}
-                    width={42}
-                    height={42}
-                    style={{ objectFit: "cover", borderRadius: 6 }}
-                  />
-                ))}
-                {images.length > 3 ? (
-                  <Tag style={{ margin: 0, fontSize: 11 }}>+{images.length - 3}</Tag>
-                ) : null}
-              </div>
-            </Image.PreviewGroup>
+            <div className="flex items-center gap-1">
+              {images.length > 0 && (
+                <Image.PreviewGroup>
+                  {images.slice(0, 3).map((url, idx) => (
+                    <Image
+                      key={`img-${idx}`}
+                      src={url}
+                      width={42}
+                      height={42}
+                      style={{ objectFit: "cover", borderRadius: 6 }}
+                    />
+                  ))}
+                </Image.PreviewGroup>
+              )}
+              {videos.slice(0, 2).map((v, idx) => (
+                <a
+                  key={`vid-${idx}`}
+                  href={v.url}
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="relative block shrink-0 overflow-hidden rounded-md bg-black"
+                  style={{ width: 42, height: 42 }}
+                >
+                  {v.thumbnailUrl ? (
+                    <img src={v.thumbnailUrl} className="h-full w-full object-cover" />
+                  ) : (
+                    <video src={v.url} muted preload="metadata" className="h-full w-full object-cover" />
+                  )}
+                  <div className="absolute inset-0 flex items-center justify-center bg-black/30">
+                    <PlayCircleOutlined className="text-white" style={{ fontSize: 16 }} />
+                  </div>
+                </a>
+              ))}
+              {images.length + videos.length > 3 ? (
+                <Tag style={{ margin: 0, fontSize: 11 }}>+{images.length + videos.length - 3}</Tag>
+              ) : null}
+            </div>
           );
         }
         return (
@@ -672,21 +749,43 @@ export function AiOrderV2Page() {
             <Card size="small" title={t("aiOrderV2.fieldResult")} style={{ marginTop: 12 }}>
               {showVal(detailData.result) ? (() => {
                 const images = parseResultImages(detailData.result);
-                if (images.length > 0) {
+                const videos = parseResultVideos(detailData.result);
+                if (images.length > 0 || videos.length > 0) {
                   return (
-                    <Image.PreviewGroup>
-                      <div className="flex flex-wrap gap-2">
-                        {images.map((url, idx) => (
-                          <Image
-                            key={`${url}-${idx}`}
-                            src={url}
-                            width={120}
-                            height={120}
-                            style={{ objectFit: "cover", borderRadius: 8 }}
-                          />
-                        ))}
-                      </div>
-                    </Image.PreviewGroup>
+                    <div className="flex flex-wrap gap-2">
+                      {images.length > 0 && (
+                        <Image.PreviewGroup>
+                          {images.map((url, idx) => (
+                            <Image
+                              key={`img-${idx}`}
+                              src={url}
+                              width={120}
+                              height={120}
+                              style={{ objectFit: "cover", borderRadius: 8 }}
+                            />
+                          ))}
+                        </Image.PreviewGroup>
+                      )}
+                      {videos.map((v, idx) => (
+                        <a
+                          key={`vid-${idx}`}
+                          href={v.url}
+                          target="_blank"
+                          rel="noopener noreferrer"
+                          className="group relative block shrink-0 overflow-hidden rounded-lg bg-black"
+                          style={{ width: 120, height: 120 }}
+                        >
+                          {v.thumbnailUrl ? (
+                            <img src={v.thumbnailUrl} className="h-full w-full object-cover" />
+                          ) : (
+                            <video src={v.url} muted preload="metadata" className="h-full w-full object-cover" />
+                          )}
+                          <div className="absolute inset-0 flex items-center justify-center bg-black/30 transition-colors group-hover:bg-black/40">
+                            <PlayCircleOutlined className="text-white" style={{ fontSize: 32 }} />
+                          </div>
+                        </a>
+                      ))}
+                    </div>
                   );
                 }
                 return (