parent
b8bccf0e45
commit
755a8d9522
43 changed files with 3891 additions and 73 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,99 @@ |
||||
{ |
||||
"nodes": [ |
||||
{ "id": "node1", "label": "计算机", "color": "#4682B4" }, |
||||
{ "id": "node2", "label": "前端", "color": "rgba(254, 241, 0, 1)" }, |
||||
{ "id": "node3", "label": "js", "color": "rgba(239, 242, 18, 1)" }, |
||||
{ "id": "node4", "label": "html", "color": "rgba(230, 234, 10, 1)" }, |
||||
{ "id": "node5", "label": "css", "color": "rgba(244, 231, 0, 1)" }, |
||||
{ "id": "node6", "label": "less", "color": "rgba(15, 245, 57, 1)" }, |
||||
{ "id": "node7", "label": "scss", "color": "rgba(133, 255, 11, 1)" }, |
||||
{ "id": "node8", "label": "VUE", "color": "rgba(42, 255, 0, 1)" }, |
||||
{ "id": "node9", "label": "React", "color": "rgba(76, 73, 245, 1)" }, |
||||
{ "id": "node10", "label": "模块化", "color": "#4682B4" }, |
||||
{ "id": "node11", "label": "webpack", "color": "#4682B4" }, |
||||
{ "id": "node12", "label": "vite", "color": "#4682B4" }, |
||||
{ "id": "node13", "label": "uniapp", "color": "rgba(77, 255, 0, 1)" }, |
||||
{ "id": "node14", "label": "element", "color": "rgba(33, 162, 255, 1)" }, |
||||
{ "id": "node15", "label": "web3", "color": "rgba(255, 0, 251, 1)" }, |
||||
{ "id": "node16", "label": "webGl", "color": "rgba(208, 0, 249, 1)" }, |
||||
{ "id": "node17", "label": "three", "color": "rgba(225, 0, 255, 1)" }, |
||||
{ "id": "node18", "label": "后端", "color": "rgba(0, 229, 255, 1)" }, |
||||
{ "id": "node19", "label": "java", "color": "rgba(237, 229, 85, 1)" }, |
||||
{ "id": "node20", "label": "PHP", "color": "rgba(195, 206, 215, 1)" }, |
||||
{ "id": "node21", "label": "Go", "color": "rgba(255, 0, 0, 1)" }, |
||||
{ "id": "node22", "label": "Python", "color": "rgba(109, 238, 180, 1)" }, |
||||
{ "id": "node23", "label": "MySQL", "color": "#4682B4" }, |
||||
{ "id": "node24", "label": "人工智能", "color": "rgba(180, 5, 255, 1)" }, |
||||
{ "id": "node25", "label": "python", "color": "rgba(255, 8, 8, 1)" }, |
||||
{ "id": "node26", "label": "AI模型", "color": "rgba(10, 138, 244, 1)" }, |
||||
{ |
||||
"id": "node27", |
||||
"label": "Spring Framework", |
||||
"color": "rgba(242, 238, 14, 1)" |
||||
}, |
||||
{ "id": "node29", "label": "Hibernate", "color": "rgba(242, 238, 14, 1)" }, |
||||
{ "id": "node31", "label": "Spring MVC", "color": "rgba(242, 238, 14, 1)" }, |
||||
{ "id": "node33", "label": "Gin", "color": "rgba(255, 0, 0, 1)" }, |
||||
{ "id": "node34", "label": "Echo", "color": "rgba(255, 0, 0, 1)" }, |
||||
{ "id": "node35", "label": "Beego", "color": "rgba(255, 8, 0, 1)" }, |
||||
{ "id": "node36", "label": "Laravel", "color": "rgba(200, 209, 217, 1)" }, |
||||
{ "id": "node37", "label": "Symfony", "color": "rgba(182, 194, 204, 1)" }, |
||||
{ |
||||
"id": "node38", |
||||
"label": "CodeIgniter", |
||||
"color": "rgba(188, 197, 204, 1)" |
||||
}, |
||||
{ "id": "node39", "label": "Django", "color": "rgba(36, 245, 144, 1)" }, |
||||
{ "id": "node40", "label": "Flask", "color": "rgba(41, 244, 176, 1)" }, |
||||
{ "id": "node41", "label": "FastAPI", "color": "rgba(58, 244, 142, 1)" } |
||||
], |
||||
"links": [ |
||||
{ "source": "node2", "target": "node3", "label": "" }, |
||||
{ "source": "node2", "target": "node5", "label": "" }, |
||||
{ "source": "node2", "target": "node4", "label": "" }, |
||||
{ "source": "node1", "target": "node2", "label": "" }, |
||||
{ "source": "node5", "target": "node6", "label": "" }, |
||||
{ "source": "node5", "target": "node7", "label": "" }, |
||||
{ "source": "node3", "target": "node8", "label": "" }, |
||||
{ "source": "node3", "target": "node9", "label": "" }, |
||||
{ "source": "node3", "target": "node10", "label": "" }, |
||||
{ "source": "node10", "target": "node11", "label": "" }, |
||||
{ "source": "node10", "target": "node12", "label": "" }, |
||||
{ "source": "node11", "target": "node9", "label": "" }, |
||||
{ "source": "node11", "target": "node8", "label": "" }, |
||||
{ "source": "node12", "target": "node8", "label": "" }, |
||||
{ "source": "node8", "target": "node13", "label": "" }, |
||||
{ "source": "node8", "target": "node14", "label": "" }, |
||||
{ "source": "node11", "target": "node13", "label": "" }, |
||||
{ "source": "node12", "target": "node13", "label": "" }, |
||||
{ "source": "node2", "target": "node15", "label": "" }, |
||||
{ "source": "node15", "target": "node16", "label": "" }, |
||||
{ "source": "node16", "target": "node17", "label": "" }, |
||||
{ "source": "node1", "target": "node1", "label": "" }, |
||||
{ "source": "node1", "target": "node18", "label": "" }, |
||||
{ "source": "node18", "target": "node21", "label": "" }, |
||||
{ "source": "node18", "target": "node20", "label": "" }, |
||||
{ "source": "node18", "target": "node19", "label": "" }, |
||||
{ "source": "node18", "target": "node22", "label": "" }, |
||||
{ "source": "node22", "target": "node23", "label": "" }, |
||||
{ "source": "node19", "target": "node23", "label": "" }, |
||||
{ "source": "node20", "target": "node23", "label": "" }, |
||||
{ "source": "node21", "target": "node23", "label": "" }, |
||||
{ "source": "node1", "target": "node24", "label": "" }, |
||||
{ "source": "node24", "target": "node25", "label": "" }, |
||||
{ "source": "node24", "target": "node26", "label": "" }, |
||||
{ "source": "node12", "target": "node9", "label": "" }, |
||||
{ "source": "node19", "target": "node27", "label": "" }, |
||||
{ "source": "node19", "target": "node29", "label": "" }, |
||||
{ "source": "node19", "target": "node31", "label": "" }, |
||||
{ "source": "node21", "target": "node33", "label": "" }, |
||||
{ "source": "node21", "target": "node34", "label": "" }, |
||||
{ "source": "node21", "target": "node35", "label": "" }, |
||||
{ "source": "node20", "target": "node36", "label": "" }, |
||||
{ "source": "node20", "target": "node37", "label": "" }, |
||||
{ "source": "node20", "target": "node38", "label": "" }, |
||||
{ "source": "node22", "target": "node39", "label": "" }, |
||||
{ "source": "node22", "target": "node40", "label": "" }, |
||||
{ "source": "node22", "target": "node41", "label": "" } |
||||
] |
||||
} |
@ -1,9 +1,19 @@ |
||||
<template> |
||||
<!-- <section class="app-main"> |
||||
<transition name="fade-transform" mode="out-in"> |
||||
<router-view :key="$route.path" /> |
||||
</transition> |
||||
</section> --> |
||||
<router-view></router-view> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import {} from 'vue' |
||||
import {useRoute } from 'vue-router' |
||||
const $route = useRoute() |
||||
console.log( $route); |
||||
|
||||
|
||||
</script> |
||||
|
||||
<style lang="scss" scoped></style> |
||||
|
After Width: | Height: | Size: 555 KiB |
Binary file not shown.
@ -0,0 +1,69 @@ |
||||
import { defineStore } from 'pinia'; |
||||
const editAtlasStore = defineStore({ |
||||
id: 'editAtlasStore', |
||||
state: (): any => ({ |
||||
log: [], |
||||
dataList: { |
||||
nodes: [], |
||||
edges: [] |
||||
} |
||||
}), |
||||
actions: { |
||||
addLog(param: any) { // 存储操作记录
|
||||
this.log.unshift(param); |
||||
}, |
||||
deleteLog() { // 删除操作记录
|
||||
this.log.splice(0, 1); |
||||
}, |
||||
clearData() { // 清空数据
|
||||
this.dataList = { |
||||
nodes: [], |
||||
edges: []
|
||||
}; |
||||
}, |
||||
getData(param: any) { // 导入数据
|
||||
this.dataList = param; |
||||
}, |
||||
addNode(param: any) { // 添加节点
|
||||
this.dataList.nodes.push(param); |
||||
}, |
||||
addEdge(param: any) { // 添加连线
|
||||
this.dataList.edges.push(param); |
||||
}, |
||||
deleteNode(param: any) { // 删除节点
|
||||
const index = this.dataList.nodes.findIndex((value: any) => { |
||||
if (typeof param === 'string') { |
||||
return value.id === param; |
||||
} else { |
||||
return value.id === param.id; |
||||
} |
||||
}); |
||||
this.dataList.nodes.splice(index, 1); |
||||
}, |
||||
deleteEdge(param: any) { // 删除连线
|
||||
const index = this.dataList.edges.findIndex((value: any) => { |
||||
if (typeof param === 'string') { |
||||
return value.id === param; |
||||
} else { |
||||
return value.id === param.id; |
||||
} |
||||
}); |
||||
this.dataList.edges.splice(index, 1); |
||||
}, |
||||
updateNode(param: any) { // 更新节点
|
||||
this.dataList.nodes.forEach((item: any, index: number) => { |
||||
if (item.id === param.id) { |
||||
this.dataList.nodes[index] = param; |
||||
} |
||||
}); |
||||
}, |
||||
updateEdge(param: any) { // 更新节点
|
||||
this.dataList.edges.forEach((item: any, index: number) => { |
||||
if (item.id === param.id) { |
||||
this.dataList.edges[index] = param; |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
export default editAtlasStore |
@ -1,5 +1,33 @@ |
||||
// 引入清除浏览器默认样式文件 |
||||
@import './reset.scss'; |
||||
.view-container{ |
||||
padding-top: 120px; |
||||
.banner{ |
||||
width: 100%; |
||||
height: 410px; |
||||
background: url('../assets/images/banner2.png') no-repeat; |
||||
background-size: cover |
||||
} |
||||
/* 设置滚动条的宽度和颜色 */ |
||||
::-webkit-scrollbar { |
||||
width: 10px; /* 滚动条宽度 */ |
||||
} |
||||
|
||||
/* 设置滚动条的轨道背景色 */ |
||||
::-webkit-scrollbar-track { |
||||
background-color: #f1f1f1; |
||||
} |
||||
|
||||
/* 设置滚动条上下按钮的样式 */ |
||||
::-webkit-scrollbar-button { |
||||
background-color: #ccc; |
||||
} |
||||
|
||||
/* 设置滚动条的滑块样式 */ |
||||
::-webkit-scrollbar-thumb { |
||||
background-color: #ccc; |
||||
border-radius: 5px; /* 滑块圆角 */ |
||||
} |
||||
|
||||
/* 设置滑块在hover状态下的样式 */ |
||||
::-webkit-scrollbar-thumb:hover { |
||||
background-color: #555; |
||||
} |
@ -0,0 +1,44 @@ |
||||
// 通用方法
|
||||
// 判断字符、数组、对象是否为空 空返回true
|
||||
const isNullAndEmpty = (param) => { |
||||
if (param instanceof Array) { // 数组
|
||||
if (param.length > 0) { |
||||
return false |
||||
} else { |
||||
return true |
||||
} |
||||
} else if (typeof param === 'string') { // 字符串
|
||||
if (param === undefined || param === null || param.trim() === '') { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else if (Object.prototype.toString.call(param) === '[object Object]') { // 对象
|
||||
for (var key in param) { |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
} |
||||
|
||||
const objectJS = { // 对象相关方法
|
||||
deepClone (obj, hash = new WeakMap()) { // 深拷贝对象
|
||||
if (obj === null) return obj |
||||
if (obj instanceof Date) return new Date(obj) |
||||
if (obj instanceof RegExp) return new RegExp(obj) |
||||
if (typeof obj !== 'object') return obj |
||||
if (hash.get(obj)) return hash.get(obj) |
||||
let cloneObj = new obj.constructor() |
||||
hash.set(obj, cloneObj) |
||||
for (let key in obj) { |
||||
if (obj.hasOwnProperty(key)) { |
||||
cloneObj[key] = this.deepClone(obj[key], hash) |
||||
} |
||||
} |
||||
return cloneObj |
||||
} |
||||
} |
||||
|
||||
export { |
||||
isNullAndEmpty, objectJS |
||||
} |
@ -0,0 +1,118 @@ |
||||
|
||||
;(function(win, lib) { |
||||
var doc = win.document; |
||||
var docEl = doc.documentElement; |
||||
var metaEl = doc.querySelector('meta[name="viewport"]'); |
||||
var flexibleEl = doc.querySelector('meta[name="flexible"]'); |
||||
var dpr = 0; |
||||
var scale = 0; |
||||
var tid; |
||||
var flexible = lib.flexible || (lib.flexible = {}); |
||||
|
||||
if (metaEl) { |
||||
console.warn('将根据已有的meta标签来设置缩放比例'); |
||||
var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/); |
||||
if (match) { |
||||
scale = parseFloat(match[1]); |
||||
dpr = parseInt(1 / scale); |
||||
} |
||||
} else if (flexibleEl) { |
||||
var content = flexibleEl.getAttribute('content'); |
||||
if (content) { |
||||
var initialDpr = content.match(/initial\-dpr=([\d\.]+)/); |
||||
var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/); |
||||
if (initialDpr) { |
||||
dpr = parseFloat(initialDpr[1]); |
||||
scale = parseFloat((1 / dpr).toFixed(2)); |
||||
} |
||||
if (maximumDpr) { |
||||
dpr = parseFloat(maximumDpr[1]); |
||||
scale = parseFloat((1 / dpr).toFixed(2)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!dpr && !scale) { |
||||
var isAndroid = win.navigator.appVersion.match(/android/gi); |
||||
var isIPhone = win.navigator.appVersion.match(/iphone/gi); |
||||
var devicePixelRatio = win.devicePixelRatio; |
||||
if (isIPhone) { |
||||
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
|
||||
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { |
||||
dpr = 3; |
||||
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ |
||||
dpr = 2; |
||||
} else { |
||||
dpr = 1; |
||||
} |
||||
} else { |
||||
// 其他设备下,仍旧使用1倍的方案
|
||||
dpr = 1; |
||||
} |
||||
scale = 1 / dpr; |
||||
} |
||||
|
||||
docEl.setAttribute('data-dpr', dpr); |
||||
if (!metaEl) { |
||||
metaEl = doc.createElement('meta'); |
||||
metaEl.setAttribute('name', 'viewport'); |
||||
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); |
||||
if (docEl.firstElementChild) { |
||||
docEl.firstElementChild.appendChild(metaEl); |
||||
} else { |
||||
var wrap = doc.createElement('div'); |
||||
wrap.appendChild(metaEl); |
||||
doc.write(wrap.innerHTML); |
||||
} |
||||
} |
||||
|
||||
function refreshRem(){ |
||||
var width = docEl.getBoundingClientRect().width; |
||||
if (width / dpr > 1920) { |
||||
width = 1920 * dpr; |
||||
} |
||||
var rem = width / 10; |
||||
docEl.style.fontSize = rem + 'px'; |
||||
flexible.rem = win.rem = rem; |
||||
} |
||||
|
||||
win.addEventListener('resize', function() { |
||||
clearTimeout(tid); |
||||
tid = setTimeout(refreshRem, 300); |
||||
}, false); |
||||
win.addEventListener('pageshow', function(e) { |
||||
if (e.persisted) { |
||||
clearTimeout(tid); |
||||
tid = setTimeout(refreshRem, 300); |
||||
} |
||||
}, false); |
||||
|
||||
if (doc.readyState === 'complete') { |
||||
doc.body.style.fontSize = 12 * dpr + 'px'; |
||||
} else { |
||||
doc.addEventListener('DOMContentLoaded', function(e) { |
||||
doc.body.style.fontSize = 12 * dpr + 'px'; |
||||
}, false); |
||||
} |
||||
|
||||
|
||||
refreshRem(); |
||||
|
||||
flexible.dpr = win.dpr = dpr; |
||||
flexible.refreshRem = refreshRem; |
||||
flexible.rem2px = function(d) { |
||||
var val = parseFloat(d) * this.rem; |
||||
if (typeof d === 'string' && d.match(/rem$/)) { |
||||
val += 'px'; |
||||
} |
||||
return val; |
||||
} |
||||
flexible.px2rem = function(d) { |
||||
var val = parseFloat(d) / this.rem; |
||||
if (typeof d === 'string' && d.match(/px$/)) { |
||||
val += 'rem'; |
||||
} |
||||
return val; |
||||
} |
||||
|
||||
})(window, window['lib'] || (window['lib'] = {})); |
@ -0,0 +1,79 @@ |
||||
import pinia from '@/store' |
||||
import editAtlasStore from '@/store/module/editAtlas' |
||||
const AtlasStore = editAtlasStore(pinia) |
||||
let obj = { |
||||
source: '', |
||||
target: '', |
||||
} |
||||
export default{ |
||||
getEvents () { |
||||
return { |
||||
'node:click': 'onClick', |
||||
mousemove: 'onMousemove', |
||||
'edge:click': 'onEdgeClick' |
||||
} |
||||
}, |
||||
onClick (e) { |
||||
const node = e.item |
||||
this.item = e.item |
||||
const graph = this.graph |
||||
const point = { |
||||
x: e.x, |
||||
y: e.y |
||||
} |
||||
const model = node.getModel() |
||||
if (this.addingEdge && this.edge) { |
||||
graph.updateItem(this.edge, { |
||||
target: model.id |
||||
}) |
||||
this.edge = null |
||||
this.addingEdge = false |
||||
} else { |
||||
obj = { |
||||
source: model.id, |
||||
target: point |
||||
} |
||||
this.edge = graph.addItem('edge', obj) |
||||
AtlasStore.addEdge(obj) |
||||
// 操作记录
|
||||
let logObj = { |
||||
id: String('log' + (AtlasStore.log.length + 1)), |
||||
action: 'addEdge', |
||||
data: obj |
||||
} |
||||
AtlasStore.addLog(logObj) |
||||
this.addingEdge = true |
||||
e.item._cfg.model.linkPoints.bottom = false |
||||
this.graph.refreshItem(e.item) |
||||
} |
||||
}, |
||||
onMousemove (e) { |
||||
const point = { |
||||
x: e.x, |
||||
y: e.y |
||||
} |
||||
if (this.addingEdge && this.edge) { |
||||
if (e.item !== null && e.item._cfg.type === 'node') { // 判断是否为节点
|
||||
this.graph.updateItem(this.edge, { |
||||
target: e.item._cfg.id |
||||
}) |
||||
obj.target = e.item._cfg.id |
||||
} else { |
||||
this.graph.updateItem(this.edge, { |
||||
target: point |
||||
}) |
||||
} |
||||
} |
||||
}, |
||||
onEdgeClick (e) { |
||||
const currentEdge = e.item |
||||
if (this.addingEdge && this.edge === currentEdge) { // 点击空白处后移除连线
|
||||
let index = AtlasStore.dataList.edges.length - 1 |
||||
AtlasStore.deleteEdge(AtlasStore.dataList.edges[index]) |
||||
this.graph.removeItem(this.edge) |
||||
this.edge = null |
||||
this.addingEdge = false |
||||
} |
||||
this.graph.setMode('default') |
||||
} |
||||
} |
@ -0,0 +1,32 @@ |
||||
import pinia from '@/store' |
||||
import editAtlasStorefrom from '@/store/module/editAtlas' |
||||
const AtlasStore = editAtlasStorefrom(pinia) |
||||
export default{ |
||||
getDefaultCfg () { |
||||
return { |
||||
trigger: 'shift' |
||||
} |
||||
}, |
||||
getEvents () { |
||||
return { |
||||
'canvas:dblclick': 'onClick' // 双击画布添加节点
|
||||
} |
||||
}, |
||||
onClick (ev) { |
||||
let obj = { |
||||
id: String('node' + (AtlasStore.dataList.nodes.length + 1)), |
||||
label: String(AtlasStore.dataList.nodes.length + 1), |
||||
x: ev.x, |
||||
y: ev.y |
||||
} |
||||
this.graph.addItem('node', obj) |
||||
AtlasStore.addNode(obj) |
||||
// 操作记录
|
||||
let logObj = { |
||||
id: String('log' + (AtlasStore.log.length + 1)), |
||||
action: 'addNode', |
||||
data: obj |
||||
} |
||||
AtlasStore.addLog(logObj) |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
export default{ |
||||
getEvents () { |
||||
return { |
||||
'node:mouseover': 'onMouseover', |
||||
'node:mouseleave': 'onMouseleave', |
||||
'node:mousedown': 'onMousedown' |
||||
} |
||||
}, |
||||
onMouseover (e) { // 鼠标移入显示锚点
|
||||
e.item._cfg.model.linkPoints.bottom = true |
||||
this.graph.refreshItem(e.item) |
||||
}, |
||||
onMouseleave (e) { // 鼠标移出隐藏锚点
|
||||
e.item._cfg.model.linkPoints.bottom = false |
||||
this.graph.refreshItem(e.item) |
||||
}, |
||||
onMousedown (e) { |
||||
const edge = this.graph.findAllByState('edge', 'selected') |
||||
if (edge.length !== 0) { |
||||
this.graph.setItemState(edge[0], 'selected', false) |
||||
} |
||||
if (e.target.cfg.className === 'link-point-bottom') { // 点击锚点
|
||||
this.graph.setMode('addEdge') |
||||
} else { // 点击节点
|
||||
this.graph.setMode('default') |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
export default{ |
||||
getEvents () { |
||||
return { |
||||
'edge:mouseover': 'onMouseover', |
||||
'edge:mouseout': 'onMouseout', |
||||
'edge:click': 'onEdgeClick', |
||||
'canvas:click': 'onCanvasClick' |
||||
} |
||||
}, |
||||
changeSelected () { // 取消选中状态
|
||||
const node = this.graph.findAllByState('node', 'selected') |
||||
if (node.length !== 0) { |
||||
this.graph.setItemState(node[0], 'selected', false) |
||||
} |
||||
const edge = this.graph.findAllByState('edge', 'selected') |
||||
if (edge.length !== 0) { |
||||
this.graph.setItemState(edge[0], 'selected', false) |
||||
} |
||||
}, |
||||
onMouseover (e) { // 鼠标移入
|
||||
this.graph.setItemState(e.item, 'hover', true) |
||||
}, |
||||
onMouseout (e) { // 鼠标移出
|
||||
this.graph.setItemState(e.item, 'hover', false) |
||||
}, |
||||
onEdgeClick (e) { // 鼠标点击
|
||||
this.changeSelected() |
||||
this.graph.setItemState(e.item, 'selected', true) |
||||
}, |
||||
onCanvasClick () { // 取消选中
|
||||
this.changeSelected() |
||||
} |
||||
} |
@ -0,0 +1,397 @@ |
||||
<template> |
||||
<div> |
||||
<v-expansion-panels multiple tile class="panel" v-model="bigPanels"> |
||||
<v-expansion-panel> |
||||
<v-expansion-panel-header style="font-weight: bold"> |
||||
配置器 |
||||
</v-expansion-panel-header> |
||||
<v-expansion-panel-content> |
||||
<v-expansion-panels |
||||
v-if="selectedNodeId" |
||||
multiple |
||||
tile |
||||
class="littlepanel" |
||||
v-model="littlePanels" |
||||
accordion |
||||
> |
||||
<v-expansion-panel tile> |
||||
<v-expansion-panel-header> 节点样式 </v-expansion-panel-header> |
||||
<v-expansion-panel-content> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>  半径:<el-input |
||||
@input="nodeChange" |
||||
v-model="radius" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="1" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
<el-col :span="12" |
||||
>背景颜色:<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="node.style.fill" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
show-alpha |
||||
></el-color-picker> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>边框宽度:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.style.lineWidth" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="1" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
<el-col :span="12" |
||||
>边框颜色:<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="node.style.stroke" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>阴影颜色:<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="node.style.shadowColor" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
<el-col :span="12" |
||||
>阴影范围:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.style.shadowBlur" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="0" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>X 偏移量:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.style.shadowOffsetX" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="0" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
<el-col :span="12" |
||||
>Y 偏移量:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.style.shadowOffsetY" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="0" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
</el-row> |
||||
</v-expansion-panel-content> |
||||
</v-expansion-panel> |
||||
<v-expansion-panel tile> |
||||
<v-expansion-panel-header> 节点文本 </v-expansion-panel-header> |
||||
<v-expansion-panel-content> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="24" |
||||
>内容:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.label" |
||||
style="width: 81%" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>大小:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.labelCfg.style.fontSize" |
||||
style="width: 60%" |
||||
size="mini" |
||||
:min="1" |
||||
type="number" |
||||
></el-input |
||||
></el-col> |
||||
<el-col :span="12" |
||||
>粗细:<el-input |
||||
@input="nodeChange" |
||||
v-model="node.labelCfg.style.fontWeight" |
||||
:min="100" |
||||
:max="900" |
||||
:step="100" |
||||
style="width: 60%" |
||||
size="mini" |
||||
type="number" |
||||
></el-input |
||||
></el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>颜色:<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="node.labelCfg.style.fill" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
<el-col :span="12" |
||||
>定位:<el-select |
||||
@change="nodeChange" |
||||
v-model="node.labelCfg.position" |
||||
style="width: 60%" |
||||
size="mini" |
||||
> |
||||
<el-option |
||||
v-for="item in placeList" |
||||
:key="item" |
||||
:label="item" |
||||
:value="item" |
||||
> |
||||
</el-option> </el-select |
||||
></el-col> |
||||
</el-row> |
||||
</v-expansion-panel-content> |
||||
</v-expansion-panel> |
||||
</v-expansion-panels> |
||||
<v-expansion-panels |
||||
v-if="selectedEdgeId" |
||||
multiple |
||||
tile |
||||
class="littlepanel" |
||||
v-model="littlePanels" |
||||
accordion |
||||
> |
||||
<v-expansion-panel tile> |
||||
<v-expansion-panel tile> |
||||
<v-expansion-panel-header> |
||||
连线样式 |
||||
</v-expansion-panel-header> |
||||
<v-expansion-panel-content> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>类型:<el-select |
||||
@change="edgeChange" |
||||
v-model="edge.type" |
||||
style="width: 72%" |
||||
size="mini" |
||||
> |
||||
<el-option |
||||
v-for="item in lineList" |
||||
:key="item" |
||||
:label="item" |
||||
:value="item" |
||||
> |
||||
</el-option> </el-select |
||||
></el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>宽度:<el-input |
||||
@input="edgeChange" |
||||
v-model="edge.style.lineWidth" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="1" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
<el-col :span="12" |
||||
>颜色:<el-color-picker |
||||
@change="edgeChange" |
||||
v-model="edge.style.stroke" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
</el-row> |
||||
</v-expansion-panel-content> |
||||
</v-expansion-panel> |
||||
<v-expansion-panel-header> 连线文本 </v-expansion-panel-header> |
||||
<v-expansion-panel-content> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="24" |
||||
>内容:<el-input |
||||
@input="edgeChange" |
||||
v-model="edge.label" |
||||
style="width: 81%" |
||||
size="mini" |
||||
></el-input |
||||
></el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>大小:<el-input |
||||
@input="edgeChange" |
||||
v-model="edge.labelCfg.style.fontSize" |
||||
style="width: 60%" |
||||
size="mini" |
||||
type="number" |
||||
:min="1" |
||||
></el-input |
||||
></el-col> |
||||
<el-col :span="12" |
||||
>粗细:<el-input |
||||
@input="edgeChange" |
||||
v-model="edge.labelCfg.style.fontWeight" |
||||
:min="100" |
||||
:max="900" |
||||
:step="100" |
||||
style="width: 60%" |
||||
size="mini" |
||||
type="number" |
||||
></el-input |
||||
></el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12" |
||||
>颜色:<el-color-picker |
||||
@change="edgeChange" |
||||
v-model="edge.labelCfg.style.fill" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
<el-col :span="12" |
||||
>定位:<el-select |
||||
@change="edgeChange" |
||||
v-model="edge.labelCfg.position" |
||||
style="width: 60%" |
||||
size="mini" |
||||
> |
||||
<el-option |
||||
v-for="item in placeList1" |
||||
:key="item" |
||||
:label="item" |
||||
:value="item" |
||||
> |
||||
</el-option> </el-select |
||||
></el-col> |
||||
</el-row> |
||||
</v-expansion-panel-content> |
||||
</v-expansion-panel> |
||||
</v-expansion-panels> |
||||
</v-expansion-panel-content> |
||||
</v-expansion-panel> |
||||
<v-expansion-panel class="panel" tile> |
||||
<v-expansion-panel-header style="font-weight: bold"> |
||||
导航器 |
||||
</v-expansion-panel-header> |
||||
<v-expansion-panel-content> |
||||
<div ref="minimap" class="g6-minimap"></div> |
||||
</v-expansion-panel-content> |
||||
</v-expansion-panel> |
||||
</v-expansion-panels> |
||||
</div> |
||||
|
||||
</template> |
||||
<script> |
||||
export default { |
||||
props: { |
||||
selectedNodeId: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
selectedEdgeId: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
graph: { |
||||
type: Object, |
||||
default: () => {} |
||||
} |
||||
}, |
||||
watch: { |
||||
selectedEdgeId: { |
||||
handler (newVal, oldVal) { |
||||
if (newVal !== '') { |
||||
let edgeArr = this.$store.state.dataList.edges.filter((item) => { |
||||
return item.id === newVal |
||||
}) |
||||
this.edge = edgeArr[0] |
||||
this.bigPanels = [0, 1] |
||||
this.littlePanels = [0, 1] |
||||
} |
||||
} |
||||
}, |
||||
selectedNodeId: { |
||||
handler (newVal, oldVal) { |
||||
if (newVal !== '') { |
||||
let nodeArr = this.$store.state.dataList.nodes.filter((item) => { |
||||
return item.id === newVal |
||||
}) |
||||
this.node = nodeArr[0] |
||||
this.radius = nodeArr[0].size[0] / 2 |
||||
this.bigPanels = [0, 1] |
||||
this.littlePanels = [0, 1] |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
data: () => ({ |
||||
lineList: ['line', 'polyline', 'arc', 'quardratic', 'cubic', 'cubic-horizontal'], |
||||
placeList: ['center', 'top', 'bottom', 'left', 'right'], |
||||
placeList1: ['start', 'middle', 'end'], |
||||
bigPanels: [0, 1], |
||||
littlePanels: [0, 1], |
||||
radius: '', |
||||
node: { |
||||
style: {}, |
||||
labelCfg: { |
||||
style: {} |
||||
} |
||||
}, |
||||
edge: { |
||||
style: {}, |
||||
labelCfg: { |
||||
style: {} |
||||
} |
||||
} |
||||
}), |
||||
methods: { |
||||
nodeChange () { |
||||
this.node.size = [this.radius * 2, this.radius * 2] |
||||
this.graph.updateItem(this.node.id, this.node) |
||||
this.$store.commit('updateNode', this.node) |
||||
}, |
||||
edgeChange () { |
||||
this.graph.updateItem(this.edge.id, this.edge) |
||||
this.$store.commit('updateEdge', this.edge) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.g6-minimap { |
||||
width: 100%; |
||||
height: 20vw; |
||||
border: 1px solid #35495e; |
||||
} |
||||
/deep/ .el-col-12 { |
||||
margin-top: 5px; |
||||
} |
||||
/deep/ .panel .v-expansion-panel-content__wrap { |
||||
padding: 0 3px 10px 3px; |
||||
box-sizing: border-box; |
||||
} |
||||
/deep/ .littlepanel .v-expansion-panel-content__wrap { |
||||
padding: 0 10px 10px 20px; |
||||
box-sizing: border-box; |
||||
} |
||||
/deep/ .el-input__inner{ |
||||
padding: 0 0 0 15px; |
||||
} |
||||
</style> |
@ -0,0 +1,408 @@ |
||||
<template> |
||||
<div> |
||||
<el-collapse v-model="state.bigPanels" accordion> |
||||
<el-collapse-item title="配置器" name="1"> |
||||
<el-collapse v-model="state.littlePanels" accordion> |
||||
<el-collapse-item |
||||
title="节点样式" |
||||
v-if="selectedNodeId" |
||||
name="1-1" |
||||
style="padding: 0 15px" |
||||
> |
||||
<!-- 节点样式配置内容 --> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
  半径: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.radius" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="1" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
背景颜色: |
||||
<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="state.node.style.fill" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
show-alpha |
||||
></el-color-picker> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
边框宽度: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.style.lineWidth" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="1" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
边框颜色: |
||||
<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="state.node.style.stroke" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
阴影颜色: |
||||
<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="state.node.style.shadowColor" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
阴影范围: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.style.shadowBlur" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="0" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
X 偏移量: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.style.shadowOffsetX" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="0" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
Y 偏移量: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.style.shadowOffsetY" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="0" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
</el-row> |
||||
</el-collapse-item> |
||||
<el-collapse-item |
||||
title="节点文本" |
||||
v-if="selectedNodeId" |
||||
name="1-2" |
||||
style="padding: 0 15px" |
||||
> |
||||
<!-- 节点文本配置内容 --> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="24"> |
||||
内容: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.label" |
||||
style="width: 81%" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
大小: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.labelCfg.style.fontSize" |
||||
style="width: 60%" |
||||
size="mini" |
||||
:min="1" |
||||
type="number" |
||||
></el-input> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
粗细: |
||||
<el-input |
||||
@input="nodeChange" |
||||
v-model="state.node.labelCfg.style.fontWeight" |
||||
:min="100" |
||||
:max="900" |
||||
:step="100" |
||||
style="width: 60%" |
||||
size="mini" |
||||
type="number" |
||||
></el-input> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
颜色: |
||||
<el-color-picker |
||||
@change="nodeChange" |
||||
v-model="state.node.labelCfg.style.fill" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
定位: |
||||
<el-select |
||||
@change="nodeChange" |
||||
v-model="state.node.labelCfg.position" |
||||
style="width: 60%" |
||||
size="mini" |
||||
> |
||||
<el-option |
||||
v-for="item in state.placeList" |
||||
:key="item" |
||||
:label="item" |
||||
:value="item" |
||||
></el-option> |
||||
</el-select> |
||||
</el-col> |
||||
</el-row> |
||||
</el-collapse-item> |
||||
<el-collapse-item title="连线样式" v-if="selectedEdgeId" name="2-1"> |
||||
<!-- 连线样式配置内容 --> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
类型: |
||||
<el-select |
||||
@change="edgeChange" |
||||
v-model="state.edge.type" |
||||
style="width: 72%" |
||||
size="mini" |
||||
> |
||||
<el-option |
||||
v-for="item in state.lineList" |
||||
:key="item" |
||||
:label="item" |
||||
:value="item" |
||||
></el-option> |
||||
</el-select> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
宽度: |
||||
<el-input |
||||
@input="edgeChange" |
||||
v-model="state.edge.style.lineWidth" |
||||
style="width: 40%" |
||||
type="number" |
||||
:min="1" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
颜色: |
||||
<el-color-picker |
||||
@change="edgeChange" |
||||
v-model="state.edge.style.stroke" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
</el-row> |
||||
</el-collapse-item> |
||||
<el-collapse-item title="连线文本" v-if="selectedEdgeId" name="2-2"> |
||||
<!-- 连线文本配置内容 --> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="24"> |
||||
内容: |
||||
<el-input |
||||
@input="edgeChange" |
||||
v-model="state.edge.label" |
||||
style="width: 81%" |
||||
size="mini" |
||||
></el-input> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
大小: |
||||
<el-input |
||||
@input="edgeChange" |
||||
v-model="state.edge.labelCfg.style.fontSize" |
||||
style="width: 60%" |
||||
size="mini" |
||||
type="number" |
||||
:min="1" |
||||
></el-input> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
粗细: |
||||
<el-input |
||||
@input="edgeChange" |
||||
v-model="state.edge.labelCfg.style.fontWeight" |
||||
:min="100" |
||||
:max="900" |
||||
:step="100" |
||||
style="width: 60%" |
||||
size="mini" |
||||
type="number" |
||||
></el-input> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row type="flex" :gutter="20"> |
||||
<el-col :span="12"> |
||||
颜色: |
||||
<el-color-picker |
||||
@change="edgeChange" |
||||
v-model="state.edge.labelCfg.style.fill" |
||||
style="vertical-align: top" |
||||
size="mini" |
||||
></el-color-picker> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
定位: |
||||
<el-select |
||||
@change="edgeChange" |
||||
v-model="state.edge.labelCfg.position" |
||||
style="width: 60%" |
||||
size="mini" |
||||
> |
||||
<el-option |
||||
v-for="item in state.placeList1" |
||||
:key="item" |
||||
:label="item" |
||||
:value="item" |
||||
></el-option> |
||||
</el-select> |
||||
</el-col> |
||||
</el-row> |
||||
</el-collapse-item> |
||||
</el-collapse> |
||||
</el-collapse-item> |
||||
<el-collapse-item title="导航器" name="2"> |
||||
<div ref="minimap" class="g6-minimap"></div> |
||||
</el-collapse-item> |
||||
</el-collapse> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
import { reactive, ref, watch, onMounted } from 'vue' |
||||
import editAtlasStore from '@/store/module/editAtlas' |
||||
const AtlasStore = editAtlasStore() |
||||
const props = defineProps({ |
||||
selectedNodeId: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
selectedEdgeId: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
graph: { |
||||
type: Object, |
||||
default: () => ({}), |
||||
}, |
||||
}) |
||||
onMounted(() => { |
||||
console.log(AtlasStore.dataList, 'AtlasStore', props.graph) |
||||
}) |
||||
const state = reactive({ |
||||
lineList: [ |
||||
'line', |
||||
'polyline', |
||||
'arc', |
||||
'quardratic', |
||||
'cubic', |
||||
'cubic-horizontal', |
||||
], |
||||
placeList: ['center', 'top', 'bottom', 'left', 'right'], |
||||
placeList1: ['start', 'middle', 'end'], |
||||
bigPanels: ['2'], |
||||
littlePanels: ['1-2', '1-2'], |
||||
radius: '', |
||||
node: { |
||||
style: {}, |
||||
labelCfg: { |
||||
style: {}, |
||||
}, |
||||
}, |
||||
edge: { |
||||
style: {}, |
||||
labelCfg: { |
||||
style: {}, |
||||
}, |
||||
}, |
||||
}) |
||||
const minimap = ref(null) |
||||
const nodeChange = () => { |
||||
state.node.size = [state.radius * 2, state.radius * 2] |
||||
console.log(state.node.id, state.node) |
||||
props.graph.updateItem(state.node.id, state.node) |
||||
// Assuming your store is setup correctly in Vue 3 |
||||
AtlasStore.updateNode(state.node) |
||||
// this.$store.commit('updateNode', state.node); |
||||
} |
||||
|
||||
const edgeChange = () => { |
||||
props.graph.updateItem(state.edge.id, state.edge) |
||||
AtlasStore.updateEdge(state.edge) |
||||
// this.$store.commit('updateEdge', state.edge); |
||||
} |
||||
|
||||
watch( |
||||
() => props.selectedEdgeId, |
||||
(newVal) => { |
||||
// Handle node selection change |
||||
if (newVal !== '') { |
||||
let edgeArr = AtlasStore.dataList.edges.filter((item) => { |
||||
return item.id === newVal |
||||
}) |
||||
console.log(edgeArr) |
||||
state.edge = edgeArr[0] |
||||
state.bigPanels = [0, 1] |
||||
state.littlePanels = [0, 1] |
||||
} |
||||
}, |
||||
) |
||||
|
||||
watch( |
||||
() => props.selectedNodeId, |
||||
(newVal) => { |
||||
console.log(newVal) |
||||
if (newVal === '') return |
||||
// console.log(AtlasStore.dataList.nodes); |
||||
// Handle edge selection change |
||||
let nodeArr = AtlasStore.dataList.nodes.filter((item) => { |
||||
return item.id === newVal |
||||
}) |
||||
console.log(nodeArr) |
||||
state.node = nodeArr[0] |
||||
state.radius = nodeArr[0].size[0] / 2 |
||||
state.bigPanels = ['1', '2'] |
||||
state.littlePanels = ['1-1', '1-2'] |
||||
}, |
||||
) |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.g6-minimap { |
||||
width: 100%; |
||||
height: 20vw; |
||||
border: 1px solid #35495e; |
||||
} |
||||
::v-deep .el-col-12 { |
||||
margin-top: 5px; |
||||
} |
||||
::v-deep .el-input__inner { |
||||
padding: 0 0 0 15px; |
||||
} |
||||
::v-deep .el-collapse-item__header { |
||||
padding: 0 15px !important; |
||||
} |
||||
</style> |
@ -0,0 +1,521 @@ |
||||
<template> |
||||
<div class="toolbar"> |
||||
<v-toolbar height="50"> |
||||
<v-tooltip v-for="(item, index) in tools" :key="index" bottom> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-btn |
||||
@click="comment_event(item.event)" |
||||
icon |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
<v-icon style="color: #35495e">{{ item.icon }}</v-icon> |
||||
</v-btn> |
||||
<template style="color: #c0c4cc" v-if="index == 8"> |
||||
{{ size }}% |
||||
</template> |
||||
<template v-else-if="index == 10"> |
||||
<v-spacer></v-spacer> |
||||
<el-select |
||||
v-model="layout" |
||||
@change="changeLayout" |
||||
size="mini" |
||||
placeholder="请选择" |
||||
> |
||||
<el-option |
||||
v-for="item in layouts" |
||||
:label="item.label" |
||||
:value="item.value" |
||||
:key="item.value" |
||||
> |
||||
</el-option> |
||||
</el-select> |
||||
<v-spacer></v-spacer> |
||||
</template> |
||||
<template |
||||
v-else-if=" |
||||
index == 2 || |
||||
index == 5 || |
||||
index == 7 || |
||||
index == 11 || |
||||
index == 13 |
||||
" |
||||
> |
||||
<v-divider vertical></v-divider> |
||||
</template> |
||||
</template> |
||||
<span>{{ item.tip }}</span> |
||||
</v-tooltip> |
||||
</v-toolbar> |
||||
<el-dialog title="上传txt文件" :visible.sync="dialogVisible" width="50%"> |
||||
数据格式:(<a |
||||
href="https://github.com/qiaolufei/KG-Editor/issues/3" |
||||
target="_blank" |
||||
>进阶版数据格式</a |
||||
>) |
||||
<pre style="color:#000"> |
||||
{ |
||||
"nodes":[ |
||||
{"id": "node1", "label": "luffy"}, |
||||
{"id": "node2", "label": "24岁"}, |
||||
{"id": "node3", "label": "62kg"} |
||||
... |
||||
], |
||||
"edges":[ |
||||
{"source": "node1", "target": "node2", "label": "年龄"}, |
||||
{"source": "node1", "target": "node3", "label": "体重"} |
||||
... |
||||
] |
||||
} |
||||
</pre> |
||||
<div style="text-align:center"> |
||||
<el-upload |
||||
drag |
||||
:limit="1" |
||||
action="https://jsonplaceholder.typicode.com/posts/" |
||||
ref="upload" |
||||
accept=".txt" |
||||
:close-on-click-modal="false" |
||||
:file-list="fileList" |
||||
:on-success="onSuccess" |
||||
:on-remove="onRemove" |
||||
> |
||||
<i class="el-icon-upload"></i> |
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> |
||||
<div class="el-upload__tip" slot="tip"> |
||||
上传txt文件,且只能上传 1 个文件 |
||||
</div> |
||||
</el-upload> |
||||
</div> |
||||
<span slot="footer" class="dialog-footer"> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
<el-button type="primary" @click="submitData">确 定</el-button> |
||||
</span> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import defaultNode from "@/default/default_node"; |
||||
import defaultEdge from "@/default/default_edge"; |
||||
import { isNullAndEmpty, objectJS } from "@/utils/commen"; |
||||
export default { |
||||
props: { |
||||
size: { |
||||
type: Number, |
||||
default: 100 |
||||
}, |
||||
selectedNodeId: { |
||||
type: String, |
||||
default: "" |
||||
}, |
||||
selectedEdgeId: { |
||||
type: String, |
||||
default: "" |
||||
}, |
||||
graph: { |
||||
type: Object, |
||||
default: () => {} |
||||
} |
||||
}, |
||||
watch: { |
||||
selectedEdgeId: { |
||||
handler(newVal, oldVal) { |
||||
if (newVal !== "") { |
||||
let edgeArr = this.$store.state.dataList.edges.filter(item => { |
||||
return item.id === newVal; |
||||
}); |
||||
this.edge = edgeArr[0]; |
||||
} |
||||
} |
||||
}, |
||||
selectedNodeId: { |
||||
handler(newVal, oldVal) { |
||||
if (newVal !== "") { |
||||
let nodeArr = this.$store.state.dataList.nodes.filter(item => { |
||||
return item.id === newVal; |
||||
}); |
||||
this.node = nodeArr[0]; |
||||
this.radius = nodeArr[0].size[0] / 2; |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
data: () => ({ |
||||
tools: [ |
||||
{ icon: "mdi-undo-variant", tip: "撤销 Ctrl+Z", event: "revoke" }, |
||||
{ icon: "mdi-backup-restore", tip: "重做", event: "restore" }, |
||||
{ icon: "mdi-content-copy", tip: "复制 Ctrl+C", event: "copy" }, |
||||
{ icon: "mdi-content-paste", tip: "粘贴 Ctrl+V", event: "paste" }, |
||||
{ |
||||
icon: "mdi-trash-can-outline", |
||||
tip: "删除 Ctrl+Backspace", |
||||
event: "delete" |
||||
}, |
||||
{ icon: "mdi-vector-arrange-above", tip: "置于顶层", event: "onTop" }, |
||||
{ icon: "mdi-vector-arrange-below", tip: "置于底层", event: "onBottom" }, |
||||
{ icon: "mdi-magnify-plus-outline", tip: "放大", event: "plus" }, |
||||
{ icon: "mdi-magnify-minus-outline", tip: "缩小", event: "minus" }, |
||||
{ icon: "mdi-arrow-collapse-all", tip: "适应画布", event: "adaptCanvas" }, |
||||
{ icon: "mdi-cloud-upload", tip: "导入文件", event: "importFile" }, |
||||
{ icon: "mdi-file-image", tip: "导出图片", event: "saveImage" }, |
||||
{ icon: "mdi-file-code", tip: "导出JSON", event: "saveJson" }, |
||||
{ icon: "mdi-help-box", tip: "帮助", event: "help" } |
||||
], |
||||
node: {}, |
||||
edge: {}, |
||||
cloneNode: {}, |
||||
dialogVisible: false, |
||||
uploadData: {}, |
||||
fileList: [], |
||||
layout: "random", |
||||
layouts: [ |
||||
{ label: "随机布局", value: "random" }, |
||||
{ label: "力导向布局", value: "force" }, |
||||
{ label: "Fruchterman布局", value: "fruchterman" }, |
||||
{ label: "环形布局", value: "circular" }, |
||||
{ label: "辐射布局", value: "radial" }, |
||||
{ label: "层次布局", value: "dagre" }, |
||||
{ label: "同心圆布局", value: "concentric" }, |
||||
{ label: "网格布局", value: "grid" } |
||||
] |
||||
}), |
||||
mounted() { |
||||
this.keyCodeForEvent(); |
||||
}, |
||||
methods: { |
||||
changeLayout() { |
||||
this.graph.updateLayout({ |
||||
type: this.layout |
||||
}); |
||||
}, |
||||
onSuccess(res, file, fileList) { |
||||
let reader = new FileReader(); |
||||
reader.readAsText(file.raw); |
||||
reader.onload = e => { |
||||
this.uploadData = []; |
||||
const str = String(e.target.result).replace(/\s*/g, ""); |
||||
if (str === "" || str === null) { |
||||
this.$message.error("文档数据不能为空!"); |
||||
} else { |
||||
try { |
||||
this.uploadData = JSON.parse(str); |
||||
} catch (err) { |
||||
this.$message.error(String(err)); |
||||
} |
||||
} |
||||
}; |
||||
}, |
||||
onRemove(file) { |
||||
this.fileList = []; |
||||
}, |
||||
comment_event(event) { |
||||
this[event](); |
||||
}, |
||||
revoke() { |
||||
// 撤销 |
||||
let log = this.$store.state.log; |
||||
let action = log[0].action; |
||||
switch (action) { |
||||
case "addNode": |
||||
this.graph.removeItem(log[0].data.id); |
||||
this.$store.commit("deleteNode", log[0].data); |
||||
break; |
||||
case "deleteNode": |
||||
this.graph.addItem("node", log[0].data); |
||||
this.$store.commit("addNode", log[0].data); |
||||
break; |
||||
case "addEdge": |
||||
this.graph.removeItem(log[0].data.id); |
||||
this.$store.commit("deleteEdge", log[0].data); |
||||
break; |
||||
case "deleteEdge": |
||||
this.graph.addItem("edge", log[0].data); |
||||
this.$store.commit("addEdge", log[0].data); |
||||
break; |
||||
} |
||||
this.$store.commit("deleteLog"); |
||||
}, |
||||
restore() { |
||||
this.graph.clear(); |
||||
this.$store.commit("clearData"); |
||||
}, |
||||
copy() { |
||||
if (this.selectedNodeId === "") { |
||||
this.$message.error("未选择节点!"); |
||||
} else { |
||||
this.$store.state.dataList.nodes.forEach(node => { |
||||
if (node.id === this.selectedNodeId) { |
||||
this.cloneNode = objectJS.deepClone(node); |
||||
this.$message.success("复制成功"); |
||||
} |
||||
}); |
||||
} |
||||
}, |
||||
paste() { |
||||
if (this.selectedNodeId === "") { |
||||
this.$message.error("未选择节点!"); |
||||
} else { |
||||
this.cloneNode.id = |
||||
"node" + (this.$store.state.dataList.nodes.length + 1); |
||||
this.cloneNode.label = this.$store.state.dataList.nodes.length + 1; |
||||
this.cloneNode.x = this.cloneNode.x + 20; |
||||
this.cloneNode.y = this.cloneNode.y + 20; |
||||
let obj = objectJS.deepClone(this.cloneNode); |
||||
this.graph.addItem("node", obj); |
||||
this.$store.commit("addNode", obj); |
||||
this.$message.success("粘贴成功"); |
||||
} |
||||
}, |
||||
delete() { |
||||
if (this.selectedEdgeId === "" && this.selectedNodeId === "") { |
||||
this.$message.error("未选择元素!"); |
||||
} else if (this.selectedEdgeId !== "") { |
||||
let obj = {}; |
||||
this.graph.getEdges().forEach(edge => { |
||||
if (edge._cfg.id === this.selectedEdgeId) { |
||||
obj = edge._cfg.model; |
||||
} |
||||
}); |
||||
// 操作记录 |
||||
let logObj = { |
||||
id: String("log" + (this.$store.state.log.length + 1)), |
||||
action: "deleteEdge", |
||||
data: obj |
||||
}; |
||||
this.$store.commit("addLog", logObj); |
||||
this.graph.removeItem(this.selectedEdgeId); |
||||
this.$store.commit("deleteEdge", this.selectedEdgeId); |
||||
this.$emit("update:selectedEdgeId", ""); |
||||
} else if (this.selectedNodeId !== "") { |
||||
let obj = {}; |
||||
this.graph.getNodes().forEach(node => { |
||||
if (node._cfg.id === this.selectedNodeId) { |
||||
obj = node._cfg.model; |
||||
} |
||||
}); |
||||
// 操作记录 |
||||
let logObj = { |
||||
id: String("log" + (this.$store.state.log.length + 1)), |
||||
action: "deleteNode", |
||||
data: obj |
||||
}; |
||||
this.$store.commit("addLog", logObj); |
||||
this.graph.removeItem(this.selectedNodeId); |
||||
this.$store.commit("deleteNode", this.selectedNodeId); |
||||
this.$emit("update:selectedNodeId", ""); |
||||
} |
||||
}, |
||||
onTop() { |
||||
if (this.selectedEdgeId === "" && this.selectedNodeId === "") { |
||||
this.$message.error("未选择元素!"); |
||||
} else if (this.selectedEdgeId !== "") { |
||||
this.graph.getEdges().forEach(edge => { |
||||
if (edge._cfg.id === this.selectedEdgeId) { |
||||
edge.toFront(); |
||||
} |
||||
}); |
||||
} else if (this.selectedNodeId !== "") { |
||||
this.graph.getNodes().forEach(node => { |
||||
if (node._cfg.id === this.selectedNodeId) { |
||||
node.toFront(); |
||||
} |
||||
}); |
||||
} |
||||
}, |
||||
onBottom() { |
||||
if (this.selectedEdgeId === "" && this.selectedNodeId === "") { |
||||
this.$message.error("未选择元素!"); |
||||
} else if (this.selectedEdgeId !== "") { |
||||
this.graph.getEdges().forEach(edge => { |
||||
if (edge._cfg.id === this.selectedEdgeId) { |
||||
edge.toBack(); |
||||
} |
||||
}); |
||||
} else if (this.selectedNodeId !== "") { |
||||
this.graph.getNodes().forEach(node => { |
||||
if (node._cfg.id === this.selectedNodeId) { |
||||
node.toBack(); |
||||
} |
||||
}); |
||||
} |
||||
}, |
||||
plus() { |
||||
const currentZoom = Number(this.graph.getZoom()); |
||||
this.graph.zoomTo(currentZoom + 0.1); |
||||
this.size = Number(((currentZoom + 0.1) * 100).toFixed(0)); |
||||
}, |
||||
minus() { |
||||
const currentZoom = Number(this.graph.getZoom()); |
||||
this.graph.zoomTo(currentZoom - 0.1); |
||||
this.size = Number(((currentZoom - 0.1) * 100).toFixed(0)); |
||||
}, |
||||
adaptCanvas() { |
||||
this.graph.fitView(20); |
||||
this.graph.fitCenter(); |
||||
this.graph.zoomTo(1); |
||||
}, |
||||
importFile() { |
||||
this.dialogVisible = true; |
||||
}, |
||||
initializeObj(source, target) { |
||||
// 初始化node/edge,防止上传文件数据中缺少参数 |
||||
for (let key in source) { |
||||
if (!target.hasOwnProperty(key)) { |
||||
target[key] = source[key]; |
||||
} |
||||
if (typeof source[key] === "object") { |
||||
this.initializeObj(source[key], target[key]); |
||||
} |
||||
} |
||||
return target; |
||||
}, |
||||
submitData() { |
||||
const dataList = this.uploadData; |
||||
let _this = this; |
||||
if (!isNullAndEmpty(dataList.nodes)) { |
||||
dataList.nodes.forEach(item => { |
||||
this.initializeObj(defaultNode, item); |
||||
_this.graph.addItem("node", item); |
||||
_this.$store.commit("addNode", item); |
||||
}); |
||||
} |
||||
if (!isNullAndEmpty(dataList.edges)) { |
||||
dataList.edges.forEach(item => { |
||||
this.initializeObj(defaultEdge, item); |
||||
_this.graph.addItem("edge", item); |
||||
_this.$store.commit("addEdge", item); |
||||
}); |
||||
} |
||||
this.graph.updateLayout({ |
||||
type: "random" |
||||
}); |
||||
this.dialogVisible = false; |
||||
}, |
||||
saveImage() { |
||||
if (!isNullAndEmpty(this.$store.state.dataList.nodes)) { |
||||
this.graph.downloadFullImage("graph", "image/png", { |
||||
backgroundColor: "#fff", |
||||
padding: [15, 15, 15, 15] |
||||
}); |
||||
} else { |
||||
this.$message.warning("画布为空!"); |
||||
} |
||||
}, |
||||
saveJson() { |
||||
if (!isNullAndEmpty(this.$store.state.dataList.nodes)) { |
||||
const dataList = this.$store.state.dataList; |
||||
const content = {}; |
||||
console.log(dataList); |
||||
content.nodes = dataList.nodes.map(x => { |
||||
return { |
||||
id: x.id, |
||||
label: x.label, |
||||
color:x.style.fill |
||||
}; |
||||
}); |
||||
content.links = dataList.edges.map(x => { |
||||
return { |
||||
source: x.source, |
||||
target: x.target, |
||||
label: x.label |
||||
}; |
||||
}); |
||||
// content.nodes = dataList.nodes |
||||
// content.edges = dataList.edges |
||||
var eleLink = document.createElement("a"); |
||||
eleLink.download = "kg.json"; |
||||
eleLink.style.display = "none"; |
||||
var blob = new Blob([JSON.stringify(content)]); |
||||
eleLink.href = URL.createObjectURL(blob); |
||||
document.body.appendChild(eleLink); |
||||
eleLink.click(); |
||||
document.body.removeChild(eleLink); |
||||
} else { |
||||
this.$message.warning("暂无数据可导出!"); |
||||
} |
||||
}, |
||||
help() { |
||||
window.open("https://github.com/qiaolufei/KG-Editor/issues/new"); |
||||
}, |
||||
// 模拟组合键触发函数 |
||||
keyCodeForEvent() { |
||||
let code = 0; |
||||
let code2 = 0; |
||||
let _this = this; |
||||
document.onkeydown = function(e) { |
||||
let evn = e || event; |
||||
let key = evn.keyCode || evn.which || evn.charCode; |
||||
if (key === 17) { |
||||
code = 17; |
||||
} |
||||
if (key === 90) { |
||||
code2 = 90; |
||||
} |
||||
if (key === 8) { |
||||
code2 = 8; |
||||
} |
||||
if (key === 67) { |
||||
code2 = 67; |
||||
} |
||||
if (key === 86) { |
||||
code2 = 86; |
||||
} |
||||
if (code === 17 && code2 === 90) { |
||||
_this.revoke(); |
||||
code = 0; |
||||
code2 = 0; |
||||
} |
||||
if (code === 17 && code2 === 8) { |
||||
_this.delete(); |
||||
code = 0; |
||||
code2 = 0; |
||||
} |
||||
if (code === 17 && code2 === 67) { |
||||
_this.copy(); |
||||
code = 0; |
||||
code2 = 0; |
||||
} |
||||
if (code === 17 && code2 === 86) { |
||||
_this.paste(); |
||||
code = 0; |
||||
code2 = 0; |
||||
} |
||||
}; |
||||
document.onkeyup = function(e) { |
||||
if (e.keyCode === 17) { |
||||
code = 0; |
||||
} |
||||
if (e.keyCode === 13) { |
||||
code2 = 0; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="less"> |
||||
.toolbar { |
||||
position: fixed; |
||||
background: #fff; |
||||
width: 100%; |
||||
z-index: 999; |
||||
} |
||||
.v-modal { |
||||
display: none; |
||||
} |
||||
.el-select .el-input.is-focus .el-input__inner { |
||||
border-color: #35495e; |
||||
} |
||||
.el-select-dropdown__item.selected { |
||||
color: #35495e; |
||||
} |
||||
.el-select .el-input__inner:focus { |
||||
border-color: #35495e; |
||||
} |
||||
.el-input__inner:focus { |
||||
border-color: #35495e; |
||||
} |
||||
.el-scrollbar__wrap { |
||||
overflow-x: hidden; |
||||
} |
||||
</style> |
@ -0,0 +1,627 @@ |
||||
<template> |
||||
<div class="toolbar"> |
||||
<div class="el-row"> |
||||
<div class="el-col" v-for="(item, index) in tools" :key="index"> |
||||
<el-tooltip |
||||
class="item-tooltip" |
||||
effect="dark" |
||||
:content="item.tip" |
||||
placement="bottom" |
||||
> |
||||
<el-button @click="comment_event(item.event)" type="text"> |
||||
<el-icon style="color: #000"> |
||||
<component :is="item.icon"></component> |
||||
</el-icon> |
||||
</el-button> |
||||
</el-tooltip> |
||||
<div |
||||
v-if="index === 8" |
||||
style="width: 50px; font-size: 14px; text-align: center" |
||||
> |
||||
{{ SIZE }}% |
||||
</div> |
||||
<template v-if="index === 10"> |
||||
<el-select |
||||
style="width: 120px; margin: 0 10px" |
||||
v-model="layout" |
||||
@change="changeLayout" |
||||
size="mini" |
||||
placeholder="请选择" |
||||
> |
||||
<el-option |
||||
v-for="item in layouts" |
||||
:label="item.label" |
||||
:value="item.value" |
||||
:key="item.value" |
||||
></el-option> |
||||
</el-select> |
||||
</template> |
||||
<el-divider |
||||
v-if="index !== 8 && index !== 10" |
||||
direction="vertical" |
||||
></el-divider> |
||||
</div> |
||||
</div> |
||||
<el-dialog title="上传txt文件" v-model="dialogVisible" width="50%"> |
||||
<!-- 此处省略了部分内容,可根据需要添加 --> |
||||
<pre style="color: #000"> |
||||
{ |
||||
"nodes":[ |
||||
{"id": "node1", "label": "luffy"}, |
||||
{"id": "node2", "label": "24岁"}, |
||||
{"id": "node3", "label": "62kg"} |
||||
... |
||||
], |
||||
"edges":[ |
||||
{"source": "node1", "target": "node2", "label": "年龄"}, |
||||
{"source": "node1", "target": "node3", "label": "体重"} |
||||
... |
||||
] |
||||
} |
||||
</pre> |
||||
<div style="text-align: center"> |
||||
<el-upload |
||||
drag |
||||
:limit="1" |
||||
action="https://jsonplaceholder.typicode.com/posts/" |
||||
ref="upload" |
||||
accept=".txt" |
||||
:close-on-click-modal="false" |
||||
:file-list="fileList" |
||||
:on-success="onSuccess" |
||||
:on-remove="onRemove" |
||||
> |
||||
<i class="el-icon-upload"></i> |
||||
<div class="el-upload__text"> |
||||
将文件拖到此处,或 |
||||
<em>点击上传</em> |
||||
</div> |
||||
<div class="el-upload__tip" slot="tip"> |
||||
上传txt文件,且只能上传 1 个文件 |
||||
</div> |
||||
</el-upload> |
||||
</div> |
||||
<span slot="footer" class="dialog-footer"> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
<el-button type="primary" @click="submitData">确 定</el-button> |
||||
</span> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
import { ref, defineProps, onMounted, watch } from 'vue' |
||||
import defaultNode from '../default/default_node' |
||||
import defaultEdge from '../default/default_edge' |
||||
import { isNullAndEmpty, objectJS } from '@/utils/commen' |
||||
import editAtlasStore from '@/store/module/editAtlas' |
||||
import { Back, Refresh, CopyDocument } from '@element-plus/icons-vue' |
||||
import { ElMessage } from 'element-plus' |
||||
import { useRouter } from 'vue-router' |
||||
const $router = useRouter() |
||||
const AtlasStore = editAtlasStore() |
||||
const props = defineProps({ |
||||
size: { |
||||
type: Number, |
||||
default: 100, |
||||
}, |
||||
selectedNodeId: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
selectedEdgeId: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
graph: { |
||||
type: Object, |
||||
default: () => {}, |
||||
}, |
||||
}) |
||||
const $emit = defineEmits(['update:selectedEdgeId', 'update:selectedNodeId']) |
||||
const tools = ref([ |
||||
{ icon: 'Back', tip: '撤销 Ctrl+Z', event: 'revoke' }, |
||||
{ icon: 'Refresh', tip: '重做', event: 'restore' }, |
||||
{ icon: 'CopyDocument', tip: '复制 Ctrl+C', event: 'copy' }, |
||||
{ icon: 'DocumentCopy', tip: '粘贴 Ctrl+V', event: 'paste' }, |
||||
{ |
||||
icon: 'Delete', |
||||
tip: '删除 Ctrl+Backspace', |
||||
event: 'DELEDT', |
||||
}, |
||||
{ icon: 'Top', tip: '置于顶层', event: 'onTop' }, |
||||
{ icon: 'Bottom', tip: '置于底层', event: 'onBottom' }, |
||||
{ icon: 'ZoomIn', tip: '放大', event: 'plus' }, |
||||
{ icon: 'ZoomOut', tip: '缩小', event: 'minus' }, |
||||
{ icon: 'FullScreen', tip: '适应画布', event: 'adaptCanvas' }, |
||||
{ icon: 'UploadFilled', tip: '导入文件', event: 'importFile' }, |
||||
{ icon: 'PictureFilled', tip: '导出图片', event: 'saveImage' }, |
||||
{ icon: 'DocumentRemove', tip: '导出JSON', event: 'saveJson' }, |
||||
{ icon: 'FolderChecked', tip: '保存', event: 'help' }, |
||||
]) |
||||
|
||||
const layout = ref('random') |
||||
const layouts = ref([ |
||||
{ label: '随机布局', value: 'random' }, |
||||
{ label: '力导向布局', value: 'force' }, |
||||
{ label: 'Fruchterman布局', value: 'fruchterman' }, |
||||
{ label: '环形布局', value: 'circular' }, |
||||
{ label: '辐射布局', value: 'radial' }, |
||||
{ label: '层次布局', value: 'dagre' }, |
||||
{ label: '同心圆布局', value: 'concentric' }, |
||||
{ label: '网格布局', value: 'grid' }, |
||||
]) |
||||
|
||||
const dialogVisible = ref(false) |
||||
const uploadData = ref({}) |
||||
const fileList = ref([]) |
||||
|
||||
const changeLayout = () => { |
||||
// 更新布局逻辑... |
||||
props.graph.updateLayout({ |
||||
type: layout.value, |
||||
}) |
||||
} |
||||
|
||||
const onSuccess = (res, file, fileList) => { |
||||
console.log(file.raw, 'file.raw') |
||||
// 处理文件上传成功逻辑... |
||||
let reader = new FileReader() |
||||
reader.readAsText(file.raw) |
||||
reader.onload = (e) => { |
||||
uploadData.value = [] |
||||
const str = String(e.target.result).replace(/\s*/g, '') |
||||
if (str === '' || str === null) { |
||||
// this.$message.error("文档数据不能为空!"); |
||||
ElMessage({ |
||||
message: '文档数据不能为空!', |
||||
type: 'error', |
||||
}) |
||||
} else { |
||||
try { |
||||
uploadData.value = JSON.parse(str) |
||||
} catch (err) { |
||||
// this.$message.error(String(err)); |
||||
ElMessage({ |
||||
message: String(err), |
||||
type: 'error', |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
const onRemove = (file) => { |
||||
// 处理文件移除逻辑... |
||||
fileList = [] |
||||
} |
||||
|
||||
const comment_event = (event) => { |
||||
// 处理工具按钮点击事件... |
||||
switch (event) { |
||||
case 'revoke': |
||||
revoke() // 撤销操作 |
||||
break |
||||
case 'restore': |
||||
restore() // 重做操作 |
||||
break |
||||
case 'copy': |
||||
// 复制操作 |
||||
copy() |
||||
break |
||||
case 'paste': |
||||
// 粘贴操作 |
||||
paste() |
||||
break |
||||
case 'DELEDT': |
||||
// 删除操作 |
||||
DELEDT() |
||||
break |
||||
case 'onTop': |
||||
// 置于顶层操作 |
||||
onTop() |
||||
break |
||||
case 'onBottom': |
||||
// 置于底层操作 |
||||
onBottom() |
||||
break |
||||
case 'plus': |
||||
plus() |
||||
// 放大操作 |
||||
break |
||||
case 'minus': |
||||
minus() |
||||
// 缩小操作 |
||||
break |
||||
case 'adaptCanvas': |
||||
adaptCanvas() |
||||
// 适应画布操作 |
||||
break |
||||
case 'importFile': |
||||
// 导入文件操作 |
||||
importFile() |
||||
break |
||||
case 'saveImage': |
||||
// 导出图片操作 |
||||
saveImage() |
||||
break |
||||
case 'saveJson': |
||||
// 导出JSON操作 |
||||
saveJson() |
||||
break |
||||
case 'help': |
||||
// 帮助操作 |
||||
help() |
||||
break |
||||
default: |
||||
// 默认情况下的处理 |
||||
return |
||||
break |
||||
} |
||||
} |
||||
const revoke = () => { |
||||
// 撤销逻辑... |
||||
|
||||
let log = AtlasStore.log |
||||
if (log.length === 0) return |
||||
|
||||
console.log(log) |
||||
let action = log[0].action |
||||
switch (action) { |
||||
case 'addNode': |
||||
props.graph.removeItem(log[0].data.id) |
||||
AtlasStore.deleteNode(log[0].data) |
||||
// this.$store.commit("deleteNode", log[0].data); |
||||
|
||||
break |
||||
case 'deleteNode': |
||||
props.graph.addItem('node', log[0].data) |
||||
AtlasStore.addNode(log[0].data) |
||||
// this.$store.commit('addNode', log[0].data) |
||||
break |
||||
case 'addEdge': |
||||
props.graph.removeItem(log[0].data.id) |
||||
AtlasStore.deleteEdge(log[0].data) |
||||
// this.$store.commit('deleteEdge', log[0].data) |
||||
break |
||||
case 'deleteEdge': |
||||
props.graph.addItem('edge', log[0].data) |
||||
AtlasStore.addEdge(log[0].data) |
||||
// this.$store.commit('addEdge', log[0].data) |
||||
break |
||||
} |
||||
AtlasStore.deleteLog() |
||||
// this.$store.commit("deleteLog"); |
||||
} |
||||
const restore = () => { |
||||
props.graph.clear() |
||||
AtlasStore.clearData() |
||||
// this.$store.commit("clearData"); |
||||
} |
||||
// 省略其他方法... |
||||
const cloneNode = ref({}) |
||||
const copy = () => { |
||||
if (props.selectedNodeId === '') { |
||||
// this.$message.error("未选择节点!"); |
||||
ElMessage({ |
||||
message: '未选择节点!', |
||||
type: 'error', |
||||
}) |
||||
} else { |
||||
AtlasStore.dataList.nodes.forEach((node) => { |
||||
if (node.id === props.selectedNodeId) { |
||||
cloneNode.value = objectJS.deepClone(node) |
||||
// this.$message.success("复制成功"); |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
const paste = () => { |
||||
if (props.selectedNodeId === '') { |
||||
// this.$message.error("未选择节点!"); |
||||
ElMessage({ |
||||
message: '未选择节点!', |
||||
type: 'error', |
||||
}) |
||||
} else { |
||||
cloneNode.value.id = 'node' + (AtlasStore.dataList.nodes.length + 1) |
||||
cloneNode.value.label = AtlasStore.dataList.nodes.length + 1 |
||||
cloneNode.value.x = cloneNode.value.x + 20 |
||||
cloneNode.value.y = cloneNode.value.y + 20 |
||||
let obj = objectJS.deepClone(cloneNode.value) |
||||
props.graph.addItem('node', obj) |
||||
AtlasStore.addNode(obj) |
||||
// this.$store.commit("addNode", obj); |
||||
// this.$message.success("粘贴成功"); |
||||
} |
||||
} |
||||
const DELEDT = () => { |
||||
if (props.selectedEdgeId === '' && props.selectedNodeId === '') { |
||||
// this.$message.error('未选择元素!') |
||||
ElMessage({ |
||||
message: '未选择元素!', |
||||
type: 'error', |
||||
}) |
||||
} else if (props.selectedEdgeId !== '') { |
||||
let obj = {} |
||||
props.graph.getEdges().forEach((edge) => { |
||||
if (edge._cfg.id === props.selectedEdgeId) { |
||||
obj = edge._cfg.model |
||||
} |
||||
}) |
||||
// 操作记录 |
||||
let logObj = { |
||||
id: String('log' + (AtlasStore.log.length + 1)), |
||||
action: 'deleteEdge', |
||||
data: obj, |
||||
} |
||||
AtlasStore.addLog(logObj) |
||||
// this.$store.commit('addLog', logObj) |
||||
props.graph.removeItem(props.selectedEdgeId) |
||||
AtlasStore.deleteEdge(props.selectedEdgeId) |
||||
// this.$store.commit('deleteEdge', props.selectedEdgeId) |
||||
$emit('update:selectedEdgeId', '') |
||||
} else if (props.selectedNodeId !== '') { |
||||
let obj = {} |
||||
props.graph.getNodes().forEach((node) => { |
||||
if (node._cfg.id === props.selectedNodeId) { |
||||
obj = node._cfg.model |
||||
} |
||||
}) |
||||
// 操作记录 |
||||
let logObj = { |
||||
id: String('log' + (AtlasStore.log.length + 1)), |
||||
action: 'deleteNode', |
||||
data: obj, |
||||
} |
||||
AtlasStore.addLog(logObj) |
||||
// this.$store.commit('addLog', logObj) |
||||
props.graph.removeItem(props.selectedNodeId) |
||||
AtlasStore.deleteNode(props.selectedNodeId) |
||||
// this.$store.commit('deleteNode', props.selectedNodeId) |
||||
$emit('selectedNodeId', '') |
||||
} |
||||
} |
||||
const onTop = () => { |
||||
if (props.selectedEdgeId === '' && props.selectedNodeId === '') { |
||||
// this.$message.error("未选择元素!"); |
||||
ElMessage({ |
||||
message: '未选择元素!', |
||||
type: 'error', |
||||
}) |
||||
} else if (props.selectedEdgeId !== '') { |
||||
props.graph.getEdges().forEach((edge) => { |
||||
if (edge._cfg.id === props.selectedEdgeId) { |
||||
edge.toFront() |
||||
} |
||||
}) |
||||
} else if (props.selectedNodeId !== '') { |
||||
props.graph.getNodes().forEach((node) => { |
||||
if (node._cfg.id === props.selectedNodeId) { |
||||
node.toFront() |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
const onBottom = () => { |
||||
if (props.selectedEdgeId === '' && props.selectedNodeId === '') { |
||||
// this.$message.error("未选择元素!"); |
||||
ElMessage({ |
||||
message: '未选择元素!', |
||||
type: 'error', |
||||
}) |
||||
} else if (props.selectedEdgeId !== '') { |
||||
props.graph.getEdges().forEach((edge) => { |
||||
if (edge._cfg.id === props.selectedEdgeId) { |
||||
edge.toBack() |
||||
} |
||||
}) |
||||
} else if (props.selectedNodeId !== '') { |
||||
props.graph.getNodes().forEach((node) => { |
||||
if (node._cfg.id === props.selectedNodeId) { |
||||
node.toBack() |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
const help = () => { |
||||
$router.push('/professionalProfile') |
||||
} |
||||
watch( |
||||
() => props.size, |
||||
(newVAl) => { |
||||
console.log(newVAl) |
||||
SIZE.value = newVAl |
||||
}, |
||||
) |
||||
const SIZE = ref(null) |
||||
onMounted(() => { |
||||
SIZE.value = props.size |
||||
}) |
||||
const plus = () => { |
||||
const currentZoom = Number(props.graph.getZoom()) |
||||
props.graph.zoomTo(currentZoom + 0.1) |
||||
SIZE.value = Number(((currentZoom + 0.1) * 100).toFixed(0)) |
||||
} |
||||
const minus = () => { |
||||
const currentZoom = Number(props.graph.getZoom()) |
||||
props.graph.zoomTo(currentZoom - 0.1) |
||||
SIZE.value = Number(((currentZoom - 0.1) * 100).toFixed(0)) |
||||
} |
||||
const adaptCanvas = () => { |
||||
props.graph.fitView(20) |
||||
props.graph.fitCenter() |
||||
props.graph.zoomTo(1) |
||||
} |
||||
const importFile = () => { |
||||
dialogVisible.value = true |
||||
} |
||||
const initializeObj = (source, target) => { |
||||
// 初始化node/edge,防止上传文件数据中缺少参数 |
||||
for (let key in source) { |
||||
if (!target.hasOwnProperty(key)) { |
||||
target[key] = source[key] |
||||
} |
||||
if (typeof source[key] === 'object') { |
||||
initializeObj(source[key], target[key]) |
||||
} |
||||
} |
||||
return target |
||||
} |
||||
|
||||
const submitData = () => { |
||||
const dataList = uploadData.value |
||||
console.log(dataList) |
||||
if (!isNullAndEmpty(dataList.nodes)) { |
||||
dataList.nodes.forEach((item) => { |
||||
initializeObj(defaultNode, item) |
||||
props.graph.addItem('node', item) |
||||
AtlasStore.addNode(item) |
||||
// _this.$store.commit("addNode", item); |
||||
}) |
||||
} |
||||
if (!isNullAndEmpty(dataList.links)) { |
||||
dataList.links.forEach((item) => { |
||||
initializeObj(defaultEdge, item) |
||||
props.graph.addItem('edge', item) |
||||
AtlasStore.addEdge(item) |
||||
// _this.$store.commit("addEdge", item); |
||||
}) |
||||
} |
||||
props.graph.updateLayout({ |
||||
type: 'random', |
||||
}) |
||||
dialogVisible.value = false |
||||
} |
||||
const saveImage = () => { |
||||
if (!isNullAndEmpty(AtlasStore.dataList.nodes)) { |
||||
props.graph.downloadFullImage('graph', 'image/png', { |
||||
backgroundColor: '#fff', |
||||
padding: [15, 15, 15, 15], |
||||
}) |
||||
} else { |
||||
// this.$message.warning("画布为空!"); |
||||
ElMessage({ |
||||
message: '画布为空!', |
||||
type: 'warning', |
||||
}) |
||||
} |
||||
} |
||||
const saveJson = () => { |
||||
if (!isNullAndEmpty(AtlasStore.dataList.nodes)) { |
||||
const dataList = AtlasStore.dataList |
||||
const content = {} |
||||
console.log(dataList) |
||||
content.nodes = dataList.nodes.map((x) => { |
||||
return { |
||||
id: x.id, |
||||
label: x.label, |
||||
color: x.style.fill, |
||||
} |
||||
}) |
||||
content.links = dataList.edges.map((x) => { |
||||
return { |
||||
source: x.source, |
||||
target: x.target, |
||||
label: x.label, |
||||
} |
||||
}) |
||||
// content.nodes = dataList.nodes |
||||
// content.links = dataList.edges |
||||
var eleLink = document.createElement('a') |
||||
eleLink.download = 'kg.json' |
||||
eleLink.style.display = 'none' |
||||
var blob = new Blob([JSON.stringify(content)]) |
||||
eleLink.href = URL.createObjectURL(blob) |
||||
document.body.appendChild(eleLink) |
||||
eleLink.click() |
||||
document.body.removeChild(eleLink) |
||||
} else { |
||||
// this.$message.warning("暂无数据可导出!"); |
||||
ElMessage({ |
||||
message: '暂无数据可导出!', |
||||
type: 'warning', |
||||
}) |
||||
} |
||||
} |
||||
const keyCodeForEvent = () => { |
||||
// 模拟组合键触发函数... |
||||
let code = 0 |
||||
let code2 = 0 |
||||
document.onkeydown = function (e) { |
||||
let evn = e || event |
||||
let key = evn.keyCode || evn.which || evn.charCode |
||||
if (key === 17) { |
||||
code = 17 |
||||
} |
||||
if (key === 90) { |
||||
code2 = 90 |
||||
} |
||||
if (key === 8) { |
||||
code2 = 8 |
||||
} |
||||
if (key === 67) { |
||||
code2 = 67 |
||||
} |
||||
if (key === 86) { |
||||
code2 = 86 |
||||
} |
||||
if (code === 17 && code2 === 90) { |
||||
revoke() |
||||
code = 0 |
||||
code2 = 0 |
||||
} |
||||
if (code === 17 && code2 === 8) { |
||||
DELETE() |
||||
code = 0 |
||||
code2 = 0 |
||||
} |
||||
if (code === 17 && code2 === 67) { |
||||
copy() |
||||
code = 0 |
||||
code2 = 0 |
||||
} |
||||
if (code === 17 && code2 === 86) { |
||||
paste() |
||||
code = 0 |
||||
code2 = 0 |
||||
} |
||||
} |
||||
document.onkeyup = function (e) { |
||||
if (e.keyCode === 17) { |
||||
code = 0 |
||||
} |
||||
if (e.keyCode === 13) { |
||||
code2 = 0 |
||||
} |
||||
} |
||||
} |
||||
|
||||
keyCodeForEvent() // 组件挂载时执行组合键绑定 |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.toolbar { |
||||
position: fixed; |
||||
top: 0; |
||||
background: #fff; |
||||
width: 100%; |
||||
z-index: 999; |
||||
box-shadow: |
||||
0 2px 4px -1px rgba(0, 0, 0, 0.2), |
||||
0 4px 5px 0 rgba(0, 0, 0, 0.14), |
||||
0 1px 10px 0 rgba(0, 0, 0, 0.12); |
||||
padding: 5px 10px; |
||||
} |
||||
.item-tooltip { |
||||
color: #c0c4cc; |
||||
} |
||||
.el-row { |
||||
margin-left: -5px; |
||||
margin-right: -5px; |
||||
} |
||||
.el-col { |
||||
padding: 0 5px; |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
</style> |
@ -0,0 +1,25 @@ |
||||
export default{ |
||||
type: 'line', |
||||
style: { |
||||
stroke: '#C0C4CC', // 颜色
|
||||
lineWidth: 1, // 宽度
|
||||
lineAppendWidth: 0, // 鼠标检测宽度
|
||||
startArrow: false, // 开始箭头
|
||||
endArrow: true, // 结束箭头
|
||||
cursor: 'pointer' |
||||
}, |
||||
label: '', |
||||
labelCfg: { |
||||
position: 'middle', // 位置
|
||||
refX: 0, // X偏移量
|
||||
refY: 0, // Y偏移量
|
||||
autoRotate: true, // 跟随边旋转
|
||||
style: { |
||||
fill: '#4682B4', // 文本颜色
|
||||
fontWeight: 400, |
||||
opacity: 1, // 文本透明度
|
||||
fontFamily: '微软雅黑', // 文本字体
|
||||
fontSize: 14 // 文本字体大小
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
export default{ |
||||
size: [60, 60], |
||||
linkPoints: { // 锚点
|
||||
bottom: false, |
||||
stroke: '#4682B4', |
||||
fill: '#fff', |
||||
size: 10 |
||||
}, |
||||
style: { |
||||
fill: '#4682B4', // 填充色
|
||||
stroke: '#4682B4', // 描边颜色
|
||||
lineWidth: 1, // 描边宽度
|
||||
shadowColor: '#909399', // 阴影颜色
|
||||
shadowBlur: 10, // 阴影范围
|
||||
shadowOffsetX: 3, // 阴影 x 方向偏移量
|
||||
shadowOffsetY: 3, // 阴影 y 方向偏移量
|
||||
cursor: 'pointer' |
||||
}, |
||||
labelCfg: { |
||||
position: 'center', // 文本位置
|
||||
style: { |
||||
fill: '#fff', // 文本颜色
|
||||
fontWeight: 400, |
||||
opacity: 1, // 文本透明度
|
||||
fontFamily: '微软雅黑', // 文本字体
|
||||
fontSize: 18 // 文本字体大小
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,227 @@ |
||||
<template> |
||||
<div class="index"> |
||||
<Toolbar |
||||
v-if="flog" |
||||
:size="size" |
||||
:graph="graph" |
||||
v-model:selectedNodeId="selectedNodeId" |
||||
v-model:selectedEdgeId="selectedEdgeId" |
||||
></Toolbar> |
||||
<div class="index__main"> |
||||
<div |
||||
ref="G6REF" |
||||
id="G6" |
||||
class="index__main-left" |
||||
:style="{ width: drawer ? '78%' : '100%' }" |
||||
></div> |
||||
<div class="index__main-right" :style="{ width: drawer ? '25%' : '0' }"> |
||||
<Sidebar |
||||
ref="sidebar" |
||||
:graph="graph" |
||||
:selectedNodeId="selectedNodeId" |
||||
:selectedEdgeId="selectedEdgeId" |
||||
></Sidebar> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
import Toolbar from './components/toolbar.vue' |
||||
import Sidebar from './components/sidebar.vue' |
||||
// import G6 from '@antv/g6' |
||||
import defaultNode from './default/default_node' |
||||
import defaultEdge from './default/default_edge' |
||||
import addNode from './actions/add_node' |
||||
import hoverNode from './actions/hover_node' |
||||
import addEdge from './actions/add_edge' |
||||
import selectEdge from './actions/select_edge' |
||||
import { reactive, ref, onMounted } from 'vue' |
||||
import editAtlasStore from '@/store/module/editAtlas' |
||||
import { useRoute } from 'vue-router' |
||||
const $route = useRoute() |
||||
const flog = ref(false) |
||||
const AtlasStore = editAtlasStore() |
||||
let drawer = ref(true) |
||||
let graph = reactive(null) |
||||
let selectedNodeId = ref('') |
||||
let selectedEdgeId = ref('') |
||||
let item = {} |
||||
let addingEdge = true |
||||
let edge = null |
||||
let size = ref(100) |
||||
let G6REF = ref(null) |
||||
let sidebar = ref(null) |
||||
let initG6 = () => { |
||||
G6.registerBehavior('hover-node', hoverNode) |
||||
// 双击添加节点 |
||||
G6.registerBehavior('click-add-node', addNode) |
||||
// 添加连线 |
||||
G6.registerBehavior('click-add-edge', addEdge) |
||||
// console.log(selectEdge,'selectEdge'); |
||||
G6.registerBehavior('select-edge', selectEdge) |
||||
let grid = new G6.Grid() |
||||
// 缩略图 |
||||
console.log(sidebar.value.$refs.minimap, 'sidebar.value') |
||||
let minimap = new G6.Minimap({ |
||||
container: sidebar.value.$refs.minimap, |
||||
size: [ |
||||
sidebar.value.$refs.minimap.offsetWidth - 8, |
||||
sidebar.value.$refs.minimap.offsetHeight, |
||||
], |
||||
}) |
||||
graph = new G6.Graph({ |
||||
container: 'G6', |
||||
width: G6REF.value.offsetWidth, |
||||
height: G6REF.value.offsetHeight, |
||||
plugins: [grid, minimap], |
||||
layout: { |
||||
type: 'force', |
||||
nodeStrength: -30, |
||||
preventOverlap: true, |
||||
nodeSize: 40, |
||||
edgeStrength: 0.1, |
||||
linkDistance: 100, |
||||
}, |
||||
modes: { |
||||
default: [ |
||||
'hover-node', |
||||
'zoom-canvas', // 缩放canvas |
||||
'drag-canvas', // 拖拽canvas |
||||
{ |
||||
type: 'drag-node', // 拖拽node |
||||
}, |
||||
'click-add-node', |
||||
'click-select', |
||||
'select-edge', |
||||
], |
||||
addEdge: [ |
||||
'click-add-edge', |
||||
'hover-node', |
||||
'zoom-canvas', |
||||
'drag-canvas', |
||||
'click-add-node', |
||||
], |
||||
}, |
||||
defaultNode: defaultNode, |
||||
defaultEdge: defaultEdge, |
||||
edgeStateStyles: { |
||||
hover: { |
||||
stroke: '#409eff', // 颜色 |
||||
}, |
||||
selected: { |
||||
stroke: '#409eff', // 颜色 |
||||
}, |
||||
}, |
||||
nodeStateStyles: { |
||||
selected: { |
||||
stroke: '#409eff', |
||||
lineWidth: 1, |
||||
fill: '#409eff', |
||||
}, |
||||
}, |
||||
}) |
||||
console.log(graph,'graph'); |
||||
graph.read(AtlasStore.dataList) |
||||
graph.on('nodeselectchange', (e) => { |
||||
console.log(e); |
||||
item = e |
||||
selectedEdgeId.value = '' |
||||
selectedNodeId.value = e.select ? e.selectedItems.nodes[0]._cfg.id : '' |
||||
console.log(selectedNodeId.value,'selectedNodeId'); |
||||
}) |
||||
graph.on('edge:click', (e) => { |
||||
|
||||
selectedNodeId.value = '' |
||||
selectedEdgeId.value = e.item._cfg.id |
||||
}) |
||||
graph.on('canvas:click', (e) => { |
||||
|
||||
selectedNodeId.value = '' |
||||
selectedEdgeId.value = '' |
||||
}) |
||||
graph.on('viewportchange', (e) => { |
||||
if (e.action === 'zoom') { |
||||
size.value = Number((Number(graph.getZoom()) * 100).toFixed(0)) |
||||
console.log(size.value); |
||||
} |
||||
}) |
||||
} |
||||
onMounted(async() => { |
||||
const json = await fetch('../../../public/data.json') |
||||
json.json().then(res => { |
||||
console.log(res); |
||||
const nodesData = res.nodes.map((item) => { |
||||
return{ |
||||
id:item.id, |
||||
label:item.label, |
||||
style:{ |
||||
fill:item.color |
||||
} |
||||
} |
||||
}) |
||||
AtlasStore.getData({nodes:nodesData,edges:res.links}) |
||||
initG6() |
||||
flog.value = true |
||||
}) |
||||
console.log($route |
||||
,'111'); |
||||
|
||||
}) |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.index { |
||||
width: 100%; |
||||
&__main { |
||||
width: 100%; |
||||
margin-top: 40px; |
||||
display: flex; |
||||
flex-direction: row; |
||||
box-sizing: border-box; |
||||
&-left { |
||||
height: 88vh; |
||||
border: 2px solid #3333; |
||||
margin: 2vw 2vw 0 2vw; |
||||
box-shadow: |
||||
0 2px 4px rgba(0, 0, 0, 0.12), |
||||
0 0 6px rgba(0, 0, 0, 0.04); |
||||
} |
||||
&-right { |
||||
height: 88vh; |
||||
border: 1px solid #3333; |
||||
margin-top: 2vw; |
||||
margin-right: 10px; |
||||
overflow-y: scroll; |
||||
box-shadow: |
||||
0 2px 4px rgba(0, 0, 0, 0.12), |
||||
0 0 6px rgba(0, 0, 0, 0.04); |
||||
&__pot { |
||||
position: absolute; |
||||
width: 2vw; |
||||
height: 2vw; |
||||
background: #35495e; |
||||
border-radius: 1vw; |
||||
clip: rect(0px 1vw 2vw 0px); |
||||
box-shadow: |
||||
0 2px 4px rgba(0, 0, 0, 0.12), |
||||
0 0 6px rgba(0, 0, 0, 0.04); |
||||
top: 50%; |
||||
cursor: pointer; |
||||
z-index: 1000 !important; |
||||
} |
||||
&__pot:hover { |
||||
background: #dcdfe6; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.g6-tooltip { |
||||
padding: 10px 6px; |
||||
color: #444; |
||||
background-color: rgba(255, 255, 255, 0.9); |
||||
border: 1px solid #e2e2e2; |
||||
border-radius: 4px; |
||||
} |
||||
.g6-grid-container{ |
||||
height: 100%;; |
||||
} |
||||
</style> |
@ -0,0 +1,14 @@ |
||||
<template> |
||||
<div> |
||||
测试三级路由 |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang='ts' setup> |
||||
import { } from 'vue' |
||||
|
||||
</script> |
||||
|
||||
<style lang='scss' scoped> |
||||
|
||||
</style> |
@ -0,0 +1,22 @@ |
||||
<template> |
||||
教研成果 |
||||
<button @click="goTo">跳转</button> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { useRouter } from 'vue-router' |
||||
const $router = useRouter() |
||||
const goTo = () => { |
||||
$router.push('test') |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.view-container { |
||||
height: 100vh; |
||||
.container { |
||||
width: $base-container-width; |
||||
margin: 0 auto; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,17 @@ |
||||
<template> |
||||
|
||||
<div class="container">测试</div> |
||||
<div>当前位置:<span v-for="item in $route.matched" :key="item.path" v-show="item.meta.title">{{ item.meta.title }} > </span></div> |
||||
|
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import {} from 'vue' |
||||
import { useRoute } from 'vue-router' |
||||
const $route = useRoute() |
||||
console.log($route) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
</style> |
@ -0,0 +1,68 @@ |
||||
<template> |
||||
<div class="view-container"> |
||||
<div class="banner"></div> |
||||
<div class="container"> |
||||
<div class="graph-box"> |
||||
<div id="3d-graph"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { onMounted } from 'vue' |
||||
import ForceGraph3D from '3d-force-graph' |
||||
onMounted(() => { |
||||
// Random tree |
||||
const N = 50 |
||||
const gData = { |
||||
nodes: [...Array(N).keys()].map((i) => ({ |
||||
id: i, |
||||
color: i % 2 === 0 ? 'pink' : 'skyblue', // 根据奇偶性设置颜色 |
||||
label: String(i), // 将节点的ID作为标签 |
||||
})), |
||||
|
||||
links: [...Array(N).keys()] |
||||
.filter((id) => id) |
||||
.map((id) => ({ |
||||
source: id, |
||||
target: Math.round(Math.random() * (id - 1)), |
||||
color: 'red', // 设置连接线颜色为红色 |
||||
|
||||
})), |
||||
} |
||||
|
||||
const Graph = ForceGraph3D()( |
||||
document.getElementById('3d-graph') as HTMLElement, |
||||
) |
||||
// .jsonUrl('./data.json') |
||||
.graphData(gData) |
||||
.backgroundColor('#f5f7fd') |
||||
.width(1200) |
||||
.nodeLabel('label') // 设置节点的标签显示方式为节点对象中的label属性 |
||||
|
||||
|
||||
}) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.node-label { |
||||
font-size: 12px; |
||||
padding: 1px 4px; |
||||
border-radius: 4px; |
||||
background-color: rgba(0,0,0,0.5); |
||||
user-select: none; |
||||
} |
||||
.view-container { |
||||
height: 100vh; |
||||
.container { |
||||
width: $base-container-width; |
||||
margin: 0 auto; |
||||
} |
||||
.graph-box { |
||||
display: flex; |
||||
justify-content: center; |
||||
width: 100%; |
||||
} |
||||
} |
||||
</style> |
@ -1,20 +1,160 @@ |
||||
<template> |
||||
<div class="view-container"> |
||||
<div class="container">专业概括</div> |
||||
<div class="banner"></div> |
||||
<div class="container"> |
||||
<div class="title">知识图谱</div> |
||||
<div class="graph-box"> |
||||
<div class="graph"> |
||||
<div id="3d-graph"></div> |
||||
</div> |
||||
<div class="edit-atlas" @click="goToEditAtlas">编辑图谱</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import {} from 'vue' |
||||
import { useRouter } from 'vue-router' |
||||
import { onMounted, ref } from 'vue' |
||||
import ForceGraph3D from '3d-force-graph' |
||||
//@ts-ignore |
||||
import { CSS2DRenderer, CSS2DObject,} from 'three/examples/jsm/renderers/CSS2DRenderer.js' |
||||
const $router = useRouter() |
||||
const jsonData = ref(null) |
||||
onMounted(() => { |
||||
const Graph = ForceGraph3D({ |
||||
extraRenderers: [new CSS2DRenderer()], |
||||
})(document.getElementById('3d-graph') as HTMLElement) |
||||
.jsonUrl('../../../public/data.json') |
||||
// .nodeAutoColorBy('group') |
||||
.nodeThreeObject((node: any) => { |
||||
const nodeEl = document.createElement('div') |
||||
nodeEl.textContent = node.label |
||||
nodeEl.style.color = '#333333' |
||||
nodeEl.style.borderRadius = '50%' |
||||
// console.log(node, 111, Graph.graphData().nodes) |
||||
return new CSS2DObject(nodeEl) |
||||
}) |
||||
.linkColor(() => '#dd92fd') // 设置连接线颜色为红色 |
||||
.backgroundColor('#f5f7fd') |
||||
.width(1332) |
||||
.height(1332) |
||||
.nodeColor((node: any) => { |
||||
return node.color |
||||
}) |
||||
.nodeRelSize(7) // 设置节点的相对大小为4 |
||||
.nodeResolution(20) |
||||
.linkDirectionalArrowLength(1) // 设置连接线箭头长度为3 |
||||
.linkDirectionalArrowRelPos(1) // 设置连接线箭头位置为连接线末端 |
||||
.nodeThreeObjectExtend(true) |
||||
.onNodeClick((node: any) => { |
||||
// Aim at node from outside it |
||||
// 目标距离 |
||||
const targetDistance = 200 // 设定一个一般的距离 |
||||
|
||||
// 计算相机新位置 |
||||
const distRatio = 1 + targetDistance / Math.hypot(node.x, node.y, node.z) |
||||
const newPos = { |
||||
x: node.x * distRatio, |
||||
y: node.y * distRatio, |
||||
z: node.z * distRatio, |
||||
} |
||||
|
||||
// 如果节点位于原点,则特殊处理 |
||||
if (node.x === 0 && node.y === 0 && node.z === 0) { |
||||
newPos.z = targetDistance // 将 z 轴设定为目标距离 |
||||
} |
||||
|
||||
// 移动相机至新位置 |
||||
Graph.cameraPosition( |
||||
newPos, // 新位置 |
||||
node, // 视角焦点 |
||||
3000, // 过渡时长 |
||||
) |
||||
|
||||
// 获取图表数据 |
||||
const graphData = Graph.graphData() |
||||
|
||||
// 遍历连接线,检查每条连接线的起点和终点是否与当前点击的节点相关联 |
||||
graphData.links.forEach((link: any) => { |
||||
// console.log(link); |
||||
|
||||
if (link.source.id === node.id || link.target.id === node.id) { |
||||
// 将与当前点击节点相关联的连接线颜色设置为红色 |
||||
// link.color = '#FF0000' // 设置连接线颜色为红色 |
||||
Graph.linkColor((item: any): any => { |
||||
if (item.source.id === node.id || item.target.id === node.id) { |
||||
return 'red' |
||||
} else { |
||||
return '#dd92fd' |
||||
} |
||||
}) |
||||
} else { |
||||
// Graph.linkColor(() => '#a4c7fe') // 设置连接线颜色为红色 |
||||
} |
||||
}) |
||||
|
||||
// 更新图形数据,以应用颜色变化 |
||||
Graph.graphData(graphData) |
||||
}) |
||||
}) |
||||
|
||||
const goToEditAtlas = () => { |
||||
console.log(jsonData.value) |
||||
|
||||
$router.push({ name: 'EditAtlas', params: { id: 123 } }) |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.node-label { |
||||
font-size: 12px; |
||||
padding: 1px 4px; |
||||
border-radius: 4px; |
||||
background-color: rgba(0, 0, 0, 0.5); |
||||
user-select: none; |
||||
} |
||||
|
||||
.view-container { |
||||
height: 100vh; |
||||
background-color: #2080f7; |
||||
// height: 100vh; |
||||
// background-color: #2080f7; |
||||
|
||||
.container { |
||||
width: $base-container-width; |
||||
margin: 0 auto; |
||||
background-color: #fff; |
||||
height: 100%; |
||||
} |
||||
.title { |
||||
font-size: 32px; |
||||
font-weight: 700; |
||||
text-align: center; |
||||
padding: 30px; |
||||
} |
||||
.graph-box { |
||||
position: relative; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
width: 100%; |
||||
height: 764px; |
||||
// border-radius: 50%; |
||||
background-color: #fff; |
||||
overflow: hidden; |
||||
.graph { |
||||
width: 1333px; |
||||
height: 1333px; |
||||
border-radius: 50%; |
||||
overflow: hidden; |
||||
} |
||||
.edit-atlas { |
||||
top: 0; |
||||
right: 5%; |
||||
position: absolute; |
||||
font-size: 16px; |
||||
color: #6da0ff; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
|
Loading…
Reference in new issue