@agilehub/vue-knowledge-graph 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +315 -3
- package/dist/components/GraphToolbar.vue.d.ts +21 -0
- package/dist/components/GraphVisualization.vue.d.ts +26 -0
- package/dist/index.d.ts +14 -0
- package/dist/module/g6Graph.d.ts +60 -0
- package/dist/types/index.d.ts +54 -0
- package/dist/vue-knowledge-graph.cjs +4 -0
- package/dist/vue-knowledge-graph.cjs.map +1 -0
- package/dist/vue-knowledge-graph.mjs +517 -0
- package/dist/vue-knowledge-graph.mjs.map +1 -0
- package/package.json +80 -6
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface GraphNode {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
type?: string;
|
|
5
|
+
size?: number;
|
|
6
|
+
style?: {
|
|
7
|
+
fill?: string;
|
|
8
|
+
stroke?: string;
|
|
9
|
+
lineWidth?: number;
|
|
10
|
+
};
|
|
11
|
+
x?: number;
|
|
12
|
+
y?: number;
|
|
13
|
+
/** 业务自定义属性,值可以是字符串、数字、对象等任意类型。 */
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface GraphEdge {
|
|
17
|
+
id?: string;
|
|
18
|
+
source: string;
|
|
19
|
+
target: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
relation_type?: string;
|
|
22
|
+
style?: {
|
|
23
|
+
stroke?: string;
|
|
24
|
+
lineWidth?: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface GraphData {
|
|
28
|
+
nodes: GraphNode[];
|
|
29
|
+
edges: GraphEdge[];
|
|
30
|
+
}
|
|
31
|
+
export interface FileInfo {
|
|
32
|
+
filename: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
nodes_count: number;
|
|
35
|
+
edges_count: number;
|
|
36
|
+
}
|
|
37
|
+
export interface GraphStats {
|
|
38
|
+
total_nodes: number;
|
|
39
|
+
total_edges: number;
|
|
40
|
+
total_files: number;
|
|
41
|
+
}
|
|
42
|
+
export interface GlobalGraphResponse {
|
|
43
|
+
nodes: GraphNode[];
|
|
44
|
+
edges: GraphEdge[];
|
|
45
|
+
files: FileInfo[];
|
|
46
|
+
stats: GraphStats;
|
|
47
|
+
}
|
|
48
|
+
export interface GenerateGraphRequest {
|
|
49
|
+
text: string;
|
|
50
|
+
}
|
|
51
|
+
export interface GenerateGraphResponse {
|
|
52
|
+
nodes: GraphNode[];
|
|
53
|
+
edges: GraphEdge[];
|
|
54
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
|
|
2
|
+
(function(){if(typeof document<"u"&&!document.getElementById("vue-knowledge-graph-styles")){var e=document.createElement("style");e.id="vue-knowledge-graph-styles",e.textContent=".graph-container[data-v-232ee924]{width:100%;height:100%}.kg-toolbar[data-v-f8202e9e]{z-index:100;background:#fff;border:1px solid #e2e8f0;border-radius:10px;align-items:center;gap:6px;padding:8px;font-family:Segoe UI,PingFang SC,system-ui,sans-serif;display:flex;position:absolute;top:16px;right:16px;box-shadow:0 2px 8px #0000000f}.kg-toolbar[data-v-f8202e9e]:hover{box-shadow:0 4px 12px #0000001a}.kg-toolbar.kg-collapsed[data-v-f8202e9e]{padding:6px;right:12px}.kg-btn-group[data-v-f8202e9e]{align-items:center;gap:8px;display:flex}.kg-btn[data-v-f8202e9e]{color:#334155;cursor:pointer;background:0 0;border:none;border-radius:6px;outline:none;align-items:center;gap:5px;padding:6px 12px;font-size:12px;font-weight:500;transition:all .2s;display:flex}.kg-btn[data-v-f8202e9e]:hover{color:#0f172a;background:#e2e8f0}.kg-btn[data-v-f8202e9e]:focus-visible,.kg-toggle[data-v-f8202e9e]:focus-visible{outline-offset:2px;outline:2px solid #60a5fa}.kg-icon[data-v-f8202e9e]{font-size:13px;line-height:1}.kg-layout-control[data-v-f8202e9e]{border-left:1px solid #e2e8f0;align-items:center;gap:7px;padding-left:10px;display:flex}.kg-layout-label[data-v-f8202e9e]{color:#64748b;white-space:nowrap;font-size:11px;font-weight:650}.kg-layout-select[data-v-f8202e9e]{color:#1e293b;background:#fff;border:1px solid #cbd5e1;border-radius:6px;outline:none;width:128px;min-height:31px;padding:5px 26px 5px 9px;font-size:12px}.kg-layout-select[data-v-f8202e9e]:hover,.kg-layout-select[data-v-f8202e9e]:focus{border-color:#60a5fa;box-shadow:0 0 0 2px #dbeafe}.kg-toggle[data-v-f8202e9e]{color:#64748b;background:0 0;border:none;border-radius:4px;padding:4px 8px;font-size:11px}.kg-toggle[data-v-f8202e9e]:hover{color:#0f172a;background:#e2e8f0}@media (width<=640px){.kg-toolbar[data-v-f8202e9e]{max-width:calc(100% - 24px)}.kg-btn-group[data-v-f8202e9e]{gap:4px}.kg-btn[data-v-f8202e9e]{padding:6px 8px}.kg-btn-label[data-v-f8202e9e]{display:none}.kg-layout-control[data-v-f8202e9e]{gap:4px;padding-left:6px}.kg-layout-label[data-v-f8202e9e]{display:none}.kg-layout-select[data-v-f8202e9e]{width:120px}}\n/*$vite$:1*/",document.head.appendChild(e)}})();
|
|
3
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require("vue"),t=require("@antv/g6");var n=[{value:`force`,label:`力导向`,description:`通用网络布局,适合结构未知或关系密集的图谱`,category:`network`},{value:`circular`,label:`环形`,description:`节点沿圆环排列,适合观察整体结构`,category:`network`},{value:`concentric`,label:`同心圆`,description:`按连接度分层,突出核心节点和外围节点`,category:`network`},{value:`antv-dagre`,label:`有向层级`,description:`按边的方向排列层级,适合 DAG 或流程数据`,category:`hierarchy`},{value:`indented`,label:`缩进树`,description:`目录式层级布局,适合近树结构`,category:`hierarchy`},{value:`mindmap`,label:`思维导图`,description:`从中心向两侧分支,适合近树结构`,category:`hierarchy`}],r=class{graph=null;container=null;resizeHandler=null;createGraph(e,n){let{container:r,width:i,height:a}=n;this.graph&&this.destroy(),this.container=r,this.graph=new t.Graph({container:r,width:i||r.offsetWidth,height:a||r.offsetHeight,...e}),this.setupResizeHandler()}setupResizeHandler(){this.cleanupResizeHandler(),this.resizeHandler=()=>{this.container&&this.resize(this.container.offsetWidth,this.container.offsetHeight)},window.addEventListener(`resize`,this.resizeHandler)}cleanupResizeHandler(){this.resizeHandler&&=(window.removeEventListener(`resize`,this.resizeHandler),null)}run(e){e.catch(()=>void 0)}hasGraph(){return this.graph!==null}resize(e,t){if(!this.graph||e<=0||t<=0)return;let[n,r]=this.graph.getSize();(n!==e||r!==t)&&this.graph.resize(e,t)}fitView(e=20){this.graph&&this.run(this.graph.fitView({when:`always`},!1))}destroy(){this.cleanupResizeHandler(),this.graph?.destroy(),this.graph=null,this.container=null}},i=class extends r{currentLayoutType=`force`;nodeClickHandler=null;canvasElement=null;canvasPointerDownHandler=null;canvasPointerMoveHandler=null;canvasPointerUpHandler=null;canvasPointerId=null;lastCanvasPoint={x:0,y:0};isCanvasDragging=!1;canvasDefaultCursor=`default`;getLayoutConfig(e,t,n){let r=[t/2,n/2],i=e=>Math.min(Math.max((e.label?.length??4)*14+28,72),220),a=()=>42;switch(e){case`circular`:return{type:e,center:r,radius:Math.min(t,n)*.35,startAngle:Math.PI/2,endAngle:Math.PI*2.5,ordering:`degree`};case`concentric`:return{type:e,center:r,preventOverlap:!0,nodeSize:42,sortBy:`degree`,equidistant:!0};case`antv-dagre`:return{type:e,rankdir:`TB`,align:`UL`,nodesep:42,ranksep:110};case`indented`:return{type:e,direction:`LR`,indent:36,getWidth:i,getHeight:a,getHGap:()=>18,getVGap:()=>12};case`mindmap`:return{type:e,direction:`H`,preLayout:!1,getWidth:i,getHeight:a,getHGap:()=>30,getVGap:()=>18};default:return{type:`force`,center:r,linkDistance:280,nodeStrength:1e3,edgeStrength:200,gravity:10}}}getColorByType(e){return{人物:`#ff4d4f`,地点:`#52c41a`,组织:`#1890ff`,概念:`#faad14`,事件:`#722ed1`,entity:`#13c2c2`}[e||`entity`]||`#13c2c2`}getColorByRelation(e){return{因果:`#e74c3c`,所属:`#2ecc71`,引用:`#3498db`,相关:`#9b59b6`}[e||`相关`]||`#9b59b6`}init(e,t,n){let r=e.width||e.container.offsetWidth,i=e.height||e.container.offsetHeight,a=this.processData(t,n);this.currentLayoutType=e.layoutType??`force`,this.createGraph({data:a,animation:!1,zoomRange:[.1,5],layout:this.getLayoutConfig(this.currentLayoutType,r,i),behaviors:[{type:`zoom-canvas`,sensitivity:1},{type:`drag-element`},{type:`hover-activate`,degree:1},{type:`click-select`,degree:1}],node:{type:`circle`,style:{cursor:`pointer`,labelPlacement:`bottom`,labelFill:`#000000`,labelFontSize:16,labelFontWeight:`bold`},state:{active:{lineWidth:3,shadowBlur:12,shadowColor:`#40a9ff`},selected:{lineWidth:4,stroke:`#ff4d4f`},inactive:{opacity:.25,labelOpacity:.35}}},edge:{type:`line`,style:{cursor:`pointer`,endArrow:!0,labelAutoRotate:!0,labelFontSize:12},state:{active:{lineWidth:3},selected:{lineWidth:3,stroke:`#ff4d4f`},inactive:{opacity:.2,labelOpacity:.2}}}},e),this.graph&&(this.nodeClickHandler=e.onNodeClick??null,this.setupEvents(),this.run(this.graph.render().then(()=>{this.setupCanvasDrag(),this.scheduleFitView()})))}update(e,t){this.graph&&(this.graph.setData(this.processData(e,t)),this.run(this.graph.render().then(()=>this.scheduleFitView())))}resetLayout(){if(!this.graph)return;let e=this.graph.getNodeData().map(e=>{let t={...e.style};return delete t.x,delete t.y,{...e,style:t}});this.graph.setData({nodes:e,edges:this.graph.getEdgeData()}),this.graph.setLayout(this.getCurrentLayout()),this.run(this.graph.layout().then(()=>this.scheduleFitView()))}switchLayout(e){!this.graph||!this.container||(this.currentLayoutType=e,this.graph.setLayout(this.getCurrentLayout()),this.run(this.graph.layout().then(()=>this.scheduleFitView())))}getLayoutType(){return this.currentLayoutType}focusNode(e){!this.graph||!this.graph.getNodeData().some(({id:t})=>t===e)||this.run(this.graph.focusElement(e,{duration:500,easing:`ease-in-out`}))}getCurrentLayout(){let e=this.container?.offsetWidth||this.graph?.getSize()[0]||0,t=this.container?.offsetHeight||this.graph?.getSize()[1]||0;return this.getLayoutConfig(this.currentLayoutType,e,t)}processData(e,t){let n={};for(let e of t)n[e.source]=(n[e.source]||0)+1,n[e.target]=(n[e.target]||0)+1;return{nodes:e.map(e=>({id:e.id,data:{...e,label:e.label,entityType:e.type},style:{x:e.x,y:e.y,size:e.size||Math.min(15+(n[e.id]||0)*3,40),fill:e.style?.fill||this.getColorByType(e.type),stroke:e.style?.stroke||`#ffffff`,lineWidth:e.style?.lineWidth||2,labelText:e.label}})),edges:t.map((e,t)=>{let n=e.style?.stroke||this.getColorByRelation(e.relation_type);return{id:e.id||`edge-${e.source}-${e.target}-${t}`,source:e.source,target:e.target,data:{label:e.label,relationType:e.relation_type},style:{stroke:n,lineWidth:e.style?.lineWidth||2,endArrowFill:n,labelText:e.label,labelFill:n}}})}}scheduleFitView(){let t=()=>{(0,e.nextTick)(()=>{requestAnimationFrame(()=>this.fitView(20))})};t(),window.setTimeout(t,300),window.setTimeout(t,1200)}setupEvents(){if(!this.graph||!this.container)return;let e=this.graph;e.on(t.NodeEvent.CLICK,e=>{this.nodeClickHandler?.(e.target.id)}),e.on(t.NodeEvent.DRAG,t=>{let n=t.target.id,r=.3,i=e.getNeighborNodesData(n).map(e=>({id:e.id,style:{x:(e.style?.x||0)+t.dx*r,y:(e.style?.y||0)+t.dy*r}}));i.length!==0&&(e.updateNodeData(i),this.run(e.draw()))})}setupCanvasDrag(){if(!this.graph)return;let e=this.graph,n=e.getCanvas(),r=n.getContextService().getDomElement();r&&(this.canvasElement=r,this.canvasDefaultCursor=n.getConfig().cursor||`default`,this.canvasPointerDownHandler=e=>{e.button===0&&this.canvasPointerId===null&&(this.canvasPointerId=e.pointerId,this.isCanvasDragging=!0,this.lastCanvasPoint={x:e.clientX,y:e.clientY},r.setPointerCapture(e.pointerId),n.setCursor(`grabbing`))},this.canvasPointerMoveHandler=t=>{if(!this.isCanvasDragging||this.canvasPointerId!==null&&t.pointerId!==this.canvasPointerId)return;let n=[t.clientX-this.lastCanvasPoint.x,t.clientY-this.lastCanvasPoint.y];this.lastCanvasPoint={x:t.clientX,y:t.clientY},this.run(e.translateBy(n,!1)),t.preventDefault?.()},this.canvasPointerUpHandler=e=>{if(this.canvasPointerId!==null&&e.pointerId!==this.canvasPointerId)return;let t=this.isCanvasDragging;this.isCanvasDragging=!1,this.canvasPointerId=null,t&&r.hasPointerCapture?.(e.pointerId)&&r.releasePointerCapture(e.pointerId),t&&n.setCursor(this.canvasDefaultCursor)},e.on(t.CanvasEvent.POINTER_DOWN,this.canvasPointerDownHandler),window.addEventListener(`pointermove`,this.canvasPointerMoveHandler,!0),window.addEventListener(`pointerup`,this.canvasPointerUpHandler,!0),window.addEventListener(`pointercancel`,this.canvasPointerUpHandler,!0))}destroy(){this.graph&&this.canvasPointerDownHandler&&this.graph.off(t.CanvasEvent.POINTER_DOWN,this.canvasPointerDownHandler),this.canvasPointerMoveHandler&&window.removeEventListener(`pointermove`,this.canvasPointerMoveHandler,!0),this.canvasPointerUpHandler&&(window.removeEventListener(`pointerup`,this.canvasPointerUpHandler,!0),window.removeEventListener(`pointercancel`,this.canvasPointerUpHandler,!0)),this.canvasElement=null,this.canvasPointerDownHandler=null,this.canvasPointerMoveHandler=null,this.canvasPointerUpHandler=null,this.canvasPointerId=null,this.isCanvasDragging=!1,this.nodeClickHandler=null,super.destroy()}},a=(0,e.defineComponent)({__name:`GraphVisualization`,props:{nodes:{},edges:{},width:{default:0},height:{default:0},layoutType:{default:`force`}},emits:[`nodeClick`],setup(t,{expose:n,emit:r}){let a=t,o=r,s=(0,e.ref)(null),c=null,l=null,u=null,d=()=>s.value?{width:s.value.clientWidth,height:s.value.clientHeight}:{width:0,height:0},f=()=>{s.value&&(s.value.clientHeight===0&&(s.value.style.height=`100%`),(0,e.nextTick)(()=>{let{width:e,height:t}=d();e!==0&&t!==0&&(c||=new i,c.init({container:s.value,width:a.width||e,height:a.height||t,layoutType:a.layoutType,onNodeClick:e=>{let t=a.nodes.find(t=>t.id===e);t&&o(`nodeClick`,t)}},a.nodes,a.edges))}))},p=()=>{if(!c){f();return}c.update(a.nodes,a.edges)};return n({fitView:()=>{c?.fitView(20)},resetLayout:()=>{c?.resetLayout()},switchLayout:e=>{c?.switchLayout(e)},getLayoutType:()=>c?.getLayoutType()??`force`,focusNode:e=>{c?.focusNode(e)}}),(0,e.watch)(()=>[a.nodes,a.edges],()=>{c?p():f()},{deep:!0}),(0,e.watch)(()=>a.layoutType,e=>{c?.switchLayout(e)}),(0,e.onMounted)(()=>{(a.nodes.length>0||a.edges.length>0)&&f(),l=new ResizeObserver(e=>{for(let t of e){let{width:e,height:n}=t.contentRect;u&&clearTimeout(u),u=setTimeout(()=>{e>0&&n>0&&c?c.resize(e,n):e>0&&n>0&&f()},100)}}),s.value&&(l.observe(s.value),(a.nodes.length>0||a.edges.length>0)&&setTimeout(()=>{c||f()},100))}),(0,e.onUnmounted)(()=>{u&&clearTimeout(u),l?.disconnect(),l=null,c?.destroy(),c=null}),(t,n)=>((0,e.openBlock)(),(0,e.createElementBlock)(`div`,{ref_key:`containerRef`,ref:s,class:`graph-container`},null,512))}}),o=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},s=o(a,[[`__scopeId`,`data-v-232ee924`]]),c=[`title`,`aria-label`,`aria-expanded`],l={class:`kg-btn-group`},u=[`title`],d=[`value`],f=[`label`],p=[`value`],m=o((0,e.defineComponent)({__name:`GraphToolbar`,props:{layoutType:{default:`force`}},emits:[`fitView`,`resetLayout`,`switchLayout`,`switchToForce`,`switchToCircular`],setup(t,{emit:r}){let i=t,a=r,o=(0,e.ref)(!1),s=(0,e.ref)(i.layoutType),m=[{category:`network`,label:`关系网络`},{category:`hierarchy`,label:`层级与树形`}],h=(0,e.computed)(()=>m.map(e=>({...e,options:n.filter(t=>t.category===e.category)})).filter(e=>e.options.length>0)),g=(0,e.computed)(()=>n.find(e=>e.value===s.value)),_=e=>{let t=e.target.value;n.some(e=>e.value===t)&&(s.value=t,a(`switchLayout`,t),t===`force`&&a(`switchToForce`),t===`circular`&&a(`switchToCircular`))};(0,e.watch)(()=>i.layoutType,e=>{s.value=e});let v=()=>{o.value=!o.value};return(t,n)=>((0,e.openBlock)(),(0,e.createElementBlock)(`div`,{class:(0,e.normalizeClass)([`kg-toolbar`,{"kg-collapsed":o.value}])},[(0,e.createElementVNode)(`button`,{onClick:v,class:`kg-btn kg-toggle`,title:o.value?`展开工具栏`:`收起工具栏`,"aria-label":o.value?`展开工具栏`:`收起工具栏`,"aria-expanded":!o.value},(0,e.toDisplayString)(o.value?`▶`:`◀`),9,c),(0,e.withDirectives)((0,e.createElementVNode)(`div`,l,[(0,e.createElementVNode)(`button`,{onClick:n[0]||=e=>a(`fitView`),class:`kg-btn`,title:`适应画布`},[...n[2]||=[(0,e.createElementVNode)(`span`,{class:`kg-icon`},`⊡`,-1),(0,e.createElementVNode)(`span`,{class:`kg-btn-label`},`适应画布`,-1)]]),(0,e.createElementVNode)(`button`,{onClick:n[1]||=e=>a(`resetLayout`),class:`kg-btn`,title:`重置布局`},[...n[3]||=[(0,e.createElementVNode)(`span`,{class:`kg-icon`},`↻`,-1),(0,e.createElementVNode)(`span`,{class:`kg-btn-label`},`重置布局`,-1)]]),(0,e.createElementVNode)(`label`,{class:`kg-layout-control`,title:g.value?.description},[n[4]||=(0,e.createElementVNode)(`span`,{class:`kg-layout-label`},`图类型`,-1),(0,e.createElementVNode)(`select`,{class:`kg-layout-select`,value:s.value,"aria-label":`选择图布局`,onChange:_},[((0,e.openBlock)(!0),(0,e.createElementBlock)(e.Fragment,null,(0,e.renderList)(h.value,t=>((0,e.openBlock)(),(0,e.createElementBlock)(`optgroup`,{key:t.category,label:t.label},[((0,e.openBlock)(!0),(0,e.createElementBlock)(e.Fragment,null,(0,e.renderList)(t.options,t=>((0,e.openBlock)(),(0,e.createElementBlock)(`option`,{key:t.value,value:t.value},(0,e.toDisplayString)(t.label),9,p))),128))],8,f))),128))],40,d)],8,u)],512),[[e.vShow,!o.value]])],2))}}),[[`__scopeId`,`data-v-f8202e9e`]]),h=e=>{e.component(`GraphVisualization`,s),e.component(`GraphToolbar`,m)},g={install:h,version:`0.1.0`};exports.GRAPH_LAYOUT_OPTIONS=n,exports.GraphToolbar=m,exports.GraphVisualization=s,exports.KnowledgeGraph=i,exports.default=g,exports.install=h;
|
|
4
|
+
//# sourceMappingURL=vue-knowledge-graph.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue-knowledge-graph.cjs","names":[],"sources":["../src/module/g6Graph.ts","../src/components/GraphVisualization.vue","../src/components/GraphVisualization.vue","../src/components/GraphToolbar.vue","../src/components/GraphToolbar.vue","../src/index.ts"],"sourcesContent":["import {\n CanvasEvent,\n Graph,\n NodeEvent,\n type Canvas,\n type EdgeData,\n type GraphOptions,\n type IElementEvent,\n type IElementDragEvent,\n type IPointerEvent,\n type NodeData,\n type SingleLayoutOptions,\n} from '@antv/g6'\nimport { nextTick } from 'vue'\nimport type { GraphEdge, GraphNode } from '../types'\n\nexport type LayoutType =\n | 'force'\n | 'circular'\n | 'concentric'\n | 'antv-dagre'\n | 'indented'\n | 'mindmap'\n\nexport type GraphLayoutCategory = 'network' | 'hierarchy'\n\nexport interface GraphLayoutOption {\n value: LayoutType\n label: string\n description: string\n category: GraphLayoutCategory\n}\n\nexport const GRAPH_LAYOUT_OPTIONS: readonly GraphLayoutOption[] = [\n {\n value: 'force',\n label: '力导向',\n description: '通用网络布局,适合结构未知或关系密集的图谱',\n category: 'network',\n },\n {\n value: 'circular',\n label: '环形',\n description: '节点沿圆环排列,适合观察整体结构',\n category: 'network',\n },\n {\n value: 'concentric',\n label: '同心圆',\n description: '按连接度分层,突出核心节点和外围节点',\n category: 'network',\n },\n {\n value: 'antv-dagre',\n label: '有向层级',\n description: '按边的方向排列层级,适合 DAG 或流程数据',\n category: 'hierarchy',\n },\n {\n value: 'indented',\n label: '缩进树',\n description: '目录式层级布局,适合近树结构',\n category: 'hierarchy',\n },\n {\n value: 'mindmap',\n label: '思维导图',\n description: '从中心向两侧分支,适合近树结构',\n category: 'hierarchy',\n },\n]\n\nexport interface GraphInitOptions {\n container: HTMLElement\n width?: number\n height?: number\n layoutType?: LayoutType\n onNodeClick?: (nodeId: string) => void\n}\n\n/** 负责 G6 实例的生命周期和通用视口操作。 */\nexport class BaseG6Graph {\n protected graph: Graph | null = null\n protected container: HTMLElement | null = null\n\n private resizeHandler: (() => void) | null = null\n\n protected createGraph(\n config: Omit<GraphOptions, 'container' | 'width' | 'height'>,\n options: GraphInitOptions,\n ) {\n const { container, width, height } = options\n\n if (this.graph) this.destroy()\n\n this.container = container\n this.graph = new Graph({\n container,\n width: width || container.offsetWidth,\n height: height || container.offsetHeight,\n ...config,\n })\n this.setupResizeHandler()\n }\n\n private setupResizeHandler() {\n this.cleanupResizeHandler()\n this.resizeHandler = () => {\n if (!this.container) return\n this.resize(this.container.offsetWidth, this.container.offsetHeight)\n }\n window.addEventListener('resize', this.resizeHandler)\n }\n\n private cleanupResizeHandler() {\n if (!this.resizeHandler) return\n window.removeEventListener('resize', this.resizeHandler)\n this.resizeHandler = null\n }\n\n protected run(action: Promise<unknown>) {\n void action.catch(() => undefined)\n }\n\n public hasGraph(): boolean {\n return this.graph !== null\n }\n\n public resize(width: number, height: number) {\n if (!this.graph || width <= 0 || height <= 0) return\n const [currentWidth, currentHeight] = this.graph.getSize()\n if (currentWidth === width && currentHeight === height) return\n this.graph.resize(width, height)\n }\n\n public fitView(_padding = 20) {\n if (this.graph) this.run(this.graph.fitView({ when: 'always' }, false))\n }\n\n public destroy() {\n this.cleanupResizeHandler()\n this.graph?.destroy()\n this.graph = null\n this.container = null\n }\n}\n\n/** 知识图谱实现:实体/关系配色、布局切换和图谱交互。 */\nexport class KnowledgeGraph extends BaseG6Graph {\n private currentLayoutType: LayoutType = 'force'\n private nodeClickHandler: ((nodeId: string) => void) | null = null\n private canvasElement: HTMLCanvasElement | null = null\n private canvasPointerDownHandler: ((event: IPointerEvent) => void) | null = null\n private canvasPointerMoveHandler: ((event: PointerEvent) => void) | null = null\n private canvasPointerUpHandler: ((event: PointerEvent) => void) | null = null\n private canvasPointerId: number | null = null\n private lastCanvasPoint = { x: 0, y: 0 }\n private isCanvasDragging = false\n private canvasDefaultCursor: NonNullable<ReturnType<Canvas['getConfig']>['cursor']> = 'default'\n\n private getLayoutConfig(\n type: LayoutType,\n width: number,\n height: number,\n ): SingleLayoutOptions {\n const center: [number, number] = [width / 2, height / 2]\n const getNodeWidth = (node: { label?: string }) =>\n Math.min(Math.max((node.label?.length ?? 4) * 14 + 28, 72), 220)\n const getNodeHeight = () => 42\n\n switch (type) {\n case 'circular':\n return {\n type,\n center,\n radius: Math.min(width, height) * 0.35,\n startAngle: Math.PI / 2,\n endAngle: Math.PI * 2.5,\n ordering: 'degree',\n }\n case 'concentric':\n return {\n type,\n center,\n preventOverlap: true,\n nodeSize: 42,\n sortBy: 'degree',\n equidistant: true,\n }\n case 'antv-dagre':\n return {\n type,\n rankdir: 'TB',\n align: 'UL',\n nodesep: 42,\n ranksep: 110,\n }\n case 'indented':\n return {\n type,\n direction: 'LR',\n indent: 36,\n getWidth: getNodeWidth,\n getHeight: getNodeHeight,\n getHGap: () => 18,\n getVGap: () => 12,\n }\n case 'mindmap':\n return {\n type,\n direction: 'H',\n preLayout: false,\n getWidth: getNodeWidth,\n getHeight: getNodeHeight,\n getHGap: () => 30,\n getVGap: () => 18,\n }\n case 'force':\n default:\n return {\n type: 'force',\n center,\n linkDistance: 280,\n nodeStrength: 1000,\n edgeStrength: 200,\n gravity: 10,\n }\n }\n }\n\n private getColorByType(entityType?: string): string {\n const colors: Record<string, string> = {\n 人物: '#ff4d4f',\n 地点: '#52c41a',\n 组织: '#1890ff',\n 概念: '#faad14',\n 事件: '#722ed1',\n entity: '#13c2c2',\n }\n return colors[entityType || 'entity'] || '#13c2c2'\n }\n\n private getColorByRelation(relationType?: string): string {\n const colors: Record<string, string> = {\n 因果: '#e74c3c',\n 所属: '#2ecc71',\n 引用: '#3498db',\n 相关: '#9b59b6',\n }\n return colors[relationType || '相关'] || '#9b59b6'\n }\n\n public init(options: GraphInitOptions, nodes: GraphNode[], edges: GraphEdge[]) {\n const width = options.width || options.container.offsetWidth\n const height = options.height || options.container.offsetHeight\n const data = this.processData(nodes, edges)\n\n this.currentLayoutType = options.layoutType ?? 'force'\n this.createGraph(\n {\n data,\n animation: false,\n zoomRange: [0.1, 5],\n layout: this.getLayoutConfig(this.currentLayoutType, width, height),\n behaviors: [\n { type: 'zoom-canvas', sensitivity: 1 },\n { type: 'drag-element' },\n { type: 'hover-activate', degree: 1 },\n { type: 'click-select', degree: 1 },\n ],\n node: {\n type: 'circle',\n style: {\n cursor: 'pointer',\n labelPlacement: 'bottom',\n labelFill: '#000000',\n labelFontSize: 16,\n labelFontWeight: 'bold',\n },\n state: {\n active: { lineWidth: 3, shadowBlur: 12, shadowColor: '#40a9ff' },\n selected: { lineWidth: 4, stroke: '#ff4d4f' },\n inactive: { opacity: 0.25, labelOpacity: 0.35 },\n },\n },\n edge: {\n type: 'line',\n style: {\n cursor: 'pointer',\n endArrow: true,\n labelAutoRotate: true,\n labelFontSize: 12,\n },\n state: {\n active: { lineWidth: 3 },\n selected: { lineWidth: 3, stroke: '#ff4d4f' },\n inactive: { opacity: 0.2, labelOpacity: 0.2 },\n },\n },\n },\n options,\n )\n\n if (!this.graph) return\n this.nodeClickHandler = options.onNodeClick ?? null\n this.setupEvents()\n this.run(this.graph.render().then(() => {\n this.setupCanvasDrag()\n this.scheduleFitView()\n }))\n }\n\n public update(nodes: GraphNode[], edges: GraphEdge[]) {\n if (!this.graph) return\n this.graph.setData(this.processData(nodes, edges))\n this.run(this.graph.render().then(() => this.scheduleFitView()))\n }\n\n public resetLayout() {\n if (!this.graph) return\n\n const nodes = this.graph.getNodeData().map((node) => {\n const style = { ...node.style }\n delete style.x\n delete style.y\n return { ...node, style }\n })\n this.graph.setData({ nodes, edges: this.graph.getEdgeData() })\n this.graph.setLayout(this.getCurrentLayout())\n this.run(this.graph.layout().then(() => this.scheduleFitView()))\n }\n\n public switchLayout(type: LayoutType) {\n if (!this.graph || !this.container) return\n this.currentLayoutType = type\n this.graph.setLayout(this.getCurrentLayout())\n this.run(this.graph.layout().then(() => this.scheduleFitView()))\n }\n\n public getLayoutType(): LayoutType {\n return this.currentLayoutType\n }\n\n public focusNode(nodeId: string) {\n if (!this.graph || !this.graph.getNodeData().some(({ id }) => id === nodeId)) return\n this.run(this.graph.focusElement(nodeId, { duration: 500, easing: 'ease-in-out' }))\n }\n\n private getCurrentLayout() {\n const width = this.container?.offsetWidth || this.graph?.getSize()[0] || 0\n const height = this.container?.offsetHeight || this.graph?.getSize()[1] || 0\n return this.getLayoutConfig(this.currentLayoutType, width, height)\n }\n\n private processData(nodes: GraphNode[], edges: GraphEdge[]): {\n nodes: NodeData[]\n edges: EdgeData[]\n } {\n const degree: Record<string, number> = {}\n for (const edge of edges) {\n degree[edge.source] = (degree[edge.source] || 0) + 1\n degree[edge.target] = (degree[edge.target] || 0) + 1\n }\n\n return {\n nodes: nodes.map((node) => ({\n id: node.id,\n data: {\n ...node,\n label: node.label,\n entityType: node.type,\n },\n style: {\n x: node.x,\n y: node.y,\n size: node.size || Math.min(15 + (degree[node.id] || 0) * 3, 40),\n fill: node.style?.fill || this.getColorByType(node.type),\n stroke: node.style?.stroke || '#ffffff',\n lineWidth: node.style?.lineWidth || 2,\n labelText: node.label,\n },\n })),\n edges: edges.map((edge, index) => {\n const color = edge.style?.stroke || this.getColorByRelation(edge.relation_type)\n return {\n id: edge.id || `edge-${edge.source}-${edge.target}-${index}`,\n source: edge.source,\n target: edge.target,\n data: { label: edge.label, relationType: edge.relation_type },\n style: {\n stroke: color,\n lineWidth: edge.style?.lineWidth || 2,\n endArrowFill: color,\n labelText: edge.label,\n labelFill: color,\n },\n }\n }),\n }\n }\n\n private scheduleFitView() {\n const fit = () => {\n nextTick(() => {\n requestAnimationFrame(() => this.fitView(20))\n })\n }\n fit()\n window.setTimeout(fit, 300)\n window.setTimeout(fit, 1200)\n }\n\n private setupEvents() {\n if (!this.graph || !this.container) return\n const graph = this.graph\n\n graph.on(NodeEvent.CLICK, (event: IElementEvent) => {\n this.nodeClickHandler?.(event.target.id)\n })\n\n graph.on(NodeEvent.DRAG, (event: IElementDragEvent) => {\n const nodeId = event.target.id\n const factor = 0.3\n const updates = graph.getNeighborNodesData(nodeId).map((node) => ({\n id: node.id,\n style: {\n x: (node.style?.x || 0) + event.dx * factor,\n y: (node.style?.y || 0) + event.dy * factor,\n },\n }))\n if (updates.length === 0) return\n graph.updateNodeData(updates)\n this.run(graph.draw())\n })\n }\n\n private setupCanvasDrag() {\n if (!this.graph) return\n const graph = this.graph\n const canvas = graph.getCanvas()\n const canvasElement = canvas.getContextService().getDomElement() as HTMLCanvasElement | null\n if (!canvasElement) return\n\n this.canvasElement = canvasElement\n this.canvasDefaultCursor = canvas.getConfig().cursor || 'default'\n this.canvasPointerDownHandler = (event) => {\n if (event.button !== 0 || this.canvasPointerId !== null) return\n\n this.canvasPointerId = event.pointerId\n this.isCanvasDragging = true\n this.lastCanvasPoint = { x: event.clientX, y: event.clientY }\n canvasElement.setPointerCapture(event.pointerId)\n canvas.setCursor('grabbing')\n }\n this.canvasPointerMoveHandler = (event) => {\n if (\n !this.isCanvasDragging ||\n (this.canvasPointerId !== null && event.pointerId !== this.canvasPointerId)\n ) {\n return\n }\n\n const offset: [number, number] = [\n event.clientX - this.lastCanvasPoint.x,\n event.clientY - this.lastCanvasPoint.y,\n ]\n this.lastCanvasPoint = { x: event.clientX, y: event.clientY }\n this.run(graph.translateBy(offset, false))\n event.preventDefault?.()\n }\n this.canvasPointerUpHandler = (event) => {\n if (this.canvasPointerId !== null && event.pointerId !== this.canvasPointerId) {\n return\n }\n\n const wasDragging = this.isCanvasDragging\n this.isCanvasDragging = false\n this.canvasPointerId = null\n if (wasDragging && canvasElement.hasPointerCapture?.(event.pointerId)) {\n canvasElement.releasePointerCapture(event.pointerId)\n }\n if (wasDragging) canvas.setCursor(this.canvasDefaultCursor)\n }\n\n graph.on(CanvasEvent.POINTER_DOWN, this.canvasPointerDownHandler)\n window.addEventListener('pointermove', this.canvasPointerMoveHandler, true)\n window.addEventListener('pointerup', this.canvasPointerUpHandler, true)\n window.addEventListener('pointercancel', this.canvasPointerUpHandler, true)\n }\n\n public override destroy() {\n if (this.graph && this.canvasPointerDownHandler) {\n this.graph.off(CanvasEvent.POINTER_DOWN, this.canvasPointerDownHandler)\n }\n if (this.canvasPointerMoveHandler) {\n window.removeEventListener('pointermove', this.canvasPointerMoveHandler, true)\n }\n if (this.canvasPointerUpHandler) {\n window.removeEventListener('pointerup', this.canvasPointerUpHandler, true)\n window.removeEventListener('pointercancel', this.canvasPointerUpHandler, true)\n }\n\n this.canvasElement = null\n this.canvasPointerDownHandler = null\n this.canvasPointerMoveHandler = null\n this.canvasPointerUpHandler = null\n this.canvasPointerId = null\n this.isCanvasDragging = false\n this.nodeClickHandler = null\n super.destroy()\n }\n}\n","<template>\n <div\n ref=\"containerRef\"\n class=\"graph-container\"\n ></div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { GraphNode, GraphEdge } from '../types'\nimport { KnowledgeGraph, type LayoutType } from '../module/g6Graph'\n\ninterface Props {\n nodes: GraphNode[]\n edges: GraphEdge[]\n width?: number\n height?: number\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 0,\n height: 0,\n layoutType: 'force',\n})\n\nconst emit = defineEmits<{\n nodeClick: [node: GraphNode]\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nlet graph: KnowledgeGraph | null = null\nlet resizeObserver: ResizeObserver | null = null\nlet resizeTimeout: ReturnType<typeof setTimeout> | null = null\n\nconst getContainerSize = () => {\n if (!containerRef.value) return { width: 0, height: 0 }\n return {\n width: containerRef.value.clientWidth,\n height: containerRef.value.clientHeight\n }\n}\n\nconst initGraph = () => {\n if (!containerRef.value) return\n\n // 确保容器有高度\n if (containerRef.value.clientHeight === 0) {\n containerRef.value.style.height = '100%'\n }\n\n nextTick(() => {\n const { width, height } = getContainerSize()\n if (width === 0 || height === 0) {\n return\n }\n\n if (!graph) {\n graph = new KnowledgeGraph()\n }\n\n graph.init(\n {\n container: containerRef.value!,\n width: props.width || width,\n height: props.height || height,\n layoutType: props.layoutType,\n onNodeClick: (nodeId) => {\n const node = props.nodes.find((item) => item.id === nodeId)\n if (node) emit('nodeClick', node)\n },\n },\n props.nodes,\n props.edges\n )\n })\n}\n\nconst updateGraph = () => {\n if (!graph) {\n initGraph()\n return\n }\n\n graph.update(props.nodes, props.edges)\n}\n\nconst fitView = () => {\n graph?.fitView(20)\n}\n\nconst resetLayout = () => {\n graph?.resetLayout()\n}\n\nconst switchLayout = (type: LayoutType) => {\n graph?.switchLayout(type)\n}\n\nconst getLayoutType = () => graph?.getLayoutType() ?? 'force'\n\nconst focusNode = (nodeId: string) => {\n graph?.focusNode(nodeId)\n}\n\ndefineExpose({\n fitView,\n resetLayout,\n switchLayout,\n getLayoutType,\n focusNode\n})\n\nwatch(\n () => [props.nodes, props.edges],\n () => {\n if (graph) {\n updateGraph()\n } else {\n initGraph()\n }\n },\n { deep: true }\n)\n\nwatch(\n () => props.layoutType,\n (type) => {\n graph?.switchLayout(type)\n }\n)\n\nonMounted(() => {\n if (props.nodes.length > 0 || props.edges.length > 0) {\n initGraph()\n }\n\n // 使用 ResizeObserver 监听容器尺寸变化\n resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n // 防止 resize 循环:使用防抖\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeTimeout = setTimeout(() => {\n if (width > 0 && height > 0 && graph) {\n graph.resize(width, height)\n } else if (width > 0 && height > 0) {\n // 首次初始化\n initGraph()\n }\n }, 100)\n }\n })\n\n if (containerRef.value) {\n resizeObserver.observe(containerRef.value)\n\n // 如果数据已存在但尺寸为 0,延迟尝试初始化\n if ((props.nodes.length > 0 || props.edges.length > 0)) {\n setTimeout(() => {\n if (!graph) initGraph()\n }, 100)\n }\n }\n})\n\nonUnmounted(() => {\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeObserver?.disconnect()\n resizeObserver = null\n graph?.destroy()\n graph = null\n})\n</script>\n\n<style scoped>\n.graph-container {\n width: 100%;\n height: 100%;\n}\n</style>\n","<template>\n <div\n ref=\"containerRef\"\n class=\"graph-container\"\n ></div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { GraphNode, GraphEdge } from '../types'\nimport { KnowledgeGraph, type LayoutType } from '../module/g6Graph'\n\ninterface Props {\n nodes: GraphNode[]\n edges: GraphEdge[]\n width?: number\n height?: number\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 0,\n height: 0,\n layoutType: 'force',\n})\n\nconst emit = defineEmits<{\n nodeClick: [node: GraphNode]\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nlet graph: KnowledgeGraph | null = null\nlet resizeObserver: ResizeObserver | null = null\nlet resizeTimeout: ReturnType<typeof setTimeout> | null = null\n\nconst getContainerSize = () => {\n if (!containerRef.value) return { width: 0, height: 0 }\n return {\n width: containerRef.value.clientWidth,\n height: containerRef.value.clientHeight\n }\n}\n\nconst initGraph = () => {\n if (!containerRef.value) return\n\n // 确保容器有高度\n if (containerRef.value.clientHeight === 0) {\n containerRef.value.style.height = '100%'\n }\n\n nextTick(() => {\n const { width, height } = getContainerSize()\n if (width === 0 || height === 0) {\n return\n }\n\n if (!graph) {\n graph = new KnowledgeGraph()\n }\n\n graph.init(\n {\n container: containerRef.value!,\n width: props.width || width,\n height: props.height || height,\n layoutType: props.layoutType,\n onNodeClick: (nodeId) => {\n const node = props.nodes.find((item) => item.id === nodeId)\n if (node) emit('nodeClick', node)\n },\n },\n props.nodes,\n props.edges\n )\n })\n}\n\nconst updateGraph = () => {\n if (!graph) {\n initGraph()\n return\n }\n\n graph.update(props.nodes, props.edges)\n}\n\nconst fitView = () => {\n graph?.fitView(20)\n}\n\nconst resetLayout = () => {\n graph?.resetLayout()\n}\n\nconst switchLayout = (type: LayoutType) => {\n graph?.switchLayout(type)\n}\n\nconst getLayoutType = () => graph?.getLayoutType() ?? 'force'\n\nconst focusNode = (nodeId: string) => {\n graph?.focusNode(nodeId)\n}\n\ndefineExpose({\n fitView,\n resetLayout,\n switchLayout,\n getLayoutType,\n focusNode\n})\n\nwatch(\n () => [props.nodes, props.edges],\n () => {\n if (graph) {\n updateGraph()\n } else {\n initGraph()\n }\n },\n { deep: true }\n)\n\nwatch(\n () => props.layoutType,\n (type) => {\n graph?.switchLayout(type)\n }\n)\n\nonMounted(() => {\n if (props.nodes.length > 0 || props.edges.length > 0) {\n initGraph()\n }\n\n // 使用 ResizeObserver 监听容器尺寸变化\n resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n // 防止 resize 循环:使用防抖\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeTimeout = setTimeout(() => {\n if (width > 0 && height > 0 && graph) {\n graph.resize(width, height)\n } else if (width > 0 && height > 0) {\n // 首次初始化\n initGraph()\n }\n }, 100)\n }\n })\n\n if (containerRef.value) {\n resizeObserver.observe(containerRef.value)\n\n // 如果数据已存在但尺寸为 0,延迟尝试初始化\n if ((props.nodes.length > 0 || props.edges.length > 0)) {\n setTimeout(() => {\n if (!graph) initGraph()\n }, 100)\n }\n }\n})\n\nonUnmounted(() => {\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeObserver?.disconnect()\n resizeObserver = null\n graph?.destroy()\n graph = null\n})\n</script>\n\n<style scoped>\n.graph-container {\n width: 100%;\n height: 100%;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n GRAPH_LAYOUT_OPTIONS,\n type GraphLayoutCategory,\n type LayoutType,\n} from '../module/g6Graph'\n\ninterface Props {\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n layoutType: 'force'\n})\n\nconst emit = defineEmits<{\n fitView: []\n resetLayout: []\n switchLayout: [type: LayoutType]\n switchToForce: []\n switchToCircular: []\n}>()\n\nconst collapsed = ref(false)\nconst selectedLayout = ref<LayoutType>(props.layoutType)\n\nconst layoutGroups: Array<{ category: GraphLayoutCategory; label: string }> = [\n { category: 'network', label: '关系网络' },\n { category: 'hierarchy', label: '层级与树形' },\n]\n\nconst groupedLayouts = computed(() =>\n layoutGroups\n .map((group) => ({\n ...group,\n options: GRAPH_LAYOUT_OPTIONS.filter((layout) => layout.category === group.category),\n }))\n .filter((group) => group.options.length > 0)\n)\n\nconst currentLayout = computed(\n () => GRAPH_LAYOUT_OPTIONS.find((layout) => layout.value === selectedLayout.value)\n)\n\nconst handleLayoutChange = (event: Event) => {\n const type = (event.target as HTMLSelectElement).value as LayoutType\n if (!GRAPH_LAYOUT_OPTIONS.some((layout) => layout.value === type)) return\n\n selectedLayout.value = type\n emit('switchLayout', type)\n if (type === 'force') emit('switchToForce')\n if (type === 'circular') emit('switchToCircular')\n}\n\nwatch(\n () => props.layoutType,\n (type) => {\n selectedLayout.value = type\n }\n)\n\nconst toggleCollapse = () => {\n collapsed.value = !collapsed.value\n}\n</script>\n\n<template>\n <div\n class=\"kg-toolbar\"\n :class=\"{ 'kg-collapsed': collapsed }\"\n >\n <button\n @click=\"toggleCollapse\"\n class=\"kg-btn kg-toggle\"\n :title=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-label=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-expanded=\"!collapsed\"\n >\n {{ collapsed ? '▶' : '◀' }}\n </button>\n <div\n class=\"kg-btn-group\"\n v-show=\"!collapsed\"\n >\n <button\n @click=\"emit('fitView')\"\n class=\"kg-btn\"\n title=\"适应画布\"\n >\n <span class=\"kg-icon\">⊡</span>\n <span class=\"kg-btn-label\">适应画布</span>\n </button>\n <button\n @click=\"emit('resetLayout')\"\n class=\"kg-btn\"\n title=\"重置布局\"\n >\n <span class=\"kg-icon\">↻</span>\n <span class=\"kg-btn-label\">重置布局</span>\n </button>\n <label\n class=\"kg-layout-control\"\n :title=\"currentLayout?.description\"\n >\n <span class=\"kg-layout-label\">图类型</span>\n <select\n class=\"kg-layout-select\"\n :value=\"selectedLayout\"\n aria-label=\"选择图布局\"\n @change=\"handleLayoutChange\"\n >\n <optgroup\n v-for=\"group in groupedLayouts\"\n :key=\"group.category\"\n :label=\"group.label\"\n >\n <option\n v-for=\"layout in group.options\"\n :key=\"layout.value\"\n :value=\"layout.value\"\n >\n {{ layout.label }}\n </option>\n </optgroup>\n </select>\n </label>\n </div>\n </div>\n</template>\n\n<style scoped>\n.kg-toolbar {\n position: absolute;\n top: 16px;\n right: 16px;\n background: #fff;\n border: 1px solid #e2e8f0;\n border-radius: 10px;\n padding: 8px;\n z-index: 100;\n display: flex;\n gap: 6px;\n align-items: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n font-family: 'Segoe UI', 'PingFang SC', system-ui, sans-serif;\n}\n\n.kg-toolbar:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n}\n\n.kg-toolbar.kg-collapsed {\n padding: 6px;\n right: 12px;\n}\n\n.kg-btn-group {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.kg-btn {\n padding: 6px 12px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: #334155;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 5px;\n outline: none;\n}\n\n.kg-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n.kg-btn:focus-visible,\n.kg-toggle:focus-visible {\n outline: 2px solid #60a5fa;\n outline-offset: 2px;\n}\n\n.kg-icon {\n font-size: 13px;\n line-height: 1;\n}\n\n.kg-layout-control {\n display: flex;\n align-items: center;\n gap: 7px;\n padding-left: 10px;\n border-left: 1px solid #e2e8f0;\n}\n\n.kg-layout-label {\n color: #64748b;\n font-size: 11px;\n font-weight: 650;\n white-space: nowrap;\n}\n\n.kg-layout-select {\n width: 128px;\n min-height: 31px;\n padding: 5px 26px 5px 9px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n background: #fff;\n color: #1e293b;\n font-size: 12px;\n outline: none;\n}\n\n.kg-layout-select:hover,\n.kg-layout-select:focus {\n border-color: #60a5fa;\n box-shadow: 0 0 0 2px #dbeafe;\n}\n\n.kg-toggle {\n padding: 4px 8px;\n background: transparent;\n color: #64748b;\n border: none;\n border-radius: 4px;\n font-size: 11px;\n}\n\n.kg-toggle:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n@media (max-width: 640px) {\n .kg-toolbar {\n max-width: calc(100% - 24px);\n }\n\n .kg-btn-group {\n gap: 4px;\n }\n\n .kg-btn {\n padding: 6px 8px;\n }\n\n .kg-btn-label {\n display: none;\n }\n\n .kg-layout-control {\n padding-left: 6px;\n gap: 4px;\n }\n\n .kg-layout-label {\n display: none;\n }\n\n .kg-layout-select {\n width: 120px;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n GRAPH_LAYOUT_OPTIONS,\n type GraphLayoutCategory,\n type LayoutType,\n} from '../module/g6Graph'\n\ninterface Props {\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n layoutType: 'force'\n})\n\nconst emit = defineEmits<{\n fitView: []\n resetLayout: []\n switchLayout: [type: LayoutType]\n switchToForce: []\n switchToCircular: []\n}>()\n\nconst collapsed = ref(false)\nconst selectedLayout = ref<LayoutType>(props.layoutType)\n\nconst layoutGroups: Array<{ category: GraphLayoutCategory; label: string }> = [\n { category: 'network', label: '关系网络' },\n { category: 'hierarchy', label: '层级与树形' },\n]\n\nconst groupedLayouts = computed(() =>\n layoutGroups\n .map((group) => ({\n ...group,\n options: GRAPH_LAYOUT_OPTIONS.filter((layout) => layout.category === group.category),\n }))\n .filter((group) => group.options.length > 0)\n)\n\nconst currentLayout = computed(\n () => GRAPH_LAYOUT_OPTIONS.find((layout) => layout.value === selectedLayout.value)\n)\n\nconst handleLayoutChange = (event: Event) => {\n const type = (event.target as HTMLSelectElement).value as LayoutType\n if (!GRAPH_LAYOUT_OPTIONS.some((layout) => layout.value === type)) return\n\n selectedLayout.value = type\n emit('switchLayout', type)\n if (type === 'force') emit('switchToForce')\n if (type === 'circular') emit('switchToCircular')\n}\n\nwatch(\n () => props.layoutType,\n (type) => {\n selectedLayout.value = type\n }\n)\n\nconst toggleCollapse = () => {\n collapsed.value = !collapsed.value\n}\n</script>\n\n<template>\n <div\n class=\"kg-toolbar\"\n :class=\"{ 'kg-collapsed': collapsed }\"\n >\n <button\n @click=\"toggleCollapse\"\n class=\"kg-btn kg-toggle\"\n :title=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-label=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-expanded=\"!collapsed\"\n >\n {{ collapsed ? '▶' : '◀' }}\n </button>\n <div\n class=\"kg-btn-group\"\n v-show=\"!collapsed\"\n >\n <button\n @click=\"emit('fitView')\"\n class=\"kg-btn\"\n title=\"适应画布\"\n >\n <span class=\"kg-icon\">⊡</span>\n <span class=\"kg-btn-label\">适应画布</span>\n </button>\n <button\n @click=\"emit('resetLayout')\"\n class=\"kg-btn\"\n title=\"重置布局\"\n >\n <span class=\"kg-icon\">↻</span>\n <span class=\"kg-btn-label\">重置布局</span>\n </button>\n <label\n class=\"kg-layout-control\"\n :title=\"currentLayout?.description\"\n >\n <span class=\"kg-layout-label\">图类型</span>\n <select\n class=\"kg-layout-select\"\n :value=\"selectedLayout\"\n aria-label=\"选择图布局\"\n @change=\"handleLayoutChange\"\n >\n <optgroup\n v-for=\"group in groupedLayouts\"\n :key=\"group.category\"\n :label=\"group.label\"\n >\n <option\n v-for=\"layout in group.options\"\n :key=\"layout.value\"\n :value=\"layout.value\"\n >\n {{ layout.label }}\n </option>\n </optgroup>\n </select>\n </label>\n </div>\n </div>\n</template>\n\n<style scoped>\n.kg-toolbar {\n position: absolute;\n top: 16px;\n right: 16px;\n background: #fff;\n border: 1px solid #e2e8f0;\n border-radius: 10px;\n padding: 8px;\n z-index: 100;\n display: flex;\n gap: 6px;\n align-items: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n font-family: 'Segoe UI', 'PingFang SC', system-ui, sans-serif;\n}\n\n.kg-toolbar:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n}\n\n.kg-toolbar.kg-collapsed {\n padding: 6px;\n right: 12px;\n}\n\n.kg-btn-group {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.kg-btn {\n padding: 6px 12px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: #334155;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 5px;\n outline: none;\n}\n\n.kg-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n.kg-btn:focus-visible,\n.kg-toggle:focus-visible {\n outline: 2px solid #60a5fa;\n outline-offset: 2px;\n}\n\n.kg-icon {\n font-size: 13px;\n line-height: 1;\n}\n\n.kg-layout-control {\n display: flex;\n align-items: center;\n gap: 7px;\n padding-left: 10px;\n border-left: 1px solid #e2e8f0;\n}\n\n.kg-layout-label {\n color: #64748b;\n font-size: 11px;\n font-weight: 650;\n white-space: nowrap;\n}\n\n.kg-layout-select {\n width: 128px;\n min-height: 31px;\n padding: 5px 26px 5px 9px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n background: #fff;\n color: #1e293b;\n font-size: 12px;\n outline: none;\n}\n\n.kg-layout-select:hover,\n.kg-layout-select:focus {\n border-color: #60a5fa;\n box-shadow: 0 0 0 2px #dbeafe;\n}\n\n.kg-toggle {\n padding: 4px 8px;\n background: transparent;\n color: #64748b;\n border: none;\n border-radius: 4px;\n font-size: 11px;\n}\n\n.kg-toggle:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n@media (max-width: 640px) {\n .kg-toolbar {\n max-width: calc(100% - 24px);\n }\n\n .kg-btn-group {\n gap: 4px;\n }\n\n .kg-btn {\n padding: 6px 8px;\n }\n\n .kg-btn-label {\n display: none;\n }\n\n .kg-layout-control {\n padding-left: 6px;\n gap: 4px;\n }\n\n .kg-layout-label {\n display: none;\n }\n\n .kg-layout-select {\n width: 120px;\n }\n}\n</style>\n","import type { App } from 'vue'\r\nimport GraphVisualization from './components/GraphVisualization.vue'\r\nimport GraphToolbar from './components/GraphToolbar.vue'\r\nimport { GRAPH_LAYOUT_OPTIONS, KnowledgeGraph } from './module/g6Graph'\r\n\r\n// 导出类型\r\nexport type { GraphNode, GraphEdge } from './types'\r\nexport type { GraphInitOptions } from './module/g6Graph'\r\nexport type {\r\n GraphLayoutCategory,\r\n GraphLayoutOption,\r\n LayoutType,\r\n} from './module/g6Graph'\r\n\r\n// Vue 插件 install 方法\r\nconst install = (app: App) => {\r\n app.component('GraphVisualization', GraphVisualization)\r\n app.component('GraphToolbar', GraphToolbar)\r\n}\r\n\r\n// 具名导出(ES Modules)\r\nexport {\r\n install,\r\n GRAPH_LAYOUT_OPTIONS,\r\n GraphVisualization,\r\n GraphToolbar,\r\n KnowledgeGraph,\r\n}\r\n\r\n// 自动导出(UMD)- 默认导出\r\nexport default {\r\n install,\r\n version: __PACKAGE_VERSION__,\r\n}\r\n"],"mappings":"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"}
|