feat: 拓扑编辑边

This commit is contained in:
TsMask
2023-12-26 16:24:54 +08:00
parent 6441a1efaf
commit 45b04975eb

View File

@@ -1,12 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, ref } from 'vue'; import { reactive, onMounted, ref, watch } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listNe, stateNe } from '@/api/ne/ne'; import { listNe, stateNe } from '@/api/ne/ne';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { Graph, Menu, Tooltip, Util, registerBehavior } from '@antv/g6'; import { Graph, Menu, Tooltip } from '@antv/g6';
import { message, Modal, Form, notification } from 'ant-design-vue/lib'; import { message, Modal, Form, notification } from 'ant-design-vue/lib';
const { t } = useI18n(); const { t } = useI18n();
/**图DOM节点实例对象 */ /**图DOM节点实例对象 */
@@ -352,6 +353,49 @@ function graphEvent(graph: Graph) {
}); });
} }
/**图画布右击菜单 */
const graphCanvasMenu = new Menu({
offsetX: 6,
offseY: 10,
itemTypes: ['canvas'],
getContent(evt) {
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
"
>
<div id="show" style="cursor: pointer; margin-bottom: 2px">
1. 显示所有隐藏项
</div>
</div>`;
},
handleMenuClick(target, item) {
console.log(target, item);
const targetId = target.id;
switch (targetId) {
case 'show':
// 显示节点
graphG6.value.getNodes().forEach((node: any) => {
if (!node.isVisible()) {
graphG6.value.showItem(node);
graphG6.value.refreshItem(node);
}
});
// 显示边
graphG6.value.getEdges().forEach((edge: any) => {
if (!edge.isVisible()) {
graphG6.value.showItem(edge);
graphG6.value.refreshItem(edge);
}
});
break;
}
},
});
/**图节点右击菜单 */ /**图节点右击菜单 */
const graphNodeMenu = new Menu({ const graphNodeMenu = new Menu({
offsetX: 6, offsetX: 6,
@@ -397,6 +441,7 @@ const graphEdgeMenu = new Menu({
offseY: 10, offseY: 10,
itemTypes: ['edge'], itemTypes: ['edge'],
getContent(evt) { getContent(evt) {
console.log(evt);
return ` return `
<div <div
style=" style="
@@ -420,11 +465,10 @@ const graphEdgeMenu = new Menu({
const targetId = target.id; const targetId = target.id;
switch (targetId) { switch (targetId) {
case 'edit': case 'edit':
const info = item.getModel(); const edge = item.getModel();
console.log(Object.assign({}, info));
modalState.title = '边信息编辑'; modalState.title = '边信息编辑';
modalState.formEdge = Object.assign(modalState.formEdge, info); modalState.formEdgeOrigin = JSON.parse(JSON.stringify(edge));
modalState.formEdge = Object.assign(modalState.formEdge, edge);
modalState.visibleByEdge = true; modalState.visibleByEdge = true;
break; break;
case 'hide': case 'hide':
@@ -518,8 +562,10 @@ function fnRanderGraph() {
type: 'polyline', type: 'polyline',
style: { style: {
offset: 20, // 拐弯处距离节点最小距离 offset: 20, // 拐弯处距离节点最小距离
radius: 4, // 拐弯处的圆角弧度,若不设置则为直角 radius: 2, // 拐弯处的圆角弧度,若不设置则为直角
stroke: '#ffffff',
lineWidth: 1, lineWidth: 1,
cursor: 'pointer',
}, },
}, },
// defaultEdge: { // defaultEdge: {
@@ -533,7 +579,13 @@ function fnRanderGraph() {
fillOpacity: 0.1, fillOpacity: 0.1,
}, },
}, },
plugins: [graphNodeMenu, graphNodeTooltip, graphEdgeMenu, graphEdgeTooltip], plugins: [
graphCanvasMenu,
graphNodeMenu,
graphNodeTooltip,
graphEdgeMenu,
graphEdgeTooltip,
],
}); });
graph.data(graphG6Data); graph.data(graphG6Data);
graph.render(); graph.render();
@@ -685,6 +737,7 @@ type ModalStateType = {
/**表单数据 */ /**表单数据 */
form: Record<string, any>; form: Record<string, any>;
/**图边表单数据 */ /**图边表单数据 */
formEdgeOrigin: Record<string, any>;
formEdge: Record<string, any>; formEdge: Record<string, any>;
/**确定按钮 loading */ /**确定按钮 loading */
confirmLoading: boolean; confirmLoading: boolean;
@@ -698,19 +751,27 @@ let modalState: ModalStateType = reactive({
id: '', id: '',
msisdn: '', msisdn: '',
}, },
formEdgeOrigin: {},
formEdge: { formEdge: {
id: '', id: '',
source: '', source: '',
target: '', target: '',
type: '', type: '',
style: {}, style: {
stroke: '#ffffff',
lineWidth: 1,
},
label: '', label: '',
labelCfg: { labelCfg: {
refX: 0, refX: 0,
refY: 0, refY: 0,
position: 'middle', position: 'middle',
autoRotate: false, autoRotate: false,
style: {}, style: {
fill: '#ffffff',
fontSize: 14,
fontWeight: 400,
},
}, },
}, },
confirmLoading: false, confirmLoading: false,
@@ -750,9 +811,18 @@ const modalStateFormEdge = Form.useForm(
* 进行表达规则校验 * 进行表达规则校验
*/ */
function fnModalCancel() { function fnModalCancel() {
// 边编辑还原
modalState.visibleByEdge = false; modalState.visibleByEdge = false;
const info = JSON.parse(JSON.stringify(modalState.formEdgeOrigin));
if (info.id) {
graphG6.value.removeItem(info.id);
console.log(info);
graphG6.value.addItem('edge', info);
modalStateFormEdge.resetFields();
modalState.formEdgeOrigin = {};
}
modalStateForm.resetFields(); modalStateForm.resetFields();
modalStateFormEdge.resetFields();
} }
/**内置边类型 */ /**内置边类型 */
@@ -791,25 +861,29 @@ const edgeType = [
}, },
]; ];
/**边编辑监听更新视图 */
watch(modalState.formEdge, edge => {
const info = JSON.parse(JSON.stringify(edge));
const edgeId = info.id;
if (edgeId) {
graphG6.value.clearItemStates(edgeId, 'selected');
graphG6.value.updateItem(edgeId, info);
}
});
/**边新增 */ /**边新增 */
function fnModalOkEdge() { function fnModalOkEdge() {
const model = { const info = JSON.parse(JSON.stringify(modalState.formEdge));
id: '2-190', const edgeId = info.id;
source: '2', if (edgeId) {
target: '190', graphG6.value.removeItem(edgeId);
type: 'line', info.id = `${info.source}~${Date.now()}~${info.target}`;
style: { console.log(info);
stroke: 'steelblue', graphG6.value.addItem('edge', info);
lineWidth: 5, modalState.visibleByEdge = false;
}, modalStateFormEdge.resetFields();
label: '边新增', modalState.formEdgeOrigin = {};
labelCfg: { }
position: 'end',
refY: 20,
},
};
graphG6.value.addItem('edge', model);
console.log(JSON.parse(JSON.stringify(graphG6.value.save()))); console.log(JSON.parse(JSON.stringify(graphG6.value.save())));
} }
@@ -976,86 +1050,159 @@ function fnGraphLoad() {
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="16">
<a-col :lg="8" :md="8" :xs="24">
<a-form-item label="stroke" name="stroke" help="边的颜色">
<a-input
v-model:value="modalState.formEdge.style.stroke"
type="color"
:placeholder="t('common.ipnutPlease')"
size="small"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-form-item label="lineWidth" name="lineWidth" help="边宽度">
<a-input-number
v-model:value="modalState.formEdge.style.lineWidth"
style="width: 100%"
:min="1"
:max="100"
placeholder="<=100"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-form-item
label="endArrow"
name="endArrow"
help="边的结束端绘制默认箭头"
>
<a-switch
v-model:checked="modalState.formEdge.style.endArrow"
checked-children=""
un-checked-children=""
/>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-form-item
label="startArrow"
name="startArrow"
help="边的开始端绘制默认箭头"
>
<a-switch
v-model:checked="modalState.formEdge.style.startArrow"
checked-children=""
un-checked-children=""
/>
</a-form-item>
</a-col>
</a-row>
<a-divider orientation="left">
标签文本 label 及其配置 labelCfg
</a-divider>
<div>{{ modalState.formEdge.labelCfg }}</div>
<a-form-item label="label" name="label" :label-col="{ span: 3 }">
<a-input v-model:value="modalState.formEdge.label" allow-clear>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> 文本文字如果没有则不会显示 </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="label" name="label"> <a-form-item
<a-input v-model:value="modalState.formEdge.label" allow-clear> label="labelCfg.refX"
<template #prefix> name="labelCfg.refX"
<a-tooltip placement="topLeft"> help="标签在 x 方向的偏移量"
<template #title> 文本文字如果没有则不会显示 </template> >
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" /> <a-input-number
</a-tooltip> v-model:value="modalState.formEdge.labelCfg.refX"
</template> style="width: 100%"
</a-input> :min="-100"
:max="100"
placeholder="<=100"
></a-input-number>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="labelCfg" name="labelCfg"> <a-form-item
{{ modalState.form.labelCfg }} label="labelCfg.refY"
name="labelCfg.refY"
help="标签在 y 方向的偏移量"
>
<a-input-number
v-model:value="modalState.formEdge.labelCfg.refY"
style="width: 100%"
:min="-100"
:max="100"
placeholder="<=100"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="labelCfg.position"
name="labelCfg.position"
help="文本相对于边的位置"
>
<a-select v-model:value="modalState.formEdge.labelCfg.position">
<a-select-option
v-for="opt in [
{ type: 'start' },
{ type: 'middle' },
{ type: 'end' },
]"
:key="opt.type"
:value="opt.type"
>
{{ opt.type }}
</a-select-option>
</a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="8" :md="8" :xs="24">
<a-form-item label="labelCfg.refX" name="labelCfg.refX"> <a-form-item
<a-input label="labelCfg.autoRotate"
v-model:value="modalState.formEdge.labelCfg.refX" name="labelCfg.autoRotate"
allow-clear help="标签文字是否跟随边旋转"
> >
<template #prefix> <a-switch
<a-tooltip placement="topLeft"> v-model:checked="modalState.formEdge.labelCfg.autoRotate"
<template #title> 标签在 x 方向的偏移量 </template> checked-children="是"
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" /> un-checked-children="否"
</a-tooltip> />
</template>
</a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="8" :md="8" :xs="24">
<a-form-item label="labelCfg.refY" name="labelCfg.refY"> <a-form-item label="fill" name="fill" help="文本颜色">
<a-input <a-input
v-model:value="modalState.formEdge.labelCfg.refY" v-model:value="modalState.formEdge.labelCfg.style.fill"
allow-clear type="color"
> :placeholder="t('common.ipnutPlease')"
<template #prefix> size="small"
<a-tooltip placement="topLeft"> ></a-input>
<template #title> 标签在 x 方向的偏移量 </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> <a-col :lg="8" :md="8" :xs="24">
<a-row :gutter="16"> <a-form-item label="fontSize" name="fontSize">
<a-col :lg="12" :md="12" :xs="24"> <a-input-number
<a-form-item label="labelCfg.refX" name="labelCfg.refX"> v-model:value="modalState.formEdge.labelCfg.style.fontSize"
<a-input style="width: 100%"
v-model:value="modalState.formEdge.labelCfg.refX" :min="10"
allow-clear :max="100"
> placeholder="<=100"
<template #prefix> ></a-input-number>
<a-tooltip placement="topLeft">
<template #title> 标签在 x 方向的偏移量 </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="labelCfg.refY" name="labelCfg.refY">
<a-input
v-model:value="modalState.formEdge.labelCfg.refY"
allow-clear
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> 标签在 x 方向的偏移量 </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>