feat: 拓扑编辑节点功能

This commit is contained in:
TsMask
2023-12-27 20:25:08 +08:00
parent b756fe9d56
commit 24816eb991

View File

@@ -59,10 +59,16 @@ const graphG6Data = reactive<Record<string, any>>({
labelCfg: {
position: 'center',
},
style: {
fill: '#00b050',
stroke: '#00b050',
lineWidth: 1,
type: 'image',
img: '/svg/service_db.svg',
clipCfg: {
show: true,
width: 0,
height: 0,
type: 'circle',
y: -3,
x: -6,
r: 23,
},
},
// 2 O&M
@@ -71,6 +77,15 @@ const graphG6Data = reactive<Record<string, any>>({
x: 50,
y: 450,
label: 'O&M',
type: 'triangle',
icon: {
show: true,
// 可更换为其他图片地址
img: '/svg/service_db.svg',
width: 24,
height: 24,
offset: 39,
},
},
// 100 EMS
{
@@ -369,7 +384,7 @@ const graphCanvasMenu = new Menu({
style="
display: flex;
flex-direction: column;
width: 140px;
width: 140px;
"
>
<div id="show" style="cursor: pointer; margin-bottom: 2px">
@@ -586,7 +601,28 @@ function fnRanderGraph() {
size: [80, 40],
style: {
radius: 8,
// fill: '#ffffff',
stroke: '#ffffff',
lineWidth: 1,
cursor: 'pointer',
},
labelCfg: {
position: 'center',
offset: 0,
style: {
fill: '#000000',
fontSize: 12,
fontWeight: 500,
},
},
icon: {
show: false,
img: '/svg/service.svg',
width: 25,
height: 25,
offset: 20, // triangle 特有
},
direction: 'up', // triangle 三角形的方向
},
// 全局边 三次贝塞尔曲线
defaultEdge: {
@@ -598,6 +634,17 @@ function fnRanderGraph() {
lineWidth: 1,
cursor: 'pointer',
},
labelCfg: {
refX: 0,
refY: 0,
position: 'middle',
autoRotate: false,
style: {
fill: '#ffffff',
fontSize: 12,
fontWeight: 500,
},
},
},
// defaultEdge: {
// type: 'line',
@@ -791,21 +838,36 @@ let modalState: ModalStateType = reactive({
x: 0,
y: 0,
type: 'circle',
size: 0,
size: [0],
anchorPoints: false,
style: {},
style: {
fill: '#ffffff',
stroke: '#ffffff',
lineWidth: 1,
},
label: '',
labelCfg: {},
labelCfg: {
position: 'center',
offset: 0,
style: {
fill: '#000000',
fontSize: 12,
fontWeight: 500,
},
},
},
formEdgeOrigin: {},
formEdge: {
id: '',
source: '',
target: '',
type: '',
type: 'polyline',
style: {
offset: 20,
radius: 2,
stroke: '#ffffff',
lineWidth: 1,
cursor: 'pointer',
},
label: '',
labelCfg: {
@@ -933,9 +995,16 @@ function fnModalOkEdge(edge: any) {
});
return;
}
graphG6.value.removeItem(edge.id);
edge.id = `${edge.source}~${Date.now()}~${edge.target}`;
graphG6.value.addItem('edge', edge);
// graphG6.value.removeItem(edge.id);
// edge.id = `${edge.source}~${Date.now()}~${edge.target}`;
// graphG6.value.addItem('edge', edge);
const item = graphG6.value.findById(edge.id);
if (item) {
graphG6.value.updateItem(item, edge);
} else {
edge.id = `${edge.source}~${Date.now()}~${edge.target}`;
graphG6.value.addItem('edge', edge);
}
modalState.visible = false;
modalStateFormEdge.resetFields();
modalState.formEdgeOrigin = {};
@@ -953,41 +1022,202 @@ const modalStateFormNode = Form.useForm(
);
/**图节点内置边类型 */
const nodeType = [
const nodeTypeOptions = [
{
type: 'circle',
description: '圆形',
value: 'circle',
label: '圆形',
},
{
type: 'rect',
description: '矩形',
value: 'rect',
label: '矩形',
},
{
type: 'ellipse',
description: '椭圆',
value: 'ellipse',
label: '椭圆',
},
{
type: 'diamond',
description: '菱形',
value: 'diamond',
label: '菱形',
},
{
type: 'triangle',
description: '三角形',
value: 'triangle',
label: '三角形',
},
{
type: 'star',
description: '星形',
value: 'star',
label: '星形',
},
{
type: 'image',
description: '图片',
value: 'image',
label: '图片',
},
{
type: 'donut',
description: '面包圈',
value: 'donut',
label: '面包圈',
},
];
/**图节点标签文本位置 */
const nodePositionOptions = [
{
value: 'top',
label: '上',
},
{
value: 'left',
label: '左',
},
{
value: 'right',
label: '右',
},
{
value: 'bottom',
label: '下',
},
{
value: 'center',
label: '居中',
},
];
/**图节点三角形方向 */
const nodeDirectionOptions = [
{ value: 'up', label: '向上' },
{ value: 'down', label: '向下' },
{ value: 'left', label: '向左' },
{ value: 'right', label: '向右' },
];
/**图节点图片裁剪的形状 */
const nodeImageClipCfgOptions = [
{ value: 'circle', label: '圆形' },
{ value: 'rect', label: '矩形' },
{ value: 'ellipse', label: '椭圆' },
];
/**图节点类型输入限制 */
function fnNodeTypeChange(type: any) {
console.log(type);
// 设置图标属性
if (['circle', 'ellipse', 'diamond', 'star', 'donut'].includes(type)) {
const nodeOrigin = modalState.formNodeOrigin;
if (nodeOrigin.icon) {
modalState.formNode = Object.assign(modalState.formNode, {
icon: nodeOrigin.icon,
});
} else {
modalState.formNode = Object.assign(modalState.formNode, {
icon: {
show: false,
img: '',
width: 25,
height: 25,
},
});
}
} else if (type === 'triangle') {
// 三角
const nodeOrigin = modalState.formNodeOrigin;
if (nodeOrigin.icon) {
modalState.formNode = Object.assign(modalState.formNode, {
direction: nodeOrigin.direction || 'up', // triangle 三角形的方向
icon: Object.assign({ offset: 20 }, nodeOrigin.icon),
});
} else {
modalState.formNode = Object.assign(modalState.formNode, {
direction: 'up', // triangle 三角形的方向
icon: {
show: false,
img: '/svg/service.svg',
width: 25,
height: 25,
offset: 20, // triangle 特有
},
});
}
}
// 设置图片属性
if (type === 'image') {
const nodeOrigin = modalState.formNodeOrigin;
if (nodeOrigin.img) {
modalState.formNode = Object.assign(modalState.formNode, {
img: nodeOrigin.img,
clipCfg: nodeOrigin.clipCfg,
});
} else {
modalState.formNode = Object.assign(modalState.formNode, {
img: '/svg/service.svg',
clipCfg: {
show: false,
width: 0,
height: 0,
type: 'circle',
},
});
}
Reflect.deleteProperty(modalState.formNode, 'style');
} else {
// 当切换非图片时补充style属性
if (!Reflect.has(modalState.formNode, 'style')) {
modalState.formNode = Object.assign(modalState.formNode, {
style: {
fill: '#ffffff',
stroke: '#ffffff',
lineWidth: 1,
},
});
}
}
}
/**图节点大小输入限制 */
function fnNodeSizeChange(value: any) {
// 处理格式
let intArr: number[] = [];
for (const v of value) {
const intV = parseInt(v);
if (!isNaN(intV)) {
intArr.push(intV);
}
}
// 节点类型限制size
const nodeType = modalState.formNode.type;
switch (nodeType) {
case 'circle':
case 'star':
case 'donut':
intArr = intArr.slice(0, 1);
break;
case 'rect':
case 'ellipse':
case 'diamond':
case 'triangle':
case 'image':
intArr = intArr.slice(0, 2);
break;
}
modalState.formNode.size = intArr;
}
/**图节点编辑监听更新视图 */
watch(modalState.formNode, node => {
const info = JSON.parse(JSON.stringify(node));
const nodeId = info.id;
console.log(info);
if (nodeId) {
if (info.type === 'image') {
Reflect.deleteProperty(info, 'style');
}
graphG6.value.clearItemStates(nodeId, 'selected');
graphG6.value.updateItem(nodeId, info);
if (info.type === 'triangle' || info.type === 'image') {
graphG6.value.read(graphG6.value.save());
}
}
});
/**图节点新增 */
function fnModalOkNode(node: any) {
console.log(JSON.parse(JSON.stringify(graphG6.value.save())));
@@ -998,8 +1228,17 @@ function fnModalOkNode(node: any) {
});
return;
}
graphG6.value.removeItem(node.id);
graphG6.value.addItem('node', node);
// graphG6.value.removeItem(node.id);
// graphG6.value.addItem('node', node);
const item = graphG6.value.findById(node.id);
if (item) {
graphG6.value.updateItem(item, node);
} else {
graphG6.value.addItem('node', node);
}
if (node.type === 'triangle' || node.type === 'image') {
graphG6.value.read(graphG6.value.save());
}
modalState.visible = false;
modalStateFormNode.resetFields();
modalState.formNodeOrigin = {};
@@ -1018,7 +1257,7 @@ function fnModalOk() {
}
if (type === 'node') {
const node = JSON.parse(JSON.stringify(modalState.formNode));
fnModalOkEdge(node);
fnModalOkNode(node);
}
}
@@ -1032,13 +1271,22 @@ function fnModalCancel() {
// 边编辑还原
const edgeOrigin = JSON.parse(JSON.stringify(modalState.formEdgeOrigin));
if (type === 'edge' && edgeOrigin.id) {
graphG6.value.removeItem(edgeOrigin.id);
graphG6.value.addItem('edge', edgeOrigin);
graphG6.value.updateItem(edgeOrigin.id, edgeOrigin);
// graphG6.value.removeItem(edgeOrigin.id);
// graphG6.value.addItem('edge', edgeOrigin);
modalStateFormEdge.resetFields();
modalState.formEdgeOrigin = {};
}
modalStateFormNode.resetFields();
// 节点编辑还原
const nodeOrigin = JSON.parse(JSON.stringify(modalState.formNodeOrigin));
if (type === 'node' && nodeOrigin.id) {
graphG6.value.updateItem(nodeOrigin.id, nodeOrigin);
if (nodeOrigin.type === 'triangle' || nodeOrigin.type === 'image') {
graphG6.value.read(graphG6.value.save());
}
modalStateFormNode.resetFields();
modalState.formNodeOrigin = {};
}
}
/**保存图数据 */
@@ -1124,6 +1372,400 @@ function fnGraphLoad() {
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<!-- 节点排版 -->
<a-form
v-if="modalState.formType === 'node'"
name="modalStateFromEdge"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
label="节点ID"
name="type"
:label-col="{ span: 3 }"
v-bind="modalStateFormNode.validateInfos.id"
>
<a-input
v-model:value="modalState.formNode.id"
allow-clear
:placeholder="t('common.inputPlease')"
:disabled="modalState.formNode.id !== ''"
></a-input>
</a-form-item>
<a-form-item
label="节点类型"
name="type"
:label-col="{ span: 3 }"
v-bind="modalStateFormNode.validateInfos.type"
>
<a-select
v-model:value="modalState.formNode.type"
:options="nodeTypeOptions"
@change="fnNodeTypeChange"
:placeholder="t('common.selectPlease')"
>
</a-select>
</a-form-item>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="x 坐标" name="x">
<a-input-number
v-model:value="modalState.formNode.x"
style="width: 100%"
:placeholder="t('common.inputPlease')"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="y 坐标" name="y">
<a-input-number
v-model:value="modalState.formNode.y"
style="width: 100%"
:placeholder="t('common.inputPlease')"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="节点大小"
name="size"
help="根据节点类型区分大小是否是数组"
:label-col="{ span: 3 }"
>
<a-select
v-model:value="modalState.formNode.size"
mode="tags"
style="width: 100%"
:token-separators="[',']"
@change="fnNodeSizeChange"
:placeholder="t('common.inputPlease')"
></a-select>
</a-form-item>
<template v-if="modalState.formNode.type !== 'image'">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="节点填充色" name="fill">
<a-input
v-model:value="modalState.formNode.style.fill"
type="color"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="描边颜色" name="stroke">
<a-input
v-model:value="modalState.formNode.style.stroke"
type="color"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="描边宽度" name="lineWidth">
<a-input-number
v-model:value="modalState.formNode.style.lineWidth"
style="width: 100%"
:min="1"
:max="100"
placeholder="<=100"
></a-input-number>
</a-form-item>
</a-col>
<a-col
:lg="12"
:md="12"
:xs="24"
v-if="modalState.formNode.type === 'triangle'"
>
<a-form-item label="方向" name="direction">
<a-select
v-model:value="modalState.formNode.direction"
:options="nodeDirectionOptions"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
</template>
<a-divider orientation="left"> 标签文本及其配置 </a-divider>
<a-form-item
label="标签文本"
name="label"
:label-col="{ span: 3 }"
help="文本文字,如果没有则不会显示"
>
<a-input
v-model:value="modalState.formNode.label"
allow-clear
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="文本颜色" name="fill">
<a-input
v-model:value="modalState.formNode.labelCfg.style.fill"
type="color"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="文本大小" name="fontSize">
<a-input-number
v-model:value="modalState.formNode.labelCfg.style.fontSize"
style="width: 100%"
:min="10"
:max="100"
placeholder="10~100"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="位置"
name="labelCfg.position"
help="文本相对于节点的位置"
>
<a-select
v-model:value="modalState.formNode.labelCfg.position"
:options="nodePositionOptions"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="文本偏移" name="labelCfg.offset">
<a-input-number
v-model:value="modalState.formNode.labelCfg.offset"
style="width: 100%"
:min="-100"
:max="100"
placeholder="-100~100"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<template v-if="modalState.formNode.type === 'image'">
<a-divider orientation="left"> 图片 </a-divider>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="裁剪功能" name="clipCfg.show">
<a-switch
v-model:checked="modalState.formNode.clipCfg.show"
checked-children=""
un-checked-children=""
/>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="图片来源" name="img">
<a-input
v-model:value="modalState.formNode.img"
allow-clear
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
</a-row>
<template v-if="modalState.formNode.clipCfg.show">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="裁剪的图片形状" name="direction">
<a-select
v-model:value="modalState.formNode.clipCfg.type"
:options="nodeImageClipCfgOptions"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row
:gutter="16"
v-if="modalState.formNode.clipCfg.type === 'circle'"
>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="剪裁圆形的半径" name="clipCfg.r">
<a-input-number
v-model:value="modalState.formNode.clipCfg.r"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row
:gutter="16"
v-if="modalState.formNode.clipCfg.type === 'rect'"
>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="剪裁矩形的宽度" name="clipCfg.width">
<a-input-number
v-model:value="modalState.formNode.clipCfg.width"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="剪裁矩形的长度" name="clipCfg.height">
<a-input-number
v-model:value="modalState.formNode.clipCfg.height"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row
:gutter="16"
v-if="modalState.formNode.clipCfg.type === 'ellipse'"
>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="剪裁椭圆的长轴半径" name="clipCfg.rx">
<a-input-number
v-model:value="modalState.formNode.clipCfg.rx"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="剪裁椭圆的短轴半径" name="clipCfg.ry">
<a-input-number
v-model:value="modalState.formNode.clipCfg.ry"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="裁剪图形的 x 坐标" name="clipCfg.x">
<a-input-number
v-model:value="modalState.formNode.clipCfg.x"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="裁剪图形的 y 坐标" name="clipCfg.y">
<a-input-number
v-model:value="modalState.formNode.clipCfg.y"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
</template>
</template>
<template
v-if="
modalState.formNode.icon &&
modalState.formNode.type !== 'rect' &&
modalState.formNode.type !== 'image'
"
>
<a-divider orientation="left"> 图标 </a-divider>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="是否显示" name="icon.show">
<a-switch
v-model:checked="modalState.formNode.icon.show"
checked-children="是"
un-checked-children="否"
/>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="图片" name="icon.img">
<a-input
v-model:value="modalState.formNode.icon.img"
allow-clear
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="宽度" name="icon.width">
<a-input-number
v-model:value="modalState.formNode.icon.width"
style="width: 100%"
:min="10"
:max="100"
placeholder="10~100"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="高度" name="icon.height">
<a-input-number
v-model:value="modalState.formNode.icon.height"
style="width: 100%"
:min="10"
:max="100"
placeholder="10~100"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16" v-if="modalState.formNode.type === 'triangle'">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="偏移" name="icon.offset">
<a-input-number
v-model:value="modalState.formNode.icon.offset"
style="width: 100%"
:min="-200"
:max="200"
placeholder="-200 ~ 200"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
</template>
</a-form>
<!-- 边排版 -->
<a-form
v-if="modalState.formType === 'edge'"
name="modalStateFromEdge"
@@ -1154,6 +1796,7 @@ function fnGraphLoad() {
<a-select
v-model:value="modalState.formEdge.source"
:options="selectSourceTargetOptions"
:placeholder="t('common.selectPlease')"
>
</a-select>
</a-form-item>
@@ -1167,6 +1810,7 @@ function fnGraphLoad() {
<a-select
v-model:value="modalState.formEdge.target"
:options="selectSourceTargetOptions"
:placeholder="t('common.selectPlease')"
>
</a-select>
</a-form-item>
@@ -1235,7 +1879,7 @@ function fnGraphLoad() {
<a-input
v-model:value="modalState.formEdge.label"
allow-clear
:placeholder="t('common.ipnutPlease')"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>