Kaynağa Gözat

feat(web): collapsible content post cards in platform/content panel

- 平台发现内容每条帖子抽为 ContentPostCard 组件,标题行加三角箭头按钮
  (展开 ChevronDown / 收起 ChevronRight),点击折叠/展开详情(meta 网格+打开原帖)
- 默认展开;本地 useState 各卡独立折叠;按钮 aria-expanded 无障碍
- 纯前端展示改动,零后端/数据变更;tsc + build 通过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sam Lee 2 gün önce
ebeveyn
işleme
1bb1882829
2 değiştirilmiş dosya ile 79 ekleme ve 38 silme
  1. 25 0
      web/app/globals.css
  2. 54 38
      web/features/runs/RunDashboardPage.tsx

+ 25 - 0
web/app/globals.css

@@ -1334,3 +1334,28 @@ a {
   color: inherit;
   opacity: 0.8;
 }
+
+/* 平台/内容帖子卡片:展开/收缩 */
+.content-card-toggle {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  width: 100%;
+  padding: 0;
+  border: none;
+  background: transparent;
+  cursor: pointer;
+  text-align: left;
+  color: inherit;
+}
+.content-card-toggle svg {
+  flex: 0 0 auto;
+  color: #8491a6;
+  transition: color 0.15s;
+}
+.content-card-toggle:hover svg {
+  color: #2563eb;
+}
+.content-card-toggle .content-card-heading {
+  flex: 1 1 auto;
+}

+ 54 - 38
web/features/runs/RunDashboardPage.tsx

@@ -3,6 +3,7 @@
 import Link from "next/link";
 
 import {
+  ChevronDown,
   ChevronRight,
   FileJson,
   GitBranch,
@@ -528,44 +529,7 @@ function StagePanel({
         <ConclusionBody stage={data.dashboard.stage_conclusions.find((stage) => stage.stage_id === "platform")} />
         <div className="business-card-list">
           {data.contentItems.items.length ? data.contentItems.items.map((item, index) => (
-            <div className="business-record-card" key={String(item.platform_content_id || index)}>
-              <div className="content-card-heading">
-                <span>平台内容 #{index + 1}</span>
-                <strong>{compactValue(item.title || item.description || "抖音视频")}</strong>
-              </div>
-              <div className="content-meta-grid">
-                <span>
-                  抖音视频 ID
-                  <strong>{compactValue(item.platform_content_id)}</strong>
-                </span>
-                <span>
-                  来源 Query
-                  <strong>{compactValue(primaryQuerySource(item).search_query_id)}</strong>
-                </span>
-                <span>
-                  搜索关键词
-                  <strong>{compactValue(primaryQuerySource(item).search_query)}</strong>
-                </span>
-                <span>
-                  Query 类型
-                  <strong>{queryGenerationMethodLabel(primaryQuerySource(item).search_query_generation_method)}</strong>
-                </span>
-                <span>
-                  抖音作者
-                  <strong>{compactValue(item.author_display_name || item.platform_author_id)}</strong>
-                </span>
-              </div>
-              <div className="business-action-row">
-                <a
-                  className="text-button"
-                  href={douyinContentUrl(item)}
-                  rel="noreferrer"
-                  target="_blank"
-                >
-                  打开原帖
-                </a>
-              </div>
-            </div>
+            <ContentPostCard item={item} index={index} key={String(item.platform_content_id || index)} />
           )) : <div className="empty-state">还没有发现内容;请先查看 Query 或平台失败原因</div>}
         </div>
       </BusinessSection>
@@ -664,6 +628,58 @@ function StagePanel({
   );
 }
 
+function ContentPostCard({ item, index }: { item: Record<string, unknown>; index: number }) {
+  const [open, setOpen] = useState(true);
+  const querySource = primaryQuerySource(item);
+  return (
+    <div className="business-record-card">
+      <button
+        type="button"
+        className="content-card-toggle"
+        aria-expanded={open}
+        onClick={() => setOpen((v) => !v)}
+      >
+        {open ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
+        <span className="content-card-heading">
+          <span>平台内容 #{index + 1}</span>
+          <strong>{compactValue(item.title || item.description || "抖音视频")}</strong>
+        </span>
+      </button>
+      {open ? (
+        <>
+          <div className="content-meta-grid">
+            <span>
+              抖音视频 ID
+              <strong>{compactValue(item.platform_content_id)}</strong>
+            </span>
+            <span>
+              来源 Query
+              <strong>{compactValue(querySource.search_query_id)}</strong>
+            </span>
+            <span>
+              搜索关键词
+              <strong>{compactValue(querySource.search_query)}</strong>
+            </span>
+            <span>
+              Query 类型
+              <strong>{queryGenerationMethodLabel(querySource.search_query_generation_method)}</strong>
+            </span>
+            <span>
+              抖音作者
+              <strong>{compactValue(item.author_display_name || item.platform_author_id)}</strong>
+            </span>
+          </div>
+          <div className="business-action-row">
+            <a className="text-button" href={douyinContentUrl(item)} rel="noreferrer" target="_blank">
+              打开原帖
+            </a>
+          </div>
+        </>
+      ) : null}
+    </div>
+  );
+}
+
 function BusinessSection({ title, icon, children }: { title: string; icon: React.ReactNode; children: React.ReactNode }) {
   return (
     <section className="business-section">