Просмотр исходного кода

逐步修复 bug——修复线的连接问题

yemeishu 1 месяц назад
Родитель
Сommit
c1d502d8d8
2 измененных файлов с 242 добавлено и 24 удалено
  1. 121 12
      public/js/knowledge-mindmap-graph.js
  2. 121 12
      resources/js/knowledge-mindmap-graph.js

+ 121 - 12
public/js/knowledge-mindmap-graph.js

@@ -19,6 +19,7 @@ class KnowledgeMindmapGraph {
             { prerequisite: 'P04', target: 'P05', threshold: 0.6 },
             { prerequisite: 'P05', target: 'P06', threshold: 0.6 },
         ];
+        this.parentMap = {};
 
         this.setMasteryData(options.masteryData || {});
     }
@@ -50,6 +51,7 @@ class KnowledgeMindmapGraph {
 
         this.masteryCache = {};
         this.treeData = this.transformNode(this.rawTree);
+        this.buildParentMap(this.treeData);
         this.relationEdges = this.normalizeEdges(rawEdges);
         const flatIds = [];
         this.collectIds(this.treeData, flatIds);
@@ -78,7 +80,7 @@ class KnowledgeMindmapGraph {
 
         const model = {
             id,
-            label,
+            label: `${id} · ${label}`,
             depth,
             locked: false,
             collapsed: depth > 0 && (node.children || []).length > 0, // 默认折叠所有有子节点的节点(除根节点)
@@ -182,10 +184,10 @@ class KnowledgeMindmapGraph {
         const seen = new Set();
         const normalized = [];
         const styleMap = {
-            prerequisite: { stroke: '#60a5fa', lineDash: [10, 8], lineWidth: 3 },
-            successor: { stroke: '#7dd3fc', lineWidth: 3 },
-            crosslink: { stroke: '#fb923c', lineDash: [8, 6], lineWidth: 2.5 },
-            sibling: { stroke: '#94a3b8', lineDash: [6, 6], lineWidth: 2.5 },
+            prerequisite: { stroke: '#60a5fa', lineDash: [10, 8], lineWidth: 3, label: '前置' },
+            successor: { stroke: '#7dd3fc', lineWidth: 3, label: '后继' },
+            crosslink: { stroke: '#fb923c', lineDash: [8, 6], lineWidth: 2.5, label: '跨联' },
+            sibling: { stroke: '#94a3b8', lineDash: [6, 6], lineWidth: 2.5, label: '同级' },
         };
 
         (rawEdges || []).forEach((edge, index) => {
@@ -198,10 +200,21 @@ class KnowledgeMindmapGraph {
             const category = edge.type || 'successor';
             const renderType =
                 category === 'successor' ? 'cubic-horizontal' : 'quadratic';
-            const style = styleMap[category] || {
+            const baseStyle = styleMap[category] || {
                 stroke: '#cbd5e1',
                 lineWidth: 2.5,
             };
+            const label = baseStyle.label || edge.label || category;
+            const arrowStroke = baseStyle.stroke || '#cbd5e1';
+            const style = {
+                ...baseStyle,
+                endArrow: {
+                    path: 'M 0,0 L 8,4 L 0,8 z',
+                    fill: arrowStroke,
+                    d: 8,
+                },
+                startArrow: false,
+            };
 
             normalized.push({
                 id: `rel-${index}`,
@@ -212,7 +225,8 @@ class KnowledgeMindmapGraph {
                 style: {
                     ...style,
                 },
-                label: category,
+                label,
+                comment: edge.comment || edge.note || '',
             });
         });
 
@@ -298,11 +312,18 @@ class KnowledgeMindmapGraph {
         window.KnowledgeMindmapGraphInstance = this;
         window.KnowledgeMindmapG6Graph = this.graph;
 
-        this.drawRelationEdges();
+        // 边提示
+        this.bindEdgeTooltip();
+
         this.applyNodeStates();
         this.startEdgeFlows();
         this.focusOnLowestMastery();
         this.repaintNodes();
+
+        // 折叠/展开或重新布局后重新挂载关联线
+        this.graph.on('afterlayout', () => {
+            this.redrawRelationEdges();
+        });
     }
 
     setCollapsedState(nodeData, depth = 0) {
@@ -334,12 +355,43 @@ class KnowledgeMindmapGraph {
     drawRelationEdges() {
         if (!this.graph || !this.relationEdges.length) return;
         this.relationEdges.forEach((edge) => {
-            // 跳过缺失节点的边,避免 G6 报错导致后续渲染异常
-            if (!this.graph.findById(edge.source) || !this.graph.findById(edge.target)) {
-                console.warn('[mindmap] 跳过无效关联边', edge.id || edge.source + '-' + edge.target);
+            // 尝试将隐藏节点映射到可见的父节点,避免关联线丢失
+            let sourceId = edge.source;
+            let targetId = edge.target;
+            const resolveVisible = (id) => {
+                let cur = id;
+                while (cur) {
+                    if (this.graph.findById(cur)) return cur;
+                    cur = this.parentMap[cur];
+                }
+                return null;
+            };
+            if (!this.graph.findById(sourceId)) {
+                const fallback = resolveVisible(sourceId);
+                if (fallback) sourceId = fallback;
+            }
+            if (!this.graph.findById(targetId)) {
+                const fallback = resolveVisible(targetId);
+                if (fallback) targetId = fallback;
+            }
+            const sourceVisible = this.graph.findById(sourceId);
+            const targetVisible = this.graph.findById(targetId);
+            // 若两端都找不到可见节点,直接跳过
+            if (!sourceVisible && !targetVisible) {
                 return;
             }
-            this.graph.addItem('edge', edge);
+            // 若只有一端可见,则将不可见端提升到其最近的可见父节点(若仍不可见则跳过)
+            if (!sourceVisible) {
+                const resolved = resolveVisible(edge.source);
+                if (!resolved || !this.graph.findById(resolved)) return;
+                sourceId = resolved;
+            }
+            if (!targetVisible) {
+                const resolved = resolveVisible(edge.target);
+                if (!resolved || !this.graph.findById(resolved)) return;
+                targetId = resolved;
+            }
+            this.graph.addItem('edge', { ...edge, source: sourceId, target: targetId });
         });
     }
 
@@ -646,6 +698,7 @@ class KnowledgeMindmapGraph {
 
         this.masteryCache = {};
         this.treeData = this.transformNode(this.rawTree);
+        this.buildParentMap(this.treeData);
         const flatIds = [];
         this.collectIds(this.treeData, flatIds);
         this.nodeIdSet = new Set(flatIds);
@@ -672,6 +725,54 @@ class KnowledgeMindmapGraph {
         this.graph.paint();
     }
 
+    bindEdgeTooltip() {
+        if (!this.graph) return;
+        const tooltipEl = document.createElement('div');
+        tooltipEl.style.position = 'fixed';
+        tooltipEl.style.pointerEvents = 'none';
+        tooltipEl.style.zIndex = '9999';
+        tooltipEl.style.display = 'none';
+        tooltipEl.style.background = 'rgba(15,23,42,0.95)';
+        tooltipEl.style.color = '#e2e8f0';
+        tooltipEl.style.padding = '8px 10px';
+        tooltipEl.style.borderRadius = '8px';
+        tooltipEl.style.boxShadow = '0 10px 30px rgba(0,0,0,0.18)';
+        tooltipEl.style.fontSize = '12px';
+        tooltipEl.style.lineHeight = '1.4';
+        document.body.appendChild(tooltipEl);
+
+        const show = (html, x, y) => {
+            tooltipEl.innerHTML = html;
+            tooltipEl.style.left = `${x + 12}px`;
+            tooltipEl.style.top = `${y + 12}px`;
+            tooltipEl.style.display = 'block';
+        };
+        const hide = () => {
+            tooltipEl.style.display = 'none';
+        };
+
+        const buildHtml = (model) => {
+            return `
+                <div style="font-weight:700;font-size:13px;margin-bottom:4px;">${model.label || '关联'}</div>
+                <div style="font-size:12px;">${model.source || ''} → ${model.target || ''}</div>
+                ${model.comment ? `<div style="font-size:11px;color:#cbd5e1;margin-top:4px;white-space:pre-line;">${model.comment}</div>` : ''}
+            `;
+        };
+
+        this.graph.on('edge:mouseenter', (evt) => {
+            const model = evt?.item?.getModel?.() || {};
+            const { clientX, clientY } = evt;
+            show(buildHtml(model), clientX, clientY);
+        });
+
+        this.graph.on('edge:mouseleave', () => hide());
+    }
+
+    redrawRelationEdges() {
+        this.clearRelationEdges();
+        this.drawRelationEdges();
+    }
+
     forceCollapseNodes() {
         if (!this.graph) return;
     }
@@ -877,6 +978,14 @@ class KnowledgeMindmapGraph {
         }
     }
 
+    buildParentMap(node, parentId = null) {
+        if (!node) return;
+        this.parentMap[node.id] = parentId;
+        (node.children || []).forEach((child) =>
+            this.buildParentMap(child, node.id)
+        );
+    }
+
 }
 
 // 定义KnowledgeMindmapGraph类,确保G6已加载

+ 121 - 12
resources/js/knowledge-mindmap-graph.js

@@ -19,6 +19,7 @@ class KnowledgeMindmapGraph {
             { prerequisite: 'P04', target: 'P05', threshold: 0.6 },
             { prerequisite: 'P05', target: 'P06', threshold: 0.6 },
         ];
+        this.parentMap = {};
 
         this.setMasteryData(options.masteryData || {});
     }
@@ -50,6 +51,7 @@ class KnowledgeMindmapGraph {
 
         this.masteryCache = {};
         this.treeData = this.transformNode(this.rawTree);
+        this.buildParentMap(this.treeData);
         this.relationEdges = this.normalizeEdges(rawEdges);
         const flatIds = [];
         this.collectIds(this.treeData, flatIds);
@@ -78,7 +80,7 @@ class KnowledgeMindmapGraph {
 
         const model = {
             id,
-            label,
+            label: `${id} · ${label}`,
             depth,
             locked: false,
             collapsed: depth > 0 && (node.children || []).length > 0, // 默认折叠所有有子节点的节点(除根节点)
@@ -182,10 +184,10 @@ class KnowledgeMindmapGraph {
         const seen = new Set();
         const normalized = [];
         const styleMap = {
-            prerequisite: { stroke: '#60a5fa', lineDash: [10, 8], lineWidth: 3 },
-            successor: { stroke: '#7dd3fc', lineWidth: 3 },
-            crosslink: { stroke: '#fb923c', lineDash: [8, 6], lineWidth: 2.5 },
-            sibling: { stroke: '#94a3b8', lineDash: [6, 6], lineWidth: 2.5 },
+            prerequisite: { stroke: '#60a5fa', lineDash: [10, 8], lineWidth: 3, label: '前置' },
+            successor: { stroke: '#7dd3fc', lineWidth: 3, label: '后继' },
+            crosslink: { stroke: '#fb923c', lineDash: [8, 6], lineWidth: 2.5, label: '跨联' },
+            sibling: { stroke: '#94a3b8', lineDash: [6, 6], lineWidth: 2.5, label: '同级' },
         };
 
         (rawEdges || []).forEach((edge, index) => {
@@ -198,10 +200,21 @@ class KnowledgeMindmapGraph {
             const category = edge.type || 'successor';
             const renderType =
                 category === 'successor' ? 'cubic-horizontal' : 'quadratic';
-            const style = styleMap[category] || {
+            const baseStyle = styleMap[category] || {
                 stroke: '#cbd5e1',
                 lineWidth: 2.5,
             };
+            const label = baseStyle.label || edge.label || category;
+            const arrowStroke = baseStyle.stroke || '#cbd5e1';
+            const style = {
+                ...baseStyle,
+                endArrow: {
+                    path: 'M 0,0 L 8,4 L 0,8 z',
+                    fill: arrowStroke,
+                    d: 8,
+                },
+                startArrow: false,
+            };
 
             normalized.push({
                 id: `rel-${index}`,
@@ -212,7 +225,8 @@ class KnowledgeMindmapGraph {
                 style: {
                     ...style,
                 },
-                label: category,
+                label,
+                comment: edge.comment || edge.note || '',
             });
         });
 
@@ -298,11 +312,18 @@ class KnowledgeMindmapGraph {
         window.KnowledgeMindmapGraphInstance = this;
         window.KnowledgeMindmapG6Graph = this.graph;
 
-        this.drawRelationEdges();
+        // 边提示
+        this.bindEdgeTooltip();
+
         this.applyNodeStates();
         this.startEdgeFlows();
         this.focusOnLowestMastery();
         this.repaintNodes();
+
+        // 折叠/展开或重新布局后重新挂载关联线
+        this.graph.on('afterlayout', () => {
+            this.redrawRelationEdges();
+        });
     }
 
     setCollapsedState(nodeData, depth = 0) {
@@ -334,12 +355,43 @@ class KnowledgeMindmapGraph {
     drawRelationEdges() {
         if (!this.graph || !this.relationEdges.length) return;
         this.relationEdges.forEach((edge) => {
-            // 跳过缺失节点的边,避免 G6 报错导致后续渲染异常
-            if (!this.graph.findById(edge.source) || !this.graph.findById(edge.target)) {
-                console.warn('[mindmap] 跳过无效关联边', edge.id || edge.source + '-' + edge.target);
+            // 尝试将隐藏节点映射到可见的父节点,避免关联线丢失
+            let sourceId = edge.source;
+            let targetId = edge.target;
+            const resolveVisible = (id) => {
+                let cur = id;
+                while (cur) {
+                    if (this.graph.findById(cur)) return cur;
+                    cur = this.parentMap[cur];
+                }
+                return null;
+            };
+            if (!this.graph.findById(sourceId)) {
+                const fallback = resolveVisible(sourceId);
+                if (fallback) sourceId = fallback;
+            }
+            if (!this.graph.findById(targetId)) {
+                const fallback = resolveVisible(targetId);
+                if (fallback) targetId = fallback;
+            }
+            const sourceVisible = this.graph.findById(sourceId);
+            const targetVisible = this.graph.findById(targetId);
+            // 若两端都找不到可见节点,直接跳过
+            if (!sourceVisible && !targetVisible) {
                 return;
             }
-            this.graph.addItem('edge', edge);
+            // 若只有一端可见,则将不可见端提升到其最近的可见父节点(若仍不可见则跳过)
+            if (!sourceVisible) {
+                const resolved = resolveVisible(edge.source);
+                if (!resolved || !this.graph.findById(resolved)) return;
+                sourceId = resolved;
+            }
+            if (!targetVisible) {
+                const resolved = resolveVisible(edge.target);
+                if (!resolved || !this.graph.findById(resolved)) return;
+                targetId = resolved;
+            }
+            this.graph.addItem('edge', { ...edge, source: sourceId, target: targetId });
         });
     }
 
@@ -646,6 +698,7 @@ class KnowledgeMindmapGraph {
 
         this.masteryCache = {};
         this.treeData = this.transformNode(this.rawTree);
+        this.buildParentMap(this.treeData);
         const flatIds = [];
         this.collectIds(this.treeData, flatIds);
         this.nodeIdSet = new Set(flatIds);
@@ -672,6 +725,54 @@ class KnowledgeMindmapGraph {
         this.graph.paint();
     }
 
+    bindEdgeTooltip() {
+        if (!this.graph) return;
+        const tooltipEl = document.createElement('div');
+        tooltipEl.style.position = 'fixed';
+        tooltipEl.style.pointerEvents = 'none';
+        tooltipEl.style.zIndex = '9999';
+        tooltipEl.style.display = 'none';
+        tooltipEl.style.background = 'rgba(15,23,42,0.95)';
+        tooltipEl.style.color = '#e2e8f0';
+        tooltipEl.style.padding = '8px 10px';
+        tooltipEl.style.borderRadius = '8px';
+        tooltipEl.style.boxShadow = '0 10px 30px rgba(0,0,0,0.18)';
+        tooltipEl.style.fontSize = '12px';
+        tooltipEl.style.lineHeight = '1.4';
+        document.body.appendChild(tooltipEl);
+
+        const show = (html, x, y) => {
+            tooltipEl.innerHTML = html;
+            tooltipEl.style.left = `${x + 12}px`;
+            tooltipEl.style.top = `${y + 12}px`;
+            tooltipEl.style.display = 'block';
+        };
+        const hide = () => {
+            tooltipEl.style.display = 'none';
+        };
+
+        const buildHtml = (model) => {
+            return `
+                <div style="font-weight:700;font-size:13px;margin-bottom:4px;">${model.label || '关联'}</div>
+                <div style="font-size:12px;">${model.source || ''} → ${model.target || ''}</div>
+                ${model.comment ? `<div style="font-size:11px;color:#cbd5e1;margin-top:4px;white-space:pre-line;">${model.comment}</div>` : ''}
+            `;
+        };
+
+        this.graph.on('edge:mouseenter', (evt) => {
+            const model = evt?.item?.getModel?.() || {};
+            const { clientX, clientY } = evt;
+            show(buildHtml(model), clientX, clientY);
+        });
+
+        this.graph.on('edge:mouseleave', () => hide());
+    }
+
+    redrawRelationEdges() {
+        this.clearRelationEdges();
+        this.drawRelationEdges();
+    }
+
     forceCollapseNodes() {
         if (!this.graph) return;
     }
@@ -877,6 +978,14 @@ class KnowledgeMindmapGraph {
         }
     }
 
+    buildParentMap(node, parentId = null) {
+        if (!node) return;
+        this.parentMap[node.id] = parentId;
+        (node.children || []).forEach((child) =>
+            this.buildParentMap(child, node.id)
+        );
+    }
+
 }
 
 // 定义KnowledgeMindmapGraph类,确保G6已加载