elksmmx 1 месяц назад
Родитель
Сommit
6b11895111
3 измененных файлов с 3133 добавлено и 176550 удалено
  1. 0 70387
      knowhub/frontend/public/itemsets_all.json
  2. 3047 106095
      knowhub/frontend/public/requirements_planb.json
  3. 86 68
      knowhub/frontend/src/pages/Dashboard.tsx

Разница между файлами не показана из-за своего большого размера
+ 0 - 70387
knowhub/frontend/public/itemsets_all.json


Разница между файлами не показана из-за своего большого размера
+ 3047 - 106095
knowhub/frontend/public/requirements_planb.json


+ 86 - 68
knowhub/frontend/src/pages/Dashboard.tsx

@@ -472,7 +472,7 @@ function CoverageFlowBoard({ data }: { data: any }) {
 
 
 // ─── 关系列卡片 ────────────────────────────────────────────────────────────────
 // ─── 关系列卡片 ────────────────────────────────────────────────────────────────
 
 
-function RelationCard({ type, item, activeId, shouldScrollIntoView = false, selectedLeafNames, directMatch = false, dimmed = false, showAllSourceTags = false, metrics, onSingleClick, onSourceNodeClick }: {
+function RelationCard({ type, item, activeId, shouldScrollIntoView = false, selectedLeafNames, directMatch = false, dimmed = false, showAllSourceTags = false, metrics, reqPlanBData, onSingleClick, onSourceNodeClick }: {
   type: string;
   type: string;
   item: any;
   item: any;
   activeId: string | null;
   activeId: string | null;
@@ -482,6 +482,7 @@ function RelationCard({ type, item, activeId, shouldScrollIntoView = false, sele
   dimmed?: boolean;
   dimmed?: boolean;
   showAllSourceTags?: boolean;
   showAllSourceTags?: boolean;
   metrics?: { nodeCount: number; reqCount: number; procCount: number; capCount: number; toolCount: number };
   metrics?: { nodeCount: number; reqCount: number; procCount: number; capCount: number; toolCount: number };
+  reqPlanBData?: any;
   onSingleClick: (nodeId: string) => void;
   onSingleClick: (nodeId: string) => void;
   onSourceNodeClick?: (nodeName: string) => void;
   onSourceNodeClick?: (nodeName: string) => void;
 }) {
 }) {
@@ -628,6 +629,15 @@ function RelationCard({ type, item, activeId, shouldScrollIntoView = false, sele
 // ─── 详情抽屉内容 ──────────────────────────────────────────────────────────────
 // ─── 详情抽屉内容 ──────────────────────────────────────────────────────────────
 
 
 function DrawerContent({ type, data, dbData, onOpenPost, nodePostsMap, selectedReqId }: { type: string; data: any; dbData: any; onOpenPost: (postId: string, post: any) => void; nodePostsMap?: Record<string, string[]>; selectedReqId?: string | null }) {
 function DrawerContent({ type, data, dbData, onOpenPost, nodePostsMap, selectedReqId }: { type: string; data: any; dbData: any; onOpenPost: (postId: string, post: any) => void; nodePostsMap?: Record<string, string[]>; selectedReqId?: string | null }) {
+  if (type === 'itemset') {
+    return (
+      <ItemsetPostsDrawer
+        itemset={data}
+        onOpenPost={onOpenPost}
+      />
+    );
+  }
+
   if (type === 'req') {
   if (type === 'req') {
     return (
     return (
       <RequirementPostsDrawer
       <RequirementPostsDrawer
@@ -1088,68 +1098,7 @@ function DrawerContent({ type, data, dbData, onOpenPost, nodePostsMap, selectedR
           </div>
           </div>
         </>
         </>
       )}
       )}
-      {type === 'itemset' && (
-        <>
-          <div className="bg-amber-50 p-4 rounded-xl border border-amber-100">
-            <div className="text-xs font-bold text-amber-600 mb-2">Pattern 节点组合</div>
-            <div className="flex flex-wrap gap-1.5">
-              {(data.leaf_names || []).map((name: string) => (
-                <span key={name} className="px-2 py-0.5 rounded-full text-xs bg-amber-100 text-amber-800 border border-amber-200 font-medium">{name}</span>
-              ))}
-            </div>
-            <div className="mt-3 flex items-center gap-3 text-[11px] text-amber-700">
-              <span>支持度 <span className="font-bold">{data.absolute_support}</span></span>
-              <span className="text-amber-400">·</span>
-              <span className="font-mono text-amber-500">#{data.id}</span>
-            </div>
-          </div>
-
-          {(() => {
-            const filteredAssocs = selectedReqId 
-              ? (data.req_associations || []).filter((assoc: any) => assoc.req_id === selectedReqId)
-              : (data.req_associations || []);
-
-            if (filteredAssocs.length === 0) {
-              return <div className="text-xs text-slate-400 text-center py-4">暂无关联需求数据</div>;
-            }
 
 
-            return (
-              <div>
-                <div className="text-xs font-bold text-slate-500 mb-2">关联需求 ({filteredAssocs.length})</div>
-                <div className="space-y-3">
-                  {filteredAssocs.map((assoc: any) => {
-                    const trueNodes = (assoc.judgments || []).filter((j: any) => j.represents).map((j: any) => j.node);
-                    const falseNodes = (assoc.judgments || []).filter((j: any) => !j.represents).map((j: any) => j.node);
-                    return (
-                      <div key={assoc.req_id} className="bg-white border border-slate-100 rounded-xl p-3 space-y-2">
-                        <div className="flex items-center gap-2">
-                          <span className="font-mono text-[10px] text-indigo-500 shrink-0">{assoc.req_id}</span>
-                          <span className="text-xs text-slate-700 leading-snug line-clamp-2">{assoc.req_text}</span>
-                        </div>
-                        {assoc.judgments?.length > 0 && (
-                          <div className="flex flex-wrap gap-1">
-                            {(assoc.judgments as any[]).map((j: any) => (
-                              <span key={j.node} title={j.reason} className={cn(
-                                "px-1.5 py-0.5 rounded-full text-[10px] border font-medium cursor-help",
-                                j.represents
-                                  ? "bg-emerald-50 text-emerald-700 border-emerald-200"
-                                  : "bg-slate-50 text-slate-400 border-slate-200 line-through"
-                              )}>{j.node}</span>
-                            ))}
-                          </div>
-                        )}
-                        {trueNodes.length === 0 && falseNodes.length > 0 && (
-                          <div className="text-[10px] text-slate-400">无节点被判断为代表该需求</div>
-                        )}
-                      </div>
-                    );
-                  })}
-                </div>
-              </div>
-            );
-          })()}
-        </>
-      )}
     </div>
     </div>
   );
   );
 }
 }
@@ -1432,6 +1381,72 @@ function StrategyResourcesGrid({ resourceIds, onOpenPost }: { resourceIds: strin
 
 
 // ─── 业务需求帖子聚合抽屉 ──────────────────────────────────────────────────────
 // ─── 业务需求帖子聚合抽屉 ──────────────────────────────────────────────────────
 
 
+function ItemsetPostsDrawer({
+  itemset,
+  onOpenPost,
+}: {
+  itemset: any;
+  onOpenPost: (postId: string, post: any) => void;
+}) {
+  const [posts, setPosts] = useState<Record<string, any>>({});
+  const [loading, setLoading] = useState(false);
+
+  const postIds = itemset.post_ids || [];
+
+  useEffect(() => {
+    if (postIds.length === 0) return;
+    setLoading(true);
+    setPosts({});
+    batchGetPosts(postIds)
+      .then(map => setPosts(map))
+      .catch(err => { console.error('Failed to load itemset posts:', err); })
+      .finally(() => setLoading(false));
+  }, [itemset.id, postIds]);
+
+  return (
+    <div className="flex flex-col gap-3 h-full overflow-hidden min-w-0">
+      <div className="shrink-0 bg-amber-50 p-4 rounded-xl border border-amber-100">
+        <div className="text-xs font-bold text-amber-600 mb-2">Pattern 节点组合</div>
+        <div className="flex flex-wrap gap-1.5">
+          {(itemset.leaf_names || []).map((name: string) => (
+            <span key={name} className="px-2 py-0.5 rounded-full text-xs bg-amber-100 text-amber-800 border border-amber-200 font-medium">{name}</span>
+          ))}
+        </div>
+        <div className="mt-3 flex items-center gap-3 text-[11px] text-amber-700">
+          <span>支持度 <span className="font-bold">{itemset.absolute_support}</span></span>
+          <span className="text-amber-400">·</span>
+          <span className="font-mono text-amber-500">#{itemset.id}</span>
+        </div>
+      </div>
+
+      <div className="flex-1 min-h-0 overflow-y-auto pr-1 custom-scrollbar">
+        <div className="text-xs font-bold text-slate-500 mb-2">关联帖子 ({postIds.length})</div>
+        <div className="grid grid-cols-1 gap-3">
+          {loading && (
+            <div className="flex items-center justify-center gap-2 text-slate-400 rounded-xl border border-slate-100 bg-slate-50 min-h-[160px]">
+              <div className="w-4 h-4 border-2 border-indigo-200 border-t-indigo-500 rounded-full animate-spin"></div>
+              加载中...
+            </div>
+          )}
+          {!loading && postIds.length === 0 && (
+            <div className="flex items-center justify-center text-xs text-slate-300 font-bold rounded-xl border border-slate-100 bg-slate-50 min-h-[160px]">
+              该 Pattern 暂无关联帖子
+            </div>
+          )}
+          {!loading && postIds.map((pid: string) => {
+            const post = posts[pid];
+            return (
+              <div key={pid} className="w-full">
+                <PostCard postId={pid} post={post} compact onClick={() => onOpenPost(pid, post)} />
+              </div>
+            );
+          })}
+        </div>
+      </div>
+    </div>
+  );
+}
+
 function RequirementPostsDrawer({
 function RequirementPostsDrawer({
   requirement,
   requirement,
   nodePostsMap,
   nodePostsMap,
@@ -1735,10 +1750,7 @@ function PatternColumn({
                   return (
                   return (
                     <span key={name} className={cn(
                     <span key={name} className={cn(
                       "px-2 py-0.5 rounded-full text-xs border",
                       "px-2 py-0.5 rounded-full text-xs border",
-                      isMatched && role === 'substance' ? "bg-orange-50 text-orange-700 border-orange-200 font-bold" :
-                      isMatched && role === 'form' ? "bg-sky-50 text-sky-700 border-sky-200 font-bold" :
-                      isMatched && role === 'both' ? "bg-emerald-50 text-emerald-700 border-emerald-200 font-bold" :
-                      isMatched ? "bg-slate-100 text-slate-700 border-slate-300 font-bold" :
+                      isMatched ? "bg-orange-50 text-orange-700 border-orange-200 font-bold" :
                       "bg-slate-50 text-slate-500 border-slate-200"
                       "bg-slate-50 text-slate-500 border-slate-200"
                     )}>{name}</span>
                     )}>{name}</span>
                   );
                   );
@@ -1759,7 +1771,7 @@ export function Dashboard({ pendingNode, onPendingConsumed }: { pendingNode?: st
   const [reqPlanBData, setReqPlanBData] = useState<any>(null);
   const [reqPlanBData, setReqPlanBData] = useState<any>(null);
 
 
   useEffect(() => {
   useEffect(() => {
-    fetch('/requirements_planb.json')
+    fetch('/requirements_planb.json?t=' + Date.now())
       .then(res => res.json())
       .then(res => res.json())
       .then(data => setReqPlanBData(data))
       .then(data => setReqPlanBData(data))
       .catch(console.error);
       .catch(console.error);
@@ -3339,7 +3351,12 @@ export function Dashboard({ pendingNode, onPendingConsumed }: { pendingNode?: st
         const node = nameToNodeMap[nodeName];
         const node = nameToNodeMap[nodeName];
         if (!node) return;
         if (!node) return;
 
 
-        const nodePostIds = collectNodePostIds(node);
+        const nodePostIds: string[] = [];
+        (node?.elements || []).forEach((el: any) => {
+          (el.post_ids || []).forEach((pid: string) => {
+            if (pid && !nodePostIds.includes(pid)) nodePostIds.push(pid);
+          });
+        });
         const nodePostIdSet = new Set(nodePostIds);
         const nodePostIdSet = new Set(nodePostIds);
         const explicitPosts = typeof sn === 'object'
         const explicitPosts = typeof sn === 'object'
           ? [
           ? [
@@ -3776,6 +3793,7 @@ export function Dashboard({ pendingNode, onPendingConsumed }: { pendingNode?: st
                   key={item.id}
                   key={item.id}
                   type={col.t}
                   type={col.t}
                   item={item}
                   item={item}
+                  reqPlanBData={reqPlanBData}
                   metrics={
                   metrics={
                     col.t === 'req' ? reqMetricsMap[item.id] :
                     col.t === 'req' ? reqMetricsMap[item.id] :
                     col.t === 'proc' ? procMetricsMap[item.id] :
                     col.t === 'proc' ? procMetricsMap[item.id] :

Некоторые файлы не были показаны из-за большого количества измененных файлов