feat: 拓扑编辑边表单排版

This commit is contained in:
TsMask
2023-12-26 19:35:35 +08:00
parent 45b04975eb
commit f20e9bc000

View File

@@ -307,6 +307,11 @@ const graphG6Data = reactive<Record<string, any>>({
/**图绑定事件 */
function graphEvent(graph: Graph) {
// 调用 graph.add / graph.addItem 方法之后触发
graph.on('afteradditem', evt => {
fnSelectSourceTargetOptionsData();
});
// 鼠标进入节点事件
graph.on('edge:mouseenter', (ev: any) => {
// 获得鼠标当前目标边
@@ -401,20 +406,43 @@ const graphNodeMenu = new Menu({
offsetX: 6,
offseY: 10,
itemTypes: ['node'],
getContent(e) {
const outDiv = document.createElement('div');
outDiv.style.width = '180px';
outDiv.innerHTML = `<ul>
<li>测试01</li>
<li>测试01</li>
<li>测试01</li>
<li>测试01</li>
<li>测试01</li>
</ul>`;
return outDiv;
getContent(evt) {
console.log(evt);
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
background: #e6f7ff;
"
>
<div id="edit" style="cursor: pointer; margin-bottom: 2px">
1. 编辑
</div>
<div id="hide" style="cursor: pointer; margin-bottom: 2px">
2. 隐藏
</div>
</div>
`;
},
handleMenuClick(target, item) {
console.log(target, item);
const targetId = target.id;
switch (targetId) {
case 'edit':
const node = item.getModel();
modalState.title = '节点信息编辑';
modalState.formNodeOrigin = JSON.parse(JSON.stringify(node));
modalState.formNode = Object.assign(modalState.formNode, node);
modalState.formType = 'node';
modalState.visible = true;
console.log(JSON.parse(JSON.stringify(modalState.formNode)));
break;
case 'hide':
graphG6.value.hideItem(item);
break;
}
},
});
@@ -469,7 +497,8 @@ const graphEdgeMenu = new Menu({
modalState.title = '边信息编辑';
modalState.formEdgeOrigin = JSON.parse(JSON.stringify(edge));
modalState.formEdge = Object.assign(modalState.formEdge, edge);
modalState.visibleByEdge = true;
modalState.formType = 'edge';
modalState.visible = true;
break;
case 'hide':
graphG6.value.hideItem(item);
@@ -534,11 +563,13 @@ function fnRanderGraph() {
},
{
type: 'drag-node',
onlyChangeComboSize: true,
shouldEnd: (e: any) => {
return true;
},
},
{ type: 'drag-combo' },
'drag-canvas',
'zoom-canvas',
{ type: 'create-edge', key: 'alt' },
],
},
@@ -594,6 +625,9 @@ function fnRanderGraph() {
graphEvent(graph);
graphG6.value = graph;
//
fnSelectSourceTargetOptionsData();
}
/**查询网元状态 */
@@ -730,12 +764,15 @@ function fnChangeMode(value: any) {
/**对话框对象信息状态类型 */
type ModalStateType = {
/**图边框是否显示 */
visibleByEdge: boolean;
/**对话框是否显示 */
visible: boolean;
/**标题 */
title: string;
/**表单数据 */
form: Record<string, any>;
/**图元素表单类型 */
formType: 'edge' | 'node';
/**图节点表单数据 */
formNodeOrigin: Record<string, any>;
formNode: Record<string, any>;
/**图边表单数据 */
formEdgeOrigin: Record<string, any>;
formEdge: Record<string, any>;
@@ -745,11 +782,20 @@ type ModalStateType = {
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdge: false,
visible: false,
title: '图信息',
form: {
formType: 'edge',
formNodeOrigin: {},
formNode: {
id: '',
msisdn: '',
x: 0,
y: 0,
type: 'circle',
size: 0,
anchorPoints: false,
style: {},
label: '',
labelCfg: {},
},
formEdgeOrigin: {},
formEdge: {
@@ -769,33 +815,42 @@ let modalState: ModalStateType = reactive({
autoRotate: false,
style: {
fill: '#ffffff',
fontSize: 14,
fontWeight: 400,
fontSize: 12,
fontWeight: 500,
},
},
},
confirmLoading: false,
});
/**对话框内表单属性和校验规则 */
const modalStateForm = Form.useForm(
modalState.form,
reactive({
imsi: [{ required: true, message: 'IMSI' + t('common.unableNull') }],
msisdn: [{ required: true, message: 'MSISDN' + t('common.unableNull') }],
staticIp: [
{ required: true, message: 'static ip' + t('common.unableNull') },
],
smData: [
{
required: true,
message: 'Subscribed SM Data' + t('common.unableNull'),
},
],
})
);
/**图元素选择开始结束点 */
let selectSourceTargetOptions = ref<Record<string, any>[]>([]);
/**对话框内表单属性和校验规则 */
/**
* 图元素选择开始结束点数据获取
*/
function fnSelectSourceTargetOptionsData() {
// 节点
graphG6.value.getNodes().forEach((node: any) => {
const info = JSON.parse(JSON.stringify(node.getModel()));
selectSourceTargetOptions.value.push({
value: info.id,
label: info.label,
info,
});
});
// 框
graphG6.value.getCombos().forEach((combo1: any) => {
const info = JSON.parse(JSON.stringify(combo1.getModel()));
selectSourceTargetOptions.value.push({
value: info.id,
label: info.label,
info,
});
});
}
/**图边对话框内表单属性和校验规则 */
const modalStateFormEdge = Form.useForm(
modalState.formEdge,
reactive({
@@ -806,62 +861,59 @@ const modalStateFormEdge = Form.useForm(
})
);
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
// 边编辑还原
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();
}
/**内置边类型 */
const edgeType = [
/**图边内置边类型 */
const edgeTypeOptions = [
{
type: 'line',
description: '连接两个节点的直线',
value: 'line',
label: '直线,连接两个节点的直线',
},
{
type: 'polyline',
description: '多段线段构成的折线,连接两个端点',
value: 'polyline',
label: '折线,多段线段构成的折线,连接两个端点',
},
{
type: 'arc',
description: '连接两个节点的一段圆弧',
value: 'arc',
label: '圆弧线,连接两个节点的一段圆弧',
},
{
type: 'quadratic',
description: '只有一个控制点的曲线',
value: 'quadratic',
label: '二阶贝塞尔曲线,只有一个控制点的曲线',
},
{
type: 'cubic',
description: '有两个控制点的曲线',
value: 'cubic',
label: '三阶贝塞尔曲线,有两个控制点的曲线',
},
{
type: 'cubic-vertical',
description: '垂直方向的三阶贝塞尔曲线',
value: 'cubic-vertical',
label: '垂直方向的三阶贝塞尔曲线',
},
{
type: 'cubic-horizontal',
description: '水平方向的三阶贝塞尔曲线',
value: 'cubic-horizontal',
label: '水平方向的三阶贝塞尔曲线',
},
{
type: 'loop',
description: '自环',
value: 'loop',
label: '自环',
},
];
/**边编辑监听更新视图 */
/**图边标签文本位置 */
const edgePositionOptions = [
{
value: 'start',
label: '开头',
},
{
value: 'middle',
label: '中间',
},
{
value: 'end',
label: '末尾',
},
];
/**图边编辑监听更新视图 */
watch(modalState.formEdge, edge => {
const info = JSON.parse(JSON.stringify(edge));
const edgeId = info.id;
@@ -871,42 +923,122 @@ watch(modalState.formEdge, edge => {
}
});
/**边新增 */
function fnModalOkEdge() {
const info = JSON.parse(JSON.stringify(modalState.formEdge));
const edgeId = info.id;
if (edgeId) {
graphG6.value.removeItem(edgeId);
info.id = `${info.source}~${Date.now()}~${info.target}`;
console.log(info);
graphG6.value.addItem('edge', info);
modalState.visibleByEdge = false;
/**边新增 */
function fnModalOkEdge(edge: any) {
console.log(JSON.parse(JSON.stringify(graphG6.value.save())));
if (!edge.id) {
message.warn({
content: `边元素ID错误`,
duration: 2,
});
return;
}
graphG6.value.removeItem(edge.id);
edge.id = `${edge.source}~${Date.now()}~${edge.target}`;
graphG6.value.addItem('edge', edge);
modalState.visible = false;
modalStateFormEdge.resetFields();
modalState.formEdgeOrigin = {};
}
/**图节点对话框内表单属性和校验规则 */
const modalStateFormNode = Form.useForm(
modalState.formNode,
reactive({
id: [{ required: true, message: '边唯一 ID' }],
source: [{ required: true, message: '起始点 id' }],
target: [{ required: true, message: '结束点 id' }],
type: [{ required: true, message: 'line' }],
})
);
/**图节点内置边类型 */
const nodeType = [
{
type: 'circle',
description: '圆形',
},
{
type: 'rect',
description: '矩形',
},
{
type: 'ellipse',
description: '椭圆',
},
{
type: 'diamond',
description: '菱形',
},
{
type: 'triangle',
description: '三角形',
},
{
type: 'star',
description: '星形',
},
{
type: 'image',
description: '图片',
},
{
type: 'donut',
description: '面包圈',
},
];
/**图节点新增 */
function fnModalOkNode(node: any) {
console.log(JSON.parse(JSON.stringify(graphG6.value.save())));
if (!node.id) {
message.warn({
content: `节点元素ID错误`,
duration: 2,
});
return;
}
graphG6.value.removeItem(node.id);
graphG6.value.addItem('node', node);
modalState.visible = false;
modalStateFormNode.resetFields();
modalState.formNodeOrigin = {};
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
const type = modalState.formType;
console.log(type);
if (type === 'edge') {
const edge = JSON.parse(JSON.stringify(modalState.formEdge));
fnModalOkEdge(edge);
}
if (type === 'node') {
const node = JSON.parse(JSON.stringify(modalState.formNode));
fnModalOkEdge(node);
}
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visible = false;
const type = modalState.formType;
// 边编辑还原
const edgeOrigin = JSON.parse(JSON.stringify(modalState.formEdgeOrigin));
if (type === 'edge' && edgeOrigin.id) {
graphG6.value.removeItem(edgeOrigin.id);
graphG6.value.addItem('edge', edgeOrigin);
modalStateFormEdge.resetFields();
modalState.formEdgeOrigin = {};
}
console.log(JSON.parse(JSON.stringify(graphG6.value.save())));
}
/**边新增 */
function fnAddEdge() {
const model = {
id: '2-190',
source: '2',
target: '190',
type: 'line',
style: {
stroke: 'steelblue',
lineWidth: 5,
},
label: '边新增',
labelCfg: {
position: 'end',
refY: 20,
},
};
graphG6.value.addItem('edge', model);
console.log(JSON.parse(JSON.stringify(graphG6.value.save())));
modalStateFormNode.resetFields();
}
/**保存图数据 */
@@ -945,11 +1077,11 @@ function fnGraphLoad() {
<a-select-option value="edit" key="edit"> 编辑 </a-select-option>
</a-select>
<a-button type="primary" @click="fnAddEdge">
<a-button type="primary">
<template #icon>
<PlusOutlined />
</template>
fnAddEdge
新增
</a-button>
<a-button type="primary" ghost @click="fnGraphLoad">
@@ -980,89 +1112,78 @@ function fnGraphLoad() {
<div ref="graphG6Dom" class="chart"></div>
</a-card>
<!-- 批量增加 -->
<!-- 图元素是辑 -->
<DraggableModal
width="800px"
:body-style="{ maxHeight: '650px', 'overflow-y': 'auto' }"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByEdge"
:visible="modalState.visible"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOkEdge"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
v-if="modalState.formType === 'edge'"
name="modalStateFromEdge"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
label="type"
label="边类型"
name="type"
:label-col="{ span: 3 }"
v-bind="modalStateFormEdge.validateInfos.type"
>
<a-select v-model:value="modalState.formEdge.type">
<a-select-option
v-for="opt in edgeType"
:key="opt.type"
:value="opt.type"
>
{{ opt.type }} : {{ opt.description }}
</a-select-option>
<a-select
v-model:value="modalState.formEdge.type"
:options="edgeTypeOptions"
>
</a-select>
</a-form-item>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="source"
label="起始元素"
name="source"
v-bind="modalStateFormEdge.validateInfos.source"
>
<a-input v-model:value="modalState.formEdge.source" allow-clear>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> 起始点 id </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
<a-select
v-model:value="modalState.formEdge.source"
:options="selectSourceTargetOptions"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="target"
label="结束元素"
name="target"
v-bind="modalStateFormEdge.validateInfos.target"
>
<a-input v-model:value="modalState.formEdge.target" allow-clear>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> 结束点 id </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
<a-select
v-model:value="modalState.formEdge.target"
:options="selectSourceTargetOptions"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="8" :md="8" :xs="24">
<a-form-item label="stroke" name="stroke" help="边的颜色">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="边的颜色" name="stroke">
<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-col :lg="12" :md="12" :xs="24">
<a-form-item label="边宽度" name="lineWidth">
<a-input-number
v-model:value="modalState.formEdge.style.lineWidth"
style="width: 100%"
@@ -1072,22 +1193,12 @@ function fnGraphLoad() {
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :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"
label="开始端箭头"
name="startArrow"
help="边的开始端绘制默认箭头"
>
@@ -1098,29 +1209,63 @@ function fnGraphLoad() {
/>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="结束端箭头"
name="endArrow"
help="边的结束端绘制默认箭头"
>
<a-switch
v-model:checked="modalState.formEdge.style.endArrow"
checked-children=""
un-checked-children=""
/>
</a-form-item>
</a-col>
</a-row>
<a-divider orientation="left">
标签文本 label 及其配置 labelCfg
</a-divider>
<a-divider orientation="left"> 标签文本及其配置 </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-form-item
label="标签文本"
name="label"
:label-col="{ span: 3 }"
help="文本文字,如果没有则不会显示"
>
<a-input
v-model:value="modalState.formEdge.label"
allow-clear
:placeholder="t('common.ipnutPlease')"
>
</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.formEdge.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.formEdge.labelCfg.style.fontSize"
style="width: 100%"
:min="10"
:max="100"
placeholder="<=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="labelCfg.refX"
label="x偏移"
name="labelCfg.refX"
help="标签在 x 方向的偏移量"
>
@@ -1135,7 +1280,7 @@ function fnGraphLoad() {
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="labelCfg.refY"
label="y偏移"
name="labelCfg.refY"
help="标签在 y 方向的偏移量"
>
@@ -1148,32 +1293,25 @@ function fnGraphLoad() {
></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="labelCfg.position"
label="位置"
name="labelCfg.position"
help="文本相对于边的位置"
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
v-model:value="modalState.formEdge.labelCfg.position"
:options="edgePositionOptions"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="8" :md="8" :xs="24">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="labelCfg.autoRotate"
label="跟随边旋转"
name="labelCfg.autoRotate"
help="标签文字是否跟随边旋转"
>
@@ -1184,27 +1322,6 @@ function fnGraphLoad() {
/>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-form-item label="fill" name="fill" help="文本颜色">
<a-input
v-model:value="modalState.formEdge.labelCfg.style.fill"
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="fontSize" name="fontSize">
<a-input-number
v-model:value="modalState.formEdge.labelCfg.style.fontSize"
style="width: 100%"
:min="10"
:max="100"
placeholder="<=100"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
</a-form>
</DraggableModal>