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> |
<template> |
||||||
|
<!-- <section class="app-main"> |
||||||
|
<transition name="fade-transform" mode="out-in"> |
||||||
|
<router-view :key="$route.path" /> |
||||||
|
</transition> |
||||||
|
</section> --> |
||||||
<router-view></router-view> |
<router-view></router-view> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<script lang="ts" setup> |
<script lang="ts" setup> |
||||||
import {} from 'vue' |
import {} from 'vue' |
||||||
|
import {useRoute } from 'vue-router' |
||||||
|
const $route = useRoute() |
||||||
|
console.log( $route); |
||||||
|
|
||||||
|
|
||||||
</script> |
</script> |
||||||
|
|
||||||
<style lang="scss" scoped></style> |
<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'; |
@import './reset.scss'; |
||||||
.view-container{ |
.banner{ |
||||||
padding-top: 120px; |
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> |
<template> |
||||||
<div class="view-container"> |
<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> |
</div> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<script lang="ts" setup> |
<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> |
</script> |
||||||
|
|
||||||
<style lang="scss" scoped> |
<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 { |
.view-container { |
||||||
height: 100vh; |
// height: 100vh; |
||||||
background-color: #2080f7; |
// background-color: #2080f7; |
||||||
|
|
||||||
.container { |
.container { |
||||||
width: $base-container-width; |
width: $base-container-width; |
||||||
margin: 0 auto; |
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> |
</style> |
||||||
|
Loading…
Reference in new issue