582 lines
16 KiB
Vue
582 lines
16 KiB
Vue
<template>
|
||
<div>
|
||
<div class="tools-bar">
|
||
<el-popover
|
||
placement="bottom"
|
||
width="200"
|
||
class="el-po"
|
||
trigger="click">
|
||
<div ref="stencilContainer" class="x6-stencil"></div>
|
||
<i class="el-icon-s-operation opt-btn" slot="reference" ></i>
|
||
</el-popover>
|
||
</div>
|
||
<div class="content" :style="{ height: contentHeight }">
|
||
<!-- <div ref="stencilContainer" class="x6-stencil"></div> -->
|
||
<div class="middle-box">
|
||
<div
|
||
ref="container"
|
||
class="x6-content"
|
||
:style="{ height: contentHeight }"
|
||
></div>
|
||
<div
|
||
id="map-container"
|
||
class="mp-view"
|
||
:style="{ height: contentHeight }"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { Graph, Shape, Addon,Path } from '@antv/x6';
|
||
const { Stencil } = Addon
|
||
const { Rect, Circle } = Shape
|
||
import AMapLoader from '@amap/amap-jsapi-loader';
|
||
export default {
|
||
data() {
|
||
return {
|
||
contentHeight:'0px', //容器高度,x6高度
|
||
mapHeight: "200px", //地图高度
|
||
center: {lng: 0, lat: 0},//地图中心点变量
|
||
zoom: 3,//地图缩放变量
|
||
|
||
graph:undefined,//x6画布
|
||
stencil:undefined,//x6拖拽
|
||
|
||
projection:undefined,//地图projection参数
|
||
|
||
map:null,//高德地图
|
||
data : {
|
||
// 节点
|
||
nodes: [
|
||
{
|
||
id: 'node1', // String,可选,节点的唯一标识
|
||
x: 0, // Number,必选,节点位置的 x 值
|
||
y: 0, // Number,必选,节点位置的 y 值
|
||
width: 80, // Number,可选,节点大小的 width 值
|
||
height: 40, // Number,可选,节点大小的 height 值
|
||
label: 'hello', // String,节点标签
|
||
},
|
||
{
|
||
id: 'node2', // String,节点的唯一标识
|
||
x: 160, // Number,必选,节点位置的 x 值
|
||
y: 180, // Number,必选,节点位置的 y 值
|
||
width: 80, // Number,可选,节点大小的 width 值
|
||
height: 40, // Number,可选,节点大小的 height 值
|
||
label: 'world', // String,节点标签
|
||
},
|
||
],
|
||
// 边
|
||
edges: [
|
||
{
|
||
source: 'node1', // String,必须,起始节点 id
|
||
target: 'node2', // String,必须,目标节点 id
|
||
},
|
||
],
|
||
}
|
||
}
|
||
},
|
||
mounted () {
|
||
this.init()
|
||
this.initCell()
|
||
this.initGraph();
|
||
this.initStencil()
|
||
this.initGaodeMap()
|
||
},
|
||
methods:{
|
||
onRender() {
|
||
//根据地理坐标计算屏幕坐标
|
||
|
||
//获取所有的点
|
||
const container = this.$refs.container
|
||
const ports = container.querySelectorAll(
|
||
'.x6-port-body',
|
||
)
|
||
//每个点的屏幕坐标重新计算
|
||
this.graph.getNodes().forEach((node)=>{
|
||
let mapPoint = node.store.data.mapPoint
|
||
//由地理位置转换为屏幕位置
|
||
var lnglat = new AMap.LngLat(mapPoint.lng,mapPoint.lat);
|
||
var pixel = this.map.lngLatToContainer(lnglat);
|
||
node.position(pixel.round().x,pixel.round().y)
|
||
})
|
||
},
|
||
init() {
|
||
this.contentHeight = document.documentElement.clientHeight -45 + "px";
|
||
this.mapHeight = document.documentElement.clientHeight -45 + "px";
|
||
},
|
||
//初始化图形
|
||
initCell(){
|
||
//桩
|
||
const ports = {
|
||
groups: {
|
||
top: {
|
||
position: 'top',
|
||
attrs: {
|
||
circle: {
|
||
r: 4,
|
||
magnet: true,
|
||
stroke: '#5F95FF',
|
||
strokeWidth: 1,
|
||
fill: '#fff',
|
||
style: {
|
||
visibility: 'hidden',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
right: {
|
||
position: 'right',
|
||
attrs: {
|
||
circle: {
|
||
r: 4,
|
||
magnet: true,
|
||
stroke: '#5F95FF',
|
||
strokeWidth: 1,
|
||
fill: '#fff',
|
||
style: {
|
||
visibility: 'hidden',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
bottom: {
|
||
position: 'bottom',
|
||
attrs: {
|
||
circle: {
|
||
r: 4,
|
||
magnet: true,
|
||
stroke: '#5F95FF',
|
||
strokeWidth: 1,
|
||
fill: '#fff',
|
||
style: {
|
||
visibility: 'hidden',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
left: {
|
||
position: 'left',
|
||
attrs: {
|
||
circle: {
|
||
r: 4,
|
||
magnet: true,
|
||
stroke: '#5F95FF',
|
||
strokeWidth: 1,
|
||
fill: '#fff',
|
||
style: {
|
||
visibility: 'hidden',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
items: [
|
||
{
|
||
group: 'top',
|
||
},
|
||
{
|
||
group: 'right',
|
||
},
|
||
{
|
||
group: 'bottom',
|
||
},
|
||
{
|
||
group: 'left',
|
||
},
|
||
],
|
||
}
|
||
//自定义节点
|
||
Graph.registerNode(
|
||
'custom-rect',
|
||
{
|
||
inherit: 'rect',
|
||
width: 66,
|
||
height: 36,
|
||
attrs: {
|
||
body: {
|
||
strokeWidth: 1,
|
||
stroke: '#5F95FF',
|
||
fill: '#EFF4FF',
|
||
},
|
||
text: {
|
||
fontSize: 12,
|
||
fill: '#262626',
|
||
},
|
||
},
|
||
ports: { ...ports },
|
||
},
|
||
true,
|
||
)
|
||
},
|
||
//初始化高德地图
|
||
initGaodeMap(){
|
||
AMapLoader.load({
|
||
key:'5cebc9186d74c44afbfee1e178a89782',
|
||
version:'2.0',
|
||
plugins:['']
|
||
}).then((AMap)=>{
|
||
this.map = new AMap.Map("map-container",{
|
||
zoom:5,//初始化地图级别
|
||
center:[105.602725,37.076636],//初始化地图中心位置
|
||
})
|
||
|
||
//var x6Dom = document.createElement('container');
|
||
var x6Dom = this.$refs.container
|
||
var x6 = document.getElementsByClassName("x6-graph-svg")[1];
|
||
console.log("x6Dom",this.$refs.container)
|
||
//创建自定义图层,添加到地图
|
||
var customLayer = new AMap.CustomLayer(x6Dom, {
|
||
zIndex: 12,
|
||
zooms: [3, 18], // 设置可见级别,[最小级别,最大级别]
|
||
dragEnable: true
|
||
});
|
||
customLayer.render = this.onRender;
|
||
this.map.add(customLayer)
|
||
|
||
}).catch(e=>{
|
||
console.log(e)
|
||
})
|
||
|
||
|
||
},
|
||
//初始化画布
|
||
initGraph(){
|
||
console.log("x6图层",this.$refs.container)
|
||
let mapZoom = this.zoom
|
||
let mapp = this.map
|
||
console.log("地图地图",this.map)
|
||
this.graph = new Graph({
|
||
container: this.$refs.container,
|
||
background: {//背景
|
||
color: 'transport', // 设置画布背景颜色
|
||
},
|
||
grid: {//网格
|
||
size: 10, // 网格大小 10px
|
||
visible: true, // 渲染网格背景
|
||
},
|
||
// panning: {//画布是否可以拖动
|
||
// enabled: true, //开启支持拖拽平移
|
||
// modifiers: 'shift', //按下修饰键并点击鼠标才能触发画布拖拽
|
||
// },
|
||
// mousewheel: {//滚轮缩放画布
|
||
// enabled: true,
|
||
// modifiers: ['ctrl'],
|
||
// guard(e){//判断一个滚轮事件是否应该被处理
|
||
|
||
// return true
|
||
// }
|
||
// },
|
||
snapline: true,//对齐线
|
||
selecting: {//点选/框选
|
||
enabled: true,//开启点选/框选
|
||
//rubberband: true,//启用框选
|
||
showNodeSelectionBox: true,//是否显示节点的选择框
|
||
},
|
||
resizing:true,//缩放节点
|
||
highlighting: {//高亮选项,指定触发某种交互时的高亮样式
|
||
// 当链接桩可以被链接时,在链接桩外围渲染一个蓝色
|
||
magnetAdsorbed: {
|
||
name: 'stroke',
|
||
args: {
|
||
attrs: {
|
||
//fill: '#5F95FF',
|
||
stroke: '#5F95FF',
|
||
},
|
||
},
|
||
},
|
||
//当可以当父节点时,高亮
|
||
embedding: {
|
||
name: 'stroke',
|
||
args: {
|
||
padding: -1,
|
||
attrs: {
|
||
stroke: '#73d13d',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
connecting:{//连线配置
|
||
//设置连接桩自动吸附,吸附距离为20
|
||
snap: {
|
||
radius: 20,
|
||
},
|
||
allowBlank:false,//是否允许连接到画布空白位置的点,设置为否
|
||
allowNode:false,//是否允许边链接到节点(非节点上的链接桩)
|
||
allowEdge:false,//是否允许边链接到另一个边
|
||
anchor: 'center',//当连接到节点时,通过 anchor 来指定被连接的节点的锚点
|
||
connectionPoint: 'anchor',//指定连接点
|
||
router: {//路由将边的路径点 vertices 做进一步转换处理
|
||
name: 'manhattan',
|
||
args: {
|
||
padding: 1,
|
||
},
|
||
},
|
||
connector: {//连接器
|
||
name: 'rounded',
|
||
args: {
|
||
radius: 8,
|
||
},
|
||
},
|
||
createEdge() {//创建新的边
|
||
return new Shape.Edge({
|
||
attrs: {
|
||
line: {
|
||
//stroke: '#A2B1C3',
|
||
stroke: 'green',
|
||
strokeWidth: 3,
|
||
targetMarker: {
|
||
name: 'block',
|
||
width: 12,
|
||
height: 8,
|
||
},
|
||
},
|
||
},
|
||
zIndex: 20,
|
||
})
|
||
},
|
||
validateConnection({ targetMagnet }) {//在移动边的时候判断连接是否有效
|
||
return !!targetMagnet
|
||
},
|
||
},
|
||
//节点嵌套
|
||
embedding:{
|
||
enabled:true,//开启节点嵌套
|
||
findParent(node){//在节点被移动时通过 findParent 指定的方法返回父节点
|
||
const bbox = node.node.getBBox()
|
||
return this.getNodes().filter((node) => {
|
||
// 只有 data.parent 为 true 的节点才是父节点
|
||
const data = node.getData()
|
||
if (data && data.canBeParent) {
|
||
const targetBBox = node.getBBox()
|
||
return bbox.isIntersectWithRect(targetBBox)
|
||
}
|
||
return false
|
||
})
|
||
}
|
||
},
|
||
//限制子节点的移动范围
|
||
translating: {
|
||
restrict(view) {
|
||
if(view){
|
||
const cell = view.cell
|
||
if (cell.isNode()) {
|
||
const parent = cell.getParent()
|
||
if (parent) {
|
||
return parent.getBBox()
|
||
}
|
||
}
|
||
}
|
||
return null
|
||
},
|
||
},
|
||
});
|
||
//this.graph.fromJSON(this.data)
|
||
|
||
//事件注册
|
||
//鼠标移动到节点上,显示桩、给当前节点添加删除按钮
|
||
this.graph.on('node:mouseenter', ( e ) => {
|
||
const container = this.$refs.container
|
||
const ports = container.querySelectorAll(
|
||
'.x6-port-body',
|
||
)
|
||
this.showPorts(ports, true)
|
||
this.showNodeTool(e.node,true)
|
||
//当鼠标移动到节点上,关闭地图的拖拽
|
||
//this.changeMapDrag(false)
|
||
})
|
||
//鼠标移出节点,隐藏桩、删除当前节点删除按钮
|
||
this.graph.on('node:mouseleave', ( e) => {
|
||
const container = this.$refs.container
|
||
const ports = container.querySelectorAll(
|
||
'.x6-port-body',
|
||
)
|
||
this.showPorts(ports, false)
|
||
this.showNodeTool(e.node,false)
|
||
//当鼠标移动到节点上,开启地图的拖拽
|
||
//this.changeMapDrag(true)
|
||
})
|
||
//鼠标移动到线上,给当前线添加删除按钮
|
||
this.graph.on('edge:mouseenter',(e)=>{
|
||
this.showEdgeTool(e.edge,true)
|
||
})
|
||
//鼠标移出线,删除当前线的删除按钮
|
||
this.graph.on('edge:mouseleave',(e)=>{
|
||
this.showEdgeTool(e.edge,false)
|
||
})
|
||
//移入父节点,父节点改变后
|
||
this.graph.on('node:change:parent', ({ node }) => {
|
||
node.attr({
|
||
body:{
|
||
stroke: 'none',
|
||
fill: '#47C769',
|
||
},
|
||
label: {
|
||
text: '节点-过程',
|
||
},
|
||
})
|
||
})
|
||
//节点添加到画布中
|
||
this.graph.on('node:added',(args)=>{
|
||
//将在容器的位置坐标转换为地图的地理坐标
|
||
this.pixelToPoint(args.node,args.node.store.data.position)
|
||
})
|
||
//节点在画布上移动
|
||
this.graph.on('node:change:position',(args)=>{
|
||
//将在容器的位置坐标转换为地图的地理坐标
|
||
this.pixelToPoint(args.node,args.current)
|
||
})
|
||
//画布平移
|
||
// this.graph.on('translate', ({ tx, ty }) => {
|
||
// console.log("平移tx",tx)
|
||
// //console.log("平移ty",ty)
|
||
// this.mapScrollBy(tx,ty)
|
||
// })
|
||
|
||
this.graph.on('blank:mousedown',(e)=>{
|
||
this.changeMapDrag(true)
|
||
})
|
||
|
||
//鼠标在空白画布上抬起
|
||
this.graph.on('blank:mouseup',(e)=>{
|
||
this.changeMapDrag(false)
|
||
})
|
||
|
||
},
|
||
//初始化拖拽Stencil
|
||
initStencil(){
|
||
this.stencil = new Stencil({
|
||
target: this.graph,//设置画布
|
||
stencilGraphWidth: 200,//模板画布宽度
|
||
stencilGraphHeight: 80,//模板画布高度
|
||
groups: [
|
||
{
|
||
name: 'group1',//分组名称
|
||
title: '元素',//分组标题
|
||
collapsable: false,//分组是否可折叠,默认为 true
|
||
},
|
||
],
|
||
})
|
||
//将Stencil挂载到页面上
|
||
this.$refs.stencilContainer.appendChild(this.stencil.container)
|
||
//定义模板节点
|
||
const process = this.graph.createNode({
|
||
shape: 'custom-rect',
|
||
label: '过程',
|
||
zIndex: 10,
|
||
attrs: {
|
||
body: {
|
||
stroke: 'none',
|
||
fill: '#3199FF',
|
||
},
|
||
},
|
||
})
|
||
const node = this.graph.createNode({
|
||
shape: 'custom-rect',
|
||
label: '节点',
|
||
zIndex: 1,
|
||
data: {
|
||
canBeParent: true,
|
||
},
|
||
})
|
||
//装载模板节点
|
||
this.stencil.load([process,node], 'group1')
|
||
},
|
||
//链接桩显示/隐藏事件
|
||
showPorts(ports,show){
|
||
ports.forEach(port => {
|
||
port.style.visibility = show?'visible' : 'hidden'
|
||
});
|
||
},
|
||
//节点上的删除按钮添加/删除事件
|
||
showNodeTool(node,show){
|
||
if(show){
|
||
node.addTools({
|
||
name: 'button-remove',
|
||
args: {
|
||
x: '100%',
|
||
y: 0,
|
||
offset: { x: -3, y: 3 },
|
||
},
|
||
})
|
||
}else{
|
||
node.removeTools()
|
||
}
|
||
},
|
||
//线上的删除按钮添加/删除事件
|
||
showEdgeTool(edge,show){
|
||
if(show){
|
||
edge.addTools({
|
||
name: 'button-remove',
|
||
args: { distance: -40 },
|
||
})
|
||
}else{
|
||
edge.removeTools()
|
||
}
|
||
},
|
||
//将像素坐标转换为地理坐标
|
||
pixelToPoint(node,position){
|
||
let ePoint = this.map.containerToLngLat(new AMap.Pixel(position.x, position.y))
|
||
//将数据记录到node中
|
||
node.store.data.mapPoint = ePoint
|
||
console.log("新位置数据",node)
|
||
},
|
||
//地图平移
|
||
mapScrollBy(tx,ty){
|
||
this.projection.panBy(tx,ty)
|
||
},
|
||
//关闭/开启地图拖拽
|
||
changeMapDrag(canDrag){
|
||
this.map.setStatus({
|
||
dragEnable:canDrag
|
||
})
|
||
}
|
||
}
|
||
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.tools-bar{
|
||
height: 45px;
|
||
line-height: 45px;
|
||
background: #fff;
|
||
display: flex;
|
||
|
||
.opt-btn{
|
||
margin-top: 4px;
|
||
font-size: 35px;
|
||
}
|
||
}
|
||
.content {
|
||
display: flex;
|
||
.x6-stencil{
|
||
width: 250px;
|
||
}
|
||
.middle-box{
|
||
width: 100%;
|
||
position: relative;
|
||
.x6-content {
|
||
width: 100%;
|
||
position: absolute;
|
||
z-index: 1;
|
||
}
|
||
.mp-view{
|
||
width: 100%;
|
||
position:absolute;
|
||
z-index: 2;
|
||
}
|
||
}
|
||
}
|
||
.el-popover{
|
||
height: 500px;
|
||
overflow: auto;
|
||
}
|
||
</style>
|
||
<style>
|
||
.el-popover{
|
||
height: 120px;
|
||
overflow: auto;
|
||
}
|
||
</style> |