依赖下载
npm install --save @antv/layout @antv/x6 @antv/x6-plugin-clipboard @antv/x6-plugin-history @antv/x6-plugin-keyboard @antv/x6-plugin-selection @antv/x6-plugin-snapline @antv/x6-plugin-transform
封装js
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";
/* 给画布绑定插件 */
export const bindPlugin = (graph) => {
graph
.use(
new Transform({
resizing: true, //节点大小
rotating: true, //节点旋转
})
)
.use(
new Selection({
rubberband: true, //框选节点功能
showNodeSelectionBox: true, //显示节点的选择框
})
)
.use(
new Snapline({
resizing: true, //改变节点大小时触发对齐线
})
)
.use(
new Clipboard({
useLocalStorage: false, //禁用复制后保存到本地
})
)
.use(
new Keyboard({
enabled: true,
})
)
.use(new History());
return graph;
};
// 画布基本设置
export const configSetting = (Shape) => {
return {
grid: true, //网格
autoResize: true, //自动缩放
translating: { restrict: true }, //限制节点的移动范围
/* 鼠标滚轮 */
mousewheel: {
enabled: true, //是否开启滚轮缩放交互
zoomAtMousePosition: true, //将鼠标位置作为中心缩放
modifiers: ["alt", "ctrl"], //修饰键
minScale: 0.1, //最小缩放比例
maxScale: 5, //最大缩放比例
},
/* 连线交互 */
connecting: {
snap: { radius: 20 }, //自动吸附
/* 路径点 */
router: {
name: "metro", //智能地铁线路由,自动避开路径上的其他节点,无法使用manhattan
args: {
padding: 10, //锚点距离转角的最小距离
},
},
/* 连线方式 */
connector: {
name: "jumpover", //跳线连接器
args: {
size: 10,
},
},
anchor: "center", //锚点
connectionPoint: "anchor", //连接点和锚点相同
allowBlank: false, //连接到画布空白位置
/* 连线的样式 */
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: "#A2B1C3",
strokeWidth: 2,
targetMarker: {
name: "block",
width: 12,
height: 8,
},
},
},
zIndex: 0,
});
},
/* 移动边的时候判断连接是否有效 */
validateConnection({ targetMagnet }) {
return !!targetMagnet;
},
},
// 添加小工具暂时没用
// onToolItemCreated({ tool }) {
// const handle = tool;
// const options = handle.options;
// if (options && options.index % 2 === 1) {
// tool.setAttrs({ fill: "red" });
// }
// },
/* 交互时的高亮 */
highlighting: {
/* 连接桩吸附连线时在连接桩外围围渲染一个包围框 */
magnetAdsorbed: {
name: "stroke",
args: {
attrs: {
fill: "#fff",
stroke: "#31d0c6",
strokeWidth: 4,
},
},
},
},
};
};
/**
* 根据节点的类型返回不同的形状
* @param {String} type 节点的类型
* @description 通过传入的type===匹配 data里面设置的type获取到相应的节点设置内容
* 编辑的时候也可以通过节点里面的data.type 获取到到底是什么节点进行响应设设置
* defaultOval 0椭圆形
* defaultSquare 1方形
* defaultYSquare 2圆角矩形
* defaultRhombus 3菱形
* defaultRhomboid 4平行四边形
* defaultCircle 5圆形
* otherImage 6图片
*/
export const configNodeShape = (type) => {
const nodeShapeList = [
{
label: "椭圆形",
data: {
type: "defaultOval", //为了方便编辑的时候找到相对应的类型进行不同的编辑处理
},
shape: "rect",
width: 100,
height: 50,
attrs: {
body: {
rx: 20,
ry: 26,
fill: "#fff",
stroke: "#333",
},
label: {
text: "椭圆形",
fontSize: 16,
fill: "#333",
},
},
},
{
label: "方形",
data: {
type: "defaultSquare",
},
shape: "rect",
width: 100,
height: 50,
attrs: {
label: {
text: "方形",
fontSize: 16,
fill: "#333",
},
body: {
fill: "#fff",
stroke: "#333",
},
},
},
{
label: "圆角矩形",
data: {
type: "defaultYSquare",
},
shape: "rect",
width: 100,
height: 50,
attrs: {
body: {
rx: 6,
ry: 6,
fill: "#fff",
stroke: "#333",
},
label: {
text: "圆角矩形",
fontSize: 16,
fill: "#333",
},
},
},
{
label: "菱形",
data: {
type: "defaultRhombus",
},
shape: "polygon",
width: 120,
height: 50,
attrs: {
body: {
refPoints: "0,10 10,0 20,10 10,20",
fill: "#fff",
stroke: "#333",
},
label: {
text: "菱形",
fontSize: 16,
fill: "#333",
},
},
},
{
label: "平行四边形",
data: {
type: "defaultRhomboid",
},
shape: "polygon",
width: 120,
height: 50,
attrs: {
body: {
refPoints: "10,0 40,0 30,20 0,20",
fill: "#fff",
stroke: "#333",
},
label: {
text: "平行四边形",
fontSize: 16,
fill: "#333",
},
},
},
{
label: "圆形",
data: {
type: "defaultCircle",
},
shape: "circle",
width: 80,
height: 80,
attrs: {
label: {
text: "圆形",
fontSize: 16,
fill: "#333",
},
body: {
fill: "#fff",
stroke: "#333",
},
},
},
{
label: "图片",
data: {
type: "otherImage",
},
shape: "rect",
width: 80,
height: 80,
markup: [
{
tagName: "rect",
selector: "body",
},
{
tagName: "image",
},
{
tagName: "text",
selector: "label",
},
],
attrs: {
body: {
stroke: "#5F95FF",
fill: "#5F95FF",
},
image: {
width: 80,
height: 80,
refX: 0,
refY: 0,
xlinkHref: "https://gw.alipayobjects.com/zos/bmw-prod/2010ac9f-40e7-49d4-8c4a-4fcf2f83033b.svg",
},
label: {
fontSize: 14,
fill: "#fff",
text: "图片",
},
},
},
];
if (type) {
const obj = nodeShapeList.find((item) => {
return item.data.type === type;
});
return obj || nodeShapeList;
}
return nodeShapeList;
};
/* 连接柱的设置,默认隐藏,鼠标经过显示 */
export const configNodePorts = () => {
return {
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",
},
],
};
};
/**
* 元素文字设置
* @param {String} labelText 文字
* @param {String} fontSize 文字大小
* @param {String} fontFill 文字背景
* @param {String} fill 元素填充
* @param {String} stroke 元素边框
*/
export const configCell = (cellForm) => {
return {
label: {
text: cellForm.labelText,
fontSize: cellForm.fontSize,
fill: cellForm.fontFill,
},
body: {
fill: cellForm.fill,
stroke: cellForm.stroke,
},
};
};
/**
* 连接线上标签设置
* @param {String} labelText 连接线文字
* @param {String} fontColor 字体颜色
* @param {String} fill 填充色
* @param {String} stroke 线条颜色
*/
export const configEdgeLabel = (lineForm) => {
if (!lineForm.label) return { attrs: { labelText: { text: "" }, labelBody: { fill: "", stroke: "" } } };
return {
markup: [
{
tagName: "rect",
selector: "labelBody",
},
{
tagName: "text",
selector: "labelText",
},
],
attrs: {
labelText: {
text: lineForm.label || "",
fill: lineForm.fontColor || "#333",
textAnchor: "middle",
textVerticalAnchor: "middle",
},
labelBody: {
ref: "labelText",
refX: -8,
refY: -5,
refWidth: "100%",
refHeight: "100%",
refWidth2: 16,
refHeight2: 10,
stroke: lineForm.stroke || "#555",
fill: lineForm.fill || "#fff",
strokeWidth: 2,
rx: 5,
ry: 5,
},
},
};
};
/**
* 修改图片节点
* @param {Object} imgForm 图片节点的配置数据
*/
export const configImageNode = (imgForm) => {
return {
label: {
text: imgForm.labelText,
fill: imgForm.labelFill,
},
body: {
fill: imgForm.fill,
},
image: {
"xlink:href": imgForm.xlinkHref,
xlinkHref: imgForm.xlinkHref,
width: imgForm.width,
height: imgForm.height,
},
};
};
// 键盘事件
export const graphBindKey = (graph) => {
// 复制
graph.bindKey(["ctrl+c"], () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.copy(cells);
}
return false;
});
// 剪切
graph.bindKey(["ctrl+x"], () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.cut(cells);
}
return false;
});
// 黏贴
graph.bindKey(["ctrl+v"], () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
graph.cleanSelection();
graph.select(cells);
}
return false;
});
// 上一步
graph.bindKey(["ctrl+z"], () => {
if (graph.canUndo()) {
graph.undo();
}
return false;
});
// 下一步
graph.bindKey(["ctrl+y"], () => {
if (graph.canRedo()) {
graph.redo();
}
return false;
});
// 全选
graph.bindKey(["ctrl+a"], () => {
const nodes = graph.getNodes();
if (nodes) {
graph.select(nodes);
}
});
// 缩放
graph.bindKey(["alt+1"], () => {
const zoom = graph.zoom();
if (zoom < 1.5) {
graph.zoom(0.1);
}
});
graph.bindKey(["alt+2"], () => {
const zoom = graph.zoom();
if (zoom > 0.5) {
graph.zoom(-0.1);
}
});
return graph;
};
// 链接桩的显示与隐藏,主要是照顾菱形
export const changePortsShow = (val) => {
const container = document.getElementById("antvCanvasContent");
const ports = container.querySelectorAll(".x6-port-body");
for (let i = 0; i < ports.length; i = i + 1) {
ports[i].style.visibility = val ? "visible" : "hidden";
}
};
实际代码
<script setup>
import { ElMessage, ElMessageBox } from "element-plus";
import { Graph, Shape } from "@antv/x6";
import { bindPlugin, configSetting, configNodeShape, configNodePorts, configCell, configEdgeLabel, configImageNode, graphBindKey, changePortsShow } from "@/utils/antvSetting";
import { shallowReactive, ref, reactive, defineProps, onMounted } from "vue";
import { ElNotification } from "element-plus";
import { GridLayout } from "@antv/layout";
let graphNode = null, //图形
isChange = ref(false), //是否有图形在右侧
isPortsShow = ref(false), //链接桩常显
isDrag = ref(false), //链接桩常显
menuItem = reactive({}), //左侧选择的图形
selectCell = {}, //选中的节点
drawerInfo = reactive({
isShow: false,
title: "",
type: 1,
}), //右侧编辑抽屉
form = reactive({}), //每个节点的内容
labelForm = reactive({
fontColor: "#333",
fill: "#FFF",
stroke: "#555",
}); //连线标签的样式
defineProps({
height: {
type: String,
default: "100vh", //'720px'
},
value: {
type: String,
default: "",
},
});
// 对象置空
const deleteObj = (obj) => {
Object.keys(obj).map((key) => {
delete obj[key];
});
};
// 初始化画布
const initGraph = () => {
let graph = new Graph({
container: document.getElementById("antvCanvasContent"),
...configSetting(Shape),
panning: {
enabled: false,
modifiers: ["shift"],
eventTypes: "leftMouseDown",
},
});
bindPlugin(graph); //画布绑定插件
graphBindKey(graph); //画布绑定插件
// 鼠标移入node节点显示连接柱
graph.on("node:mouseenter", () => {
changePortsShow(true);
});
graph.on("node:mouseleave", () => {
if (isPortsShow.value) return;
changePortsShow(false);
});
// 点击编辑
graph.on("cell:click", ({ cell }) => {
editForm(cell);
});
// 绑定删除事件
graph.bindKey(["delete", "backspace"], () => {
handlerDel();
});
// 画布有变化
graph.on("cell:changed", () => {
isChangeValue();
});
graphNode = graph;
};
// 切换是否可拖拽
const isOpenDrag = () => {
graphNode.togglePanning(isDrag.value);
};
// 从左侧拖拽图形
const menuDrag = (type) => {
deleteObj(menuItem);
// 根据type获取到不同节点的预设参数
Object.assign(menuItem, configNodeShape(type));
};
// 鼠标释放
const drop = (event) => {
// 节点预设 ,添加位置信息和链接桩信息组合成完整的节点
const nodeItem = {
...menuItem,
x: event.offsetX - menuItem.width / 2,
y: event.offsetY - menuItem.height / 2,
ports: configNodePorts(),
};
// 创建节点
graphNode.addNode(nodeItem);
isChangeValue();
};
// 画布是否有变动
const isChangeValue = () => {
if (!isChange.value && graphNode.getCells().length) {
isChange.value = true;
}
if (!graphNode.getCells().length) {
isChange.value = false;
}
};
// 点击编辑根据不同的内容获取编辑数据
const editForm = (cell) => {
if (Object.keys(selectCell).length) selectCell.removeTools(); // 删除修改线的工具
selectCell = cell;
// 编辑node节点
if (cell.isNode() && cell.data.type && cell.data.type.includes("default")) {
drawerInfo.title = "编辑节点";
drawerInfo.type = 1;
const body = cell.attrs.body || cell.attrs.rect || cell.attrs.polygon || cell.attrs.circle;
let data = {
labelText: cell.attrs.label.text || "",
fontSize: cell.attrs.label.fontSize || 14,
fontFill: cell.attrs.label.fill || "",
fill: body.fill || "",
stroke: body.stroke || "",
};
deleteObj(form);
Object.assign(form, data);
drawerInfo.isShow = true;
}
// 编辑图片节点
if (cell.isNode() && cell.data.type && cell.data.type === "otherImage") {
drawerInfo.title = "编辑图片节点";
drawerInfo.type = 2;
const attrs = cell.attrs || { body: { fill: "" }, label: { text: "", fill: "" }, image: { xlinkHref: "", height: 80, width: 80 } };
let data = {
fill: attrs.body.fill,
labelText: attrs.label.text,
labelFill: attrs.label.fill,
height: (attrs.image && attrs.image.height) || 80,
width: (attrs.image && attrs.image.width) || 80,
xlinkHref: (attrs.image && (attrs.image.xlinkHref || attrs.image["xlink:href"])) || "https://gw.alipayobjects.com/zos/bmw-prod/2010ac9f-40e7-49d4-8c4a-4fcf2f83033b.svg",
};
deleteObj(form);
Object.assign(form, data);
drawerInfo.isShow = true;
}
// 编辑连线
if (!cell.isNode() && cell.shape === "edge") {
drawerInfo.title = "编辑连线";
drawerInfo.type = 3;
let data = {
label: cell.labels && cell.labels[0] ? cell.labels[0].attrs.labelText.text : "",
stroke: cell.attrs.line.stroke || "",
connector: "normal",
strokeWidth: cell.attrs.line.strokeWidth || "",
isArrows: cell.attrs.line.sourceMarker ? true : false,
isAnit: cell.attrs.line.strokeDasharray ? true : false,
isTools: false,
};
deleteObj(form);
Object.assign(form, data);
// 看连线上是否有label
const edgeCellLabel = (cell.labels && cell.labels[0] && cell.labels[0].attrs) || false;
if (form.label && edgeCellLabel) {
labelForm = shallowReactive({
fontColor: edgeCellLabel.labelText.fill || "#333",
fill: edgeCellLabel.labelBody.fill || "#fff",
stroke: edgeCellLabel.labelBody.stroke || "#555",
});
} else {
labelForm = shallowReactive({ fontColor: "#333", fill: "#FFF", stroke: "#555" });
}
drawerInfo.isShow = true;
}
};
// 关闭右侧
const closeEditForm = () => {
drawerInfo.isShow = false;
if (Object.keys(selectCell).length) selectCell.removeTools();
};
// 修改一般节点
const changeNode = () => {
selectCell.setAttrs(configCell(form));
};
// 修改图片节点
const changeImageNode = () => {
selectCell.setAttrs(configImageNode(form));
};
// 修改边label属性
const changeEdgeLabel = () => {
selectCell.setLabels([
configEdgeLabel({
label: form.label,
fontColor: labelForm.fontColor,
fill: labelForm.fill,
stroke: labelForm.stroke,
}),
]);
if (!form.label) labelForm = shallowReactive({ fontColor: "#333", fill: "#FFF", stroke: "#555" });
};
// 修改边的颜色
const changeEdgeStroke = (val) => {
selectCell.setAttrs({ line: { stroke: val } });
};
// 边的样式
const changeEdgeConnector = (val) => {
switch (val) {
case "normal":
selectCell.setConnector(val);
break;
case "smooth":
selectCell.setConnector(val);
break;
case "rounded":
selectCell.setConnector(val, { radius: 20 });
break;
case "jumpover":
selectCell.setConnector(val, { radius: 20 });
break;
}
};
// 边的宽度
const changeEdgeStrokeWidth = (val) => {
// 判断是否为双向箭头
if (form.isArrows) {
selectCell.attr({
line: {
strokeWidth: val,
sourceMarker: {
width: 12 * (val / 2) || 12,
height: 8 * (val / 2) || 8,
},
targetMarker: {
width: 12 * (val / 2) || 12,
height: 8 * (val / 2) || 8,
},
},
});
} else {
selectCell.attr({
line: {
strokeWidth: val,
targetMarker: {
width: 12 * (val / 2) || 12,
height: 8 * (val / 2) || 8,
},
},
});
}
};
// 双向箭头
const changeEdgeArrows = (val) => {
if (val) {
selectCell.attr({
line: {
sourceMarker: {
name: "block",
width: 12 * (form.strokeWidth / 2) || 12,
height: 8 * (form.strokeWidth / 2) || 8,
},
targetMarker: {
name: "block",
width: 12 * (form.strokeWidth / 2) || 12,
height: 8 * (form.strokeWidth / 2) || 8,
},
},
});
} else {
selectCell.attr({
line: {
sourceMarker: "",
targetMarker: {
name: "block",
size: 10 * (form.strokeWidth / 2) || 10,
},
},
});
}
};
// 边修改为流动虚线
const changeEdgeAnit = (val) => {
if (val) {
selectCell.attr({ line: { strokeDasharray: 5, style: { animation: "ant-line 30s infinite linear" } } });
} else {
selectCell.attr({ line: { strokeDasharray: 0, style: { animation: "" } } });
}
};
// 给线添加调节工具
const changeEdgeTools = (val) => {
if (val) selectCell.addTools(["vertices", "segments"]);
else selectCell.removeTools();
};
// 删除节点
const handlerDel = () => {
ElMessageBox.confirm(`此操作将永久删除此${drawerInfo.type == 1 ? "节点" : "连线"}, 是否继续?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
const cells = graphNode.getSelectedCells();
if (cells.length) {
graphNode.removeCells(cells);
deleteObj(form);
drawerInfo.isShow = false;
isChangeValue();
ElMessage({ type: "success", message: "删除成功" });
}
})
.catch(() => {});
};
// 布局
const layout = () => {
const gridLayout = new GridLayout({
type: "grid",
width: 738,
sortBy: "label",
cols: 8,
});
const { cells } = graphNode.toJSON();
let data = {
nodes: [],
edges: [],
};
for (const i of cells) {
if (i.shape == "edge") {
data.edges.push(i);
} else {
data.nodes.push(i);
}
}
const model = gridLayout.layout(data);
graphNode.fromJSON(model);
};
// 导出
const handlerSend = () => {
const { cells } = graphNode.toJSON();
const tempGroupJson = cells.map((item) => {
if (item.ports && item.ports.groups) delete item.ports.groups;
if (item.tools) delete item.tools;
return item;
});
if (selectCell) {
selectCell.removeTools();
selectCell = "";
}
console.log(tempGroupJson);
// this.$emit("finish", JSON.stringify(tempGroupJson));
// console.log(JSON.stringify(tempGroupJson));
};
// 保存数据
const handlerData = () => {
let data = selectCell.getData();
let form = {
time: new Date(),
info: "业务数据",
};
selectCell.setData({ ...data, ...form });
console.log(selectCell.getData());
};
const openTip = () => {
ElNotification({
title: "快捷键提示",
dangerouslyUseHTMLString: true,
message: `ctrl+c 复制 ctrl+x 剪切 ctrl+v 黏贴 ctrl+z 后退 ctrl+y 前进 ctrl+a 全选`,
duration: 0,
type: "success",
position: "bottom-left",
});
};
onMounted(() => {
initGraph();
});
</script>
<template>
<div class="antv-content">
<div class="antv-menu">
<p class="menu-title">
基础图形列表
<svg @click="openTip" width="18px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728="">
<path
fill="currentColor"
d="M512 64a448 448 0 1 1 0 896.064A448 448 0 0 1 512 64zm67.2 275.072c33.28 0 60.288-23.104 60.288-57.344s-27.072-57.344-60.288-57.344c-33.28 0-60.16 23.104-60.16 57.344s26.88 57.344 60.16 57.344zM590.912 699.2c0-6.848 2.368-24.64 1.024-34.752l-52.608 60.544c-10.88 11.456-24.512 19.392-30.912 17.28a12.992 12.992 0 0 1-8.256-14.72l87.68-276.992c7.168-35.136-12.544-67.2-54.336-71.296-44.096 0-108.992 44.736-148.48 101.504 0 6.784-1.28 23.68.064 33.792l52.544-60.608c10.88-11.328 23.552-19.328 29.952-17.152a12.8 12.8 0 0 1 7.808 16.128L388.48 728.576c-10.048 32.256 8.96 63.872 55.04 71.04 67.84 0 107.904-43.648 147.456-100.416z"
></path>
</svg>
</p>
<ul class="menu-list">
<li draggable="true" @drag="menuDrag('defaultOval')"><i class="icon-oval"></i> <span>椭圆形</span></li>
<li draggable="true" @drag="menuDrag('defaultSquare')"><i class="icon-square"></i><span>矩形</span></li>
<li draggable="true" @drag="menuDrag('defaultYSquare')"><i class="icon-ysquare"></i><span>圆角矩形</span></li>
<li draggable="true" @drag="menuDrag('defaultRhombus')"><i class="icon-rhombus"></i><span>菱形</span></li>
<li draggable="true" @drag="menuDrag('defaultRhomboid')"><i class="icon-rhomboid"></i><span>平行四边形</span></li>
<li draggable="true" @drag="menuDrag('defaultCircle')"><i class="icon-circle"></i><span>圆形</span></li>
<li draggable="true" @drag="menuDrag('otherImage')">
<svg width="18px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M160 160v704h704V160H160zm-32-64h768a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32H128a32 32 0 0 1-32-32V128a32 32 0 0 1 32-32z"></path>
<path
fill="currentColor"
d="M384 288q64 0 64 64t-64 64q-64 0-64-64t64-64zM185.408 876.992l-50.816-38.912L350.72 556.032a96 96 0 0 1 134.592-17.856l1.856 1.472 122.88 99.136a32 32 0 0 0 44.992-4.864l216-269.888 49.92 39.936-215.808 269.824-.256.32a96 96 0 0 1-135.04 14.464l-122.88-99.072-.64-.512a32 32 0 0 0-44.8 5.952L185.408 876.992z"
></path>
</svg>
<span>图片</span>
</li>
</ul>
<div class="save-btn" v-if="isChange">
<el-button type="success" @click="handlerSend">保存当前方案</el-button>
<el-button type="success" @click="layout">布局</el-button>
</div>
</div>
<div class="antv-canvas">
<div class="canvas-content" :style="{ height: height }" id="antvCanvasContent" @drop="drop($event)" @dragover.prevent></div>
<div class="canvas-tips">
<div class="canvas-tips-item">
<el-switch v-model="isPortsShow" @change="changePortsShow"></el-switch>
<span>链接桩常显</span>
</div>
<div class="canvas-tips-item">
<el-switch v-model="isDrag" @change="isOpenDrag"></el-switch>
<span>开启拖拽(按住shift在空白区域拖拽)</span>
</div>
</div>
</div>
<div class="antv-edit el-drawer rtl" v-if="drawerInfo.isShow">
<header class="el-drawer__header">
<span class="el-drawer__title">{{ drawerInfo.title }}</span>
<button class="el-drawer__close-btn" type="button" @click="closeEditForm">
<i class="el-icon el-drawer__close">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path
fill="currentColor"
d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"
></path>
</svg>
</i>
</button>
</header>
<el-divider />
<div class="el-drawer__body">
<div v-if="drawerInfo.type == 1">
<el-form ref="nodeForm" :model="form" label-width="80px">
<el-form-item label="节点文本">
<el-input v-model="form.labelText" size="small" @input="changeNode('labelText', form.labelText)"></el-input>
</el-form-item>
<el-form-item label="字体大小">
<el-input v-model="form.fontSize" size="small" @input="changeNode('fontSize', form.fontSize)"></el-input>
</el-form-item>
<el-form-item label="字体颜色">
<el-color-picker v-model="form.fontFill" @change="changeNode('fontFill', form.fontFill)"></el-color-picker>
</el-form-item>
<el-form-item label="节点背景">
<el-color-picker v-model="form.fill" @change="changeNode('fill', form.fill)"></el-color-picker>
</el-form-item>
<el-form-item label="边框颜色">
<el-color-picker v-model="form.stroke" @change="changeNode('stroke', form.stroke)"></el-color-picker>
</el-form-item>
<div class="see-box">
<h5>预览</h5>
<div class="see-item" :style="{ background: form.fill, color: form.fontFill, 'border-color': form.stroke, 'font-size': form.fontSize + 'px' }">{{ form.labelText }}</div>
</div>
</el-form>
</div>
<div v-if="drawerInfo.type == 2">
<el-form ref="imageForm" :model="form" label-width="80px">
<el-form-item label="节点文本">
<el-input v-model="form.labelText" size="small" @input="changeImageNode('labelText', form.labelText)"></el-input>
</el-form-item>
<el-form-item label="字体颜色">
<el-color-picker v-model="form.labelFill" @change="changeImageNode('labelFill', form.labelFill)"></el-color-picker>
</el-form-item>
<el-form-item label="节点背景">
<el-color-picker v-model="form.fill" @change="changeImageNode('fill', form.fill)"></el-color-picker>
</el-form-item>
<el-form-item label="图片地址">
<el-input type="textarea" autosize v-model="form.xlinkHref" size="small" placeholder="图片地址" @input="changeImageNode('xlinkHref', form.xlinkHref)"></el-input>
<el-image :preview-src-list="[form.xlinkHref]" :src="form.xlinkHref" style="width: 80px; height: 80px; background: #f2f2f2" fit="fill"></el-image>
</el-form-item>
<el-form-item label="图片尺寸">
<div>
<span style="font-size: 14px; padding-right: 5px; color: #888">宽</span>
<el-input-number v-model="form.width" :min="0" label="宽" size="small" @change="changeImageNode('width', form.width)"></el-input-number>
</div>
<div>
<span style="font-size: 14px; padding-right: 5px; color: #888">高</span>
<el-input-number v-model="form.height" :min="0" label="高" size="small" @change="changeImageNode('height', form.height)"></el-input-number>
</div>
</el-form-item>
</el-form>
</div>
<div v-if="drawerInfo.type == 3">
<el-form ref="edgeForm" :model="form" label-width="80px">
<el-form-item label="标签内容">
<el-input v-model="form.label" size="small" placeholder="标签文字,空则没有" @input="changeEdgeLabel()"></el-input>
<div v-if="form.label" class="label-style">
<p>字体颜色:<el-color-picker v-model="labelForm.fontColor" size="small" @change="changeEdgeLabel()"></el-color-picker></p>
<p>背景颜色:<el-color-picker v-model="labelForm.fill" size="small" @change="changeEdgeLabel()"></el-color-picker></p>
<p>描边颜色:<el-color-picker v-model="labelForm.stroke" size="small" @change="changeEdgeLabel()"></el-color-picker></p>
</div>
</el-form-item>
<el-form-item label="线条颜色">
<el-color-picker v-model="form.stroke" size="small" @change="changeEdgeStroke"></el-color-picker>
</el-form-item>
<el-form-item label="线条样式">
<el-select v-model="form.connector" size="small" placeholder="请选择" @change="changeEdgeConnector">
<el-option label="直角" value="normal"></el-option>
<el-option label="圆角" value="rounded"></el-option>
<el-option label="平滑" value="smooth"></el-option>
<el-option label="跳线(两线交叉)" value="jumpover"></el-option>
</el-select>
</el-form-item>
<el-form-item label="线条宽度">
<el-input-number v-model="form.strokeWidth" size="small" @change="changeEdgeStrokeWidth" :min="2" :step="2" :max="6" label="线条宽度"></el-input-number>
</el-form-item>
<el-form-item label="双向箭头">
<el-switch v-model="form.isArrows" @change="changeEdgeArrows"></el-switch>
</el-form-item>
<el-form-item label="流动线条">
<el-switch v-model="form.isAnit" @change="changeEdgeAnit"></el-switch>
</el-form-item>
<el-form-item label="调整线条">
<el-switch v-model="form.isTools" @change="changeEdgeTools"></el-switch>
</el-form-item>
</el-form>
</div>
<div class="edit-btn">
<el-row :gutter="20">
<el-col :span="12">
<el-button type="primary" @click="handlerData" style="width: 100%">保存数据</el-button>
</el-col>
<el-col :span="12">
<el-button type="danger" @click="handlerDel" style="width: 100%">删除此{{ drawerInfo.type == 1 ? "节点" : "连线" }}</el-button>
</el-col>
</el-row>
</div>
</div>
</div>
</div>
</template>
<style>
@keyframes ant-line {
from {
stroke-dashoffset: 100%;
}
to {
stroke-dashoffset: 0%;
}
}
* {
margin: 0;
padding: 0;
}
html,
body,
#app {
width: 100%;
height: 100%;
}
.modal-position {
position: unset !important;
}
</style>
<style lang="less" scoped>
.antv-content {
height: 100%;
background: #fff;
display: flex;
overflow: hidden;
position: relative;
.antv-menu {
width: 200px;
border-right: 1px solid #d5d5d5;
padding: 12px;
flex-shrink: 0;
.menu-title {
display: flex;
svg {
cursor: pointer;
}
}
li {
padding: 10px;
border-radius: 8px;
border: 1px solid #555;
background: #fff;
margin: 8px 12px;
font-size: 12px;
display: flex;
align-items: center;
cursor: pointer;
transition: all 0.5s ease;
&:hover {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
i,
svg {
font-size: 18px;
margin-right: 10px;
}
}
}
.antv-canvas {
flex: 1;
position: relative;
.canvas-content {
position: relative;
height: 100vh;
min-height: 100vh;
}
.canvas-tips {
padding: 10px;
display: flex;
align-items: center;
position: absolute;
top: 0;
left: 0;
.canvas-tips-item {
margin-right: 10px;
span {
padding-left: 10px;
font-size: 12px;
}
}
}
}
.antv-edit {
width: 300px;
.el-drawer__header {
margin-bottom: 0;
}
.el-drawer__body {
padding-top: 0;
}
.see-box {
padding: 20px;
background: #f2f2f2;
h5 {
padding-bottom: 10px;
}
.see-item {
padding: 10px 30px;
border: 2px solid #333;
text-align: center;
}
}
.edit-btn {
margin-top: 14px;
}
}
}
i.icon-oval {
display: inline-block;
width: 16px;
height: 10px;
border-radius: 10px;
border: 2px solid #555;
}
i.icon-square {
display: inline-block;
width: 16px;
height: 10px;
border: 2px solid #555;
}
i.icon-ysquare {
display: inline-block;
width: 16px;
height: 10px;
border-radius: 4px;
border: 2px solid #555;
}
i.icon-rhombus {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #555;
transform: rotate(45deg);
}
i.icon-rhomboid {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #555;
transform: skew(-30deg);
}
i.icon-circle {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 16px;
border: 2px solid #555;
}
</style>