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