1、 下载x6:
npm i @antv/x6@2.17.1
同步下载所需要用到的x6插件:
"@antv/x6-plugin-clipboard": "^2.0.0", // 如果使用剪切板功能,需要安装此包
"@antv/x6-plugin-history": "^2.0.0", // 如果使用撤销重做功能,需要安装此包
"@antv/x6-plugin-keyboard": "^2.0.0", // 如果使用快捷键功能,需要安装此包
"@antv/x6-plugin-minimap": "^2.0.0", // 如果使用小地图功能,需要安装此包
"@antv/x6-plugin-scroller": "^2.0.0", // 如果使用滚动画布功能,需要安装此包
"@antv/x6-plugin-selection": "^2.0.0", // 如果使用框选功能,需要安装此包
"@antv/x6-plugin-snapline": "^2.0.0", // 如果使用对齐线功能,需要安装此包
"@antv/x6-plugin-dnd": "^2.0.0", // 如果使用 dnd 功能,需要安装此包
"@antv/x6-plugin-stencil": "^2.0.0", // 如果使用 stencil 功能,需要安装此包
"@antv/x6-plugin-transform": "^2.0.0", // 如果使用图形变换功能,需要安装此包
"@antv/x6-plugin-export": "^2.0.0", // 如果使用图片导出功能,需要安装此包
"@antv/x6-react-components": "^2.0.0", // 如果使用配套 UI 组件,需要安装此包
"@antv/x6-react-shape": "^2.0.0", // 如果使用 react 渲染功能,需要安装此包
"@antv/x6-vue-shape": "^2.0.0" // 如果使用 vue 渲染功能,需要安装此包
2、页面中引入x6
<template>
<div class="entry-container" ref="screenRef">
<div class="mapBox">
<div id="mapBody"></div>
</div>
<div id="minimap"></div>
<TeleportContainer /> // 很重要,必须将TeleportContainer标签放置在html结构中
</div>
</div>
</template>
<script setup>
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
</script>
3、注册自定义节点:
// 引入自定义节点
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";
// 注册外部节点
register({
shape: "ArtNode",
inherit: "vue-shape",
width: 300,
height: 72,
component: ArtNode,
ports: { ...ports },
});
// 注册群组节点
register({
shape: "GroupNode",
inherit: "vue-shape",
width: 325,
height: 210,
component: GroupNode,
ports: { ...ports },
});
const ports = {
groups: {
in: {
position: "left",
attrs: {
circle: {
r: 5,
magnet: true,
stroke: "#ccc",
strokeWidth: 2,
fill: "#fff",
},
},
},
out: {
position: "right",
attrs: {
circle: {
r: 5,
magnet: true,
stroke: "#ccc",
strokeWidth: 2,
fill: "#fff",
},
},
},
},
items: [
{
id: "port1",
group: "in",
},
{
id: "port2",
group: "out",
},
],
};
4、注册x6插件
// 注册插件
const handleUsePlugin = () => {
graph
.use(
new Transform({
resizing: true,
rotating: true,
})
)
.use(
new Selection({
rubberband: true,
showNodeSelectionBox: true,
})
)
.use(
new Scroller({
enabled: true,
autoResize: true,
pannable: true, //是否启用画布平移能力
modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽
})
)
.use(
new MiniMap({
container: document.getElementById("minimap"),
width: 200,
height: 160,
scalable: false,
minScale: 0.1,
maxScale: 2,
padding: 10,
})
)
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History());
};
5、注册边&连接器
Edge.config({
markup: [
{
tagName: "path",
selector: "wrap",
attrs: {
fill: "none",
cursor: "pointer",
stroke: "transparent",
strokeLinecap: "round",
},
},
{
tagName: "path",
selector: "line",
attrs: {
fill: "none",
pointerEvents: "none",
},
},
],
connector: { name: "curveConnector" },
attrs: {
wrap: {
connection: true,
strokeWidth: 10,
strokeLinejoin: "round",
},
line: {
connection: true,
stroke: "#A2B1C3",
strokeWidth: 1,
targetMarker: {
name: "classic",
size: 6,
},
},
},
});
// 注册连接器
Graph.registerConnector(
"curveConnector",
(sourcePoint, targetPoint) => {
const hgap = Math.abs(targetPoint.x - sourcePoint.x);
const path = new Path();
path.appendSegment(
Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y)
);
path.appendSegment(
Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y)
);
// 水平三阶贝塞尔曲线
path.appendSegment(
Path.createSegment(
"C",
sourcePoint.x < targetPoint.x
? sourcePoint.x + hgap / 2
: sourcePoint.x - hgap / 2,
sourcePoint.y,
sourcePoint.x < targetPoint.x
? targetPoint.x - hgap / 2
: targetPoint.x + hgap / 2,
targetPoint.y,
targetPoint.x - 6,
targetPoint.y
)
);
path.appendSegment(
Path.createSegment("L", targetPoint.x + 2, targetPoint.y)
);
return path.serialize();
},
true
);
Graph.registerEdge("data-processing-curve", Edge, true);
6、初始化x6画布
// 创建画布
const handleInit = () => {
// 创建画布
graph = new Graph({
container: document.getElementById("mapBody"),
grid: true,
width: 1500,
height: 1200,
autoResize: true,
// 设置画布背景颜色
background: {
color: "#f0f3f5",
},
// 画布缩放 按住ctrl
mousewheel: {
enabled: true,
global: true,
modifiers: ["ctrl", "meta"],
},
// 设置连接线
connecting: {
snap: true, //开启和关闭连线过程中自动吸附
anchor: "center",
connectionPoint: "boundary",
allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 true
allowBlank: false, // 是否允许连接到画布空白位置的点
highlight: true,
sourceAnchor: {
name: "left",
args: {
dx: Platform.IS_SAFARI ? 4 : 8,
},
},
targetAnchor: {
name: "right",
args: {
dx: Platform.IS_SAFARI ? 4 : -8,
},
},
//创建边
createEdge() {
return graph.createEdge({
shape: "data-processing-curve",
attrs: {
line: {
stroke: "#A2B1C3",
strokeWidth: 1,
targetMarker: {
name: "block",
width: 12,
height: 8,
},
},
},
zIndex: -1,
});
},
validateConnection({ targetMagnet }) {
return !!targetMagnet;
},
},
highlighting: {
magnetAdsorbed: {
name: "stroke",
args: {
attrs: {
fill: "#006eff",
stroke: "#006eff",
},
},
},
},
});
graph.centerContent();
handleUsePlugin();
7、给x6画布绑定事件(实现复制、粘贴、删除、撤销、回复等功能)
// 画布方法&快捷键
const handleGraphMethods = () => {
// 撤销/复原
graph.bindKey(["meta+z", "ctrl+z"], () => {
if (graph.canUndo()) {
graph.undo();
}
return false;
});
graph.bindKey(["meta+y", "ctrl+y"], () => {
if (graph.canRedo()) {
graph.redo();
}
return false;
});
//复制
graph.bindKey(["meta+c", "ctrl+c"], () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.copy(cells);
}
return false;
});
// 粘贴
graph.bindKey(["meta+v", "ctrl+v"], () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
cells.forEach((item) => {
let node = item.store.data;
node.shape !== "data-processing-curve" && (node.data.id = node.id);
});
graph.cleanSelection();
graph.select(cells);
}
return false;
});
// 删除
graph.bindKey("backspace", () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.removeCells(cells);
}
});
// 右键菜单
graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {
handleContextmenu(e);
});
// 历史改变
graph.on("history:change", () => {
canRedo.value = graph.canRedo();
canUndo.value = graph.canUndo();
});
// 选中节点
graph.on("cell:selected", ({ cell, index, options }) => {
selectCell.value = cell;
});
// 取消选中节点
graph.on("cell:unselected", ({ cell, index, options }) => {
selectCell.value = null;
});
// 双击节点
graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {
if (graph.isNode(cell)) {
drawerData.value = JSON.parse(JSON.stringify(cell.getData()));
drawerData.value.showDrawer = true;
}
});
};
8、拖拽生成节点
// 指定画布拖拽区
dnd = new Dnd({
target: graph,
scaled: false,
dndContainer: document.querySelector(".treeContent"),
// 确保拖拽节点时和拖拽完成后节点id一致
getDragNode: (node) => node.clone({ keepId: true }),
getDropNode: (node) => {
// 拖拽结束后
let data = node.getData();
drawerData.value = {
...data,
id: node.id,
isCreate: true,
showDrawer: true,
};
return node.clone({ keepId: true });
},
});
handleGraphMethods();
};
// 拖拽生成节点
const handleCreateNode = (type, e) => {
// graph.cleanSelection();
const node =
type === "group"
? graph.createNode({
shape: "GroupNode",
data: {
type: "group",
group: [{ type: "inside" }, { type: "outside" }],
},
})
: graph.createNode({
shape: "ArtNode",
data: {
type,
},
});
dnd.start(node, e);
};
9、保存/设置节点数据
// 保存
const handleToJson = () => {
const json = graph.toJSON();
json.knwlgMapId = route.query.pkId;
submitNode(json).then(() => {
proxy.$modal.msgSuccess("保存成功!");
});
};
// 获取地图数据
const getMapDetail = () => {
loading.value = true;
getNodeDetail({ knwlgMapId: route.query.pkId })
.then(({ data: cells }) => {
graph.fromJSON({ cells });
loading.value = false;
})
.catch(() => {
loading.value = false;
});
};
完整代码:
<template>
<div class="entry-container" ref="screenRef" v-loading="loading">
<div class="treeContent" style="width: 240px">
<div class="flex mb20">
<div class="mr5">插入节点</div>
<el-tooltip placement="bottom">
<template #content>
* 创建节点方式:<br />
按住内部节点/外部节点/文章集合后往右侧区域拖拽<br />
* 节点/边操作快捷键:<br />
删除:点击选中节点/边后按Backspace键<br />
撤销:操作后Ctrl+z<br />
恢复:操作后Ctrl+y<br />
复制:选中节点/边后Ctrl+c Ctrl+v<br />
多选:直接框选或按住Ctrl单点<br />
* 画布操作快捷键:<br />
按住Ctrl+鼠标左键可拖拽画布<br />
按住Ctrl+鼠标滚轮可缩放画布<br />
</template>
<el-icon class="mr5" :size="14" color="#006eff"
><QuestionFilled
/></el-icon>
</el-tooltip>
</div>
<div class="sigleGroup">
<div
class="shapeItem flex"
v-for="(item, index) in shapeList"
:key="index"
@mousedown="handleCreateNode(item.type, $event)"
>
<img class="mr10" :src="item.imgSrc" />
<span>{{ item.name }}</span>
</div>
</div>
</div>
<div class="mapContent">
<div class="mapHeader flex">
<div class="flex">
<div v-if="!mapInfo.showIpt">{{ mapInfo.knwlgMapName }}</div>
<el-input
v-else
ref="mapNameRef"
maxlength="20"
autofocus
v-model.trim="mapInfo.knwlgMapName"
placeholder="输入地图名称"
show-word-limit
clearable
style="width: 200px"
@blur="handleBlur"
></el-input>
<el-button
v-if="!mapInfo.showIpt"
link
type="primary"
icon="Edit"
@click="handleShowIpt"
></el-button>
</div>
<div class="flex">
<el-tooltip content="撤销">
<el-button
:disabled="!canUndo"
link
type="primary"
icon="RefreshLeft"
@click="handleUndo"
></el-button>
</el-tooltip>
<el-tooltip content="恢复">
<el-button
:disabled="!canRedo"
link
type="primary"
icon="RefreshRight"
@click="handleRedo"
></el-button>
</el-tooltip>
<el-tooltip content="删除选中的线或节点">
<el-button
:disabled="!selectCell"
link
type="primary"
icon="Delete"
@click="handleDelete()"
></el-button>
</el-tooltip>
<el-tooltip content="复制选中的线或节点">
<el-button
:disabled="!selectCell"
link
type="primary"
icon="CopyDocument"
@click="handleCopy"
></el-button>
</el-tooltip>
<el-tooltip content="全屏">
<el-button
link
type="primary"
icon="FullScreen"
@click="toggle"
></el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-button type="primary" @click="handleToJson">保存</el-button>
<el-button @click="handleClosePage">关闭</el-button>
</div>
</div>
<div class="mapBox">
<div id="mapBody"></div>
</div>
<div id="minimap"></div>
<TeleportContainer />
</div>
<mapDrawer
v-model="drawerData"
@handleUpdateData="handleUpdateData"
@handleDelCell="handleDelete"
/>
</div>
</template>
<script setup name="Ekms/map/grapth">
import { getNodeDetail, submitNode, editName } from "@/api/ekms/map";
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
import { useFullscreen } from "@vueuse/core";
import ContextMenu from "@imengyu/vue3-context-menu";
import useTagsViewStore from "@/store/modules/tagsView";
import { ports, shapeList } from "./components/constans.js";
import ArtNode from "./components/artNode.vue";
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";
const { proxy } = getCurrentInstance();
const screenRef = ref(null);
const { toggle } = useFullscreen(screenRef);
const route = useRoute();
const router = useRouter();
const mapInfo = ref({
originName: route.query.knwlgMapName,
knwlgMapName: route.query.knwlgMapName,
});
let graph = null;
let dnd = null;
const TeleportContainer = getTeleport();
const canRedo = ref(false);
const canUndo = ref(false);
const loading = ref(false);
// const routeId = ref(route.query.pkId);
const selectCell = ref(null);
const drawerData = ref({
showDrawer: false,
title: "",
group: [{ type: "inside" }, { type: "outside" }],
});
onMounted(() => {
if (!route.query.pkId) return;
handleInit();
// window.addEventListener("resize", resizeFn);
});
// const resizeFn = () => {
// const width = document.querySelector(".mapContent").offsetWidth;
// const height = document.querySelector(".mapContent").offsetHeight;
// graph.resize(width, height);
// };
// onActivated(() => {
// if (routeId.value != route.query.pkId) {
// routeId.value = route.query.pkId;
// graph && graph.dispose();
// handleInit();
// } else {
// resizeFn();
// graph.centerContent();
// }
// });
const handleShowIpt = () => {
mapInfo.value.showIpt = true;
nextTick(() => {
proxy.$refs.mapNameRef.input.focus();
});
};
const handleBlur = async () => {
mapInfo.value.showIpt = false;
!mapInfo.value.knwlgMapName?.length &&
(mapInfo.value.knwlgMapName = route.query.knwlgMapName);
await editName({
pkId: route.query.pkId,
knwlgMapName: mapInfo.value.knwlgMapName,
});
};
// 获取地图数据
const getMapDetail = () => {
loading.value = true;
getNodeDetail({ knwlgMapId: route.query.pkId })
.then(({ data: cells }) => {
graph.fromJSON({ cells });
loading.value = false;
})
.catch(() => {
loading.value = false;
});
};
// 注册外部节点
register({
shape: "ArtNode",
inherit: "vue-shape",
width: 300,
height: 72,
component: ArtNode,
ports: { ...ports },
});
// 注册群组节点
register({
shape: "GroupNode",
inherit: "vue-shape",
width: 325,
height: 210,
component: GroupNode,
ports: { ...ports },
});
Edge.config({
markup: [
{
tagName: "path",
selector: "wrap",
attrs: {
fill: "none",
cursor: "pointer",
stroke: "transparent",
strokeLinecap: "round",
},
},
{
tagName: "path",
selector: "line",
attrs: {
fill: "none",
pointerEvents: "none",
},
},
],
connector: { name: "curveConnector" },
attrs: {
wrap: {
connection: true,
strokeWidth: 10,
strokeLinejoin: "round",
},
line: {
connection: true,
stroke: "#A2B1C3",
strokeWidth: 1,
targetMarker: {
name: "classic",
size: 6,
},
},
},
});
// 注册连接器
Graph.registerConnector(
"curveConnector",
(sourcePoint, targetPoint) => {
const hgap = Math.abs(targetPoint.x - sourcePoint.x);
const path = new Path();
path.appendSegment(
Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y)
);
path.appendSegment(
Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y)
);
// 水平三阶贝塞尔曲线
path.appendSegment(
Path.createSegment(
"C",
sourcePoint.x < targetPoint.x
? sourcePoint.x + hgap / 2
: sourcePoint.x - hgap / 2,
sourcePoint.y,
sourcePoint.x < targetPoint.x
? targetPoint.x - hgap / 2
: targetPoint.x + hgap / 2,
targetPoint.y,
targetPoint.x - 6,
targetPoint.y
)
);
path.appendSegment(
Path.createSegment("L", targetPoint.x + 2, targetPoint.y)
);
return path.serialize();
},
true
);
Graph.registerEdge("data-processing-curve", Edge, true);
// 创建画布
const handleInit = () => {
// 创建画布
graph = new Graph({
container: document.getElementById("mapBody"),
grid: true,
width: 1500,
height: 1200,
autoResize: true,
// 设置画布背景颜色
background: {
color: "#f0f3f5",
},
// 画布缩放 按住ctrl
mousewheel: {
enabled: true,
global: true,
modifiers: ["ctrl", "meta"],
},
// 设置连接线
connecting: {
snap: true, //开启和关闭连线过程中自动吸附
anchor: "center",
connectionPoint: "boundary",
allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 true
allowBlank: false, // 是否允许连接到画布空白位置的点
highlight: true,
sourceAnchor: {
name: "left",
args: {
dx: Platform.IS_SAFARI ? 4 : 8,
},
},
targetAnchor: {
name: "right",
args: {
dx: Platform.IS_SAFARI ? 4 : -8,
},
},
//创建边
createEdge() {
return graph.createEdge({
shape: "data-processing-curve",
attrs: {
line: {
stroke: "#A2B1C3",
strokeWidth: 1,
targetMarker: {
name: "block",
width: 12,
height: 8,
},
},
},
zIndex: -1,
});
},
validateConnection({ targetMagnet }) {
return !!targetMagnet;
},
},
highlighting: {
magnetAdsorbed: {
name: "stroke",
args: {
attrs: {
fill: "#006eff",
stroke: "#006eff",
},
},
},
},
});
// resizeFn();
graph.centerContent();
getMapDetail();
handleUsePlugin();
// 指定画布拖拽区
dnd = new Dnd({
target: graph,
scaled: false,
dndContainer: document.querySelector(".treeContent"),
// 确保拖拽节点时和拖拽完成后节点id一致
getDragNode: (node) => node.clone({ keepId: true }),
getDropNode: (node) => {
// 拖拽结束后
let data = node.getData();
drawerData.value = {
...data,
id: node.id,
isCreate: true,
showDrawer: true,
};
return node.clone({ keepId: true });
},
});
handleGraphMethods();
};
// 画布方法&快捷键
const handleGraphMethods = () => {
// 撤销/复原
graph.bindKey(["meta+z", "ctrl+z"], () => {
if (graph.canUndo()) {
graph.undo();
}
return false;
});
graph.bindKey(["meta+y", "ctrl+y"], () => {
if (graph.canRedo()) {
graph.redo();
}
return false;
});
//复制
graph.bindKey(["meta+c", "ctrl+c"], () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.copy(cells);
}
return false;
});
// 粘贴
graph.bindKey(["meta+v", "ctrl+v"], () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
cells.forEach((item) => {
let node = item.store.data;
node.shape !== "data-processing-curve" && (node.data.id = node.id);
});
graph.cleanSelection();
graph.select(cells);
}
return false;
});
// 删除
graph.bindKey("backspace", () => {
handleDelete();
});
// 右键菜单
graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {
handleContextmenu(e);
});
// 历史改变
graph.on("history:change", () => {
canRedo.value = graph.canRedo();
canUndo.value = graph.canUndo();
});
// 选中节点
graph.on("cell:selected", ({ cell, index, options }) => {
selectCell.value = cell;
});
// 取消选中节点
graph.on("cell:unselected", ({ cell, index, options }) => {
selectCell.value = null;
});
// 双击节点
graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {
if (graph.isNode(cell)) {
drawerData.value = JSON.parse(JSON.stringify(cell.getData()));
drawerData.value.showDrawer = true;
}
});
};
// 注册插件
const handleUsePlugin = () => {
graph
.use(
new Transform({
resizing: true,
rotating: true,
})
)
.use(
new Selection({
rubberband: true,
showNodeSelectionBox: true,
})
)
.use(
new Scroller({
enabled: true,
autoResize: true,
pannable: true, //是否启用画布平移能力
modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽
})
)
.use(
new MiniMap({
container: document.getElementById("minimap"),
width: 200,
height: 160,
scalable: false,
minScale: 0.1,
maxScale: 2,
padding: 10,
})
)
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History());
};
// 拖拽生成节点
const handleCreateNode = (type, e) => {
// graph.cleanSelection();
const node =
type === "group"
? graph.createNode({
shape: "GroupNode",
data: {
type: "group",
group: [{ type: "inside" }, { type: "outside" }],
},
})
: graph.createNode({
shape: "ArtNode",
data: {
type,
},
});
dnd.start(node, e);
};
// 修改节点内容
const handleUpdateData = (id, value) => {
let cell = graph.getCellById(id || selectCell.value.id);
cell.updateData(value);
if (drawerData.value.type === "group") {
cell.resize(325, 50 + drawerData.value.group.length * 82);
}
drawerData.value.showDrawer = false;
};
// 撤销
const handleUndo = () => {
graph.undo();
};
// 恢复
const handleRedo = () => {
graph.redo();
};
// 复制
const handleCopy = () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.copy(cells);
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
graph.cleanSelection();
graph.select(cells);
}
}
};
// 右键菜单
const handleContextmenu = (e) => {
const cells = graph.getSelectedCells();
ContextMenu.showContextMenu({
x: e.pageX,
y: e.pageY,
items: [
{
label: "复制(Ctrl+c&Ctrl+z)",
disabled: cells.length === 0 ? true : false,
onClick: () => {
if (cells.length) {
graph.copy(cells);
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
// 解决复制的节点修改失效问题
cells.forEach((item) => {
let node = item.store.data;
node.shape !== "data-processing-curve" &&
(node.data.id = node.id);
});
graph.cleanSelection();
graph.select(cells);
}
}
},
},
{
label: "删除(Backspace)",
disabled: cells.length === 0 ? true : false,
onClick: () => {
if (cells.length) {
graph.removeCells(cells);
}
},
},
],
});
};
// 删除节点/边
const handleDelete = (id) => {
// 判断是新增删除还是修改删除
const cells = id ? [graph.getCellById(id)] : graph.getSelectedCells();
if (cells.length) {
graph.removeCells(cells);
}
};
// 保存
const handleToJson = () => {
const json = graph.toJSON();
json.knwlgMapId = route.query.pkId;
submitNode(json).then(() => {
proxy.$modal.msgSuccess("保存成功!");
});
};
// 关闭页面
const handleClosePage = () => {
let tag = useTagsViewStore().visitedViews.find(
(item) => item.path === "/ekms/map/grapth"
);
proxy.$tab.closePage(tag).then(() => {
router.push("/ekms/map/mapLibrary");
});
};
onBeforeUnmount(() => {
graph && graph.dispose();
});
</script>
<style lang="scss" scoped>
.entry-container {
display: flex;
position: relative;
background: #f0f3f5;
height: calc(100vh - 70px);
:deep(.treeContent) {
padding: 20px;
min-width: 240px;
.shapeItem {
padding: 0 10px;
margin-bottom: 10px;
height: 44px;
line-height: 20px;
font-size: 14px;
background: #f6f8fd;
border-radius: 4px;
user-select: none;
cursor: move;
&:hover {
border: 1px solid rgba(0, 112, 255, 1);
}
img {
width: 28px;
height: 28px;
}
}
}
:deep(.mapContent) {
flex: 1;
position: relative;
.mapHeader {
padding: 0 20px;
width: 100%;
height: 40px;
line-height: 40px;
background: #fff;
justify-content: space-between;
box-shadow: 0px 1px 0px 0px rgba(215, 220, 230, 1);
}
.mapBox {
width: calc(100vw - 245px);
height: calc(100vh - 110px);
}
#minimap {
position: absolute;
right: 0;
bottom: 0;
}
}
}
:deep(#mapBody) {
.x6-widget-selection-box {
border-color: rgba(0, 110, 255, 1);
}
.x6-widget-transform {
display: none !important;
}
.x6-edge-selected path:nth-child(2) {
stroke: #006eff;
stroke-dasharray: 8px, 2px;
}
}
</style>