hookehuyr

新增控制节点的自定义节点测试

......@@ -13,9 +13,11 @@
:afterDelete="handleAfterDelete"
:beforeAdd="handleBeforeAdd"
:afterAdd="handleAfterAdd"
@dragend-node="onDragEndNode"
@dblclick-node="onDblclickNode"
@dblclick-edge="onDblClickEdge"
:activityConfig="state.activityConfig"
:controlConfig="state.controlConfig"
>
<!-- 左侧菜单 -->
<template v-slot:menu>
......@@ -33,6 +35,21 @@
</template>
</vue-flow-edit-menu>
</vue-flow-edit-menu-group>
<vue-flow-edit-menu-group label="控制节点" value>
<!-- 注意 key 值的绑定 -->
<vue-flow-edit-menu
v-for="(value, key) in state.controlConfig"
:key="key"
:model="{ control: key, text: value.text, desc: value.desc }"
>
<template v-slot:content>
<div class="activity-menu">
<img :src="value.img" />
<span>{{ value.text }}</span>
</div>
</template>
</vue-flow-edit-menu>
</vue-flow-edit-menu-group>
<vue-flow-edit-menu-group
v-for="(group, groupIndex) in state.menuData"
:label="group.label"
......@@ -297,6 +314,26 @@ export default {
img: "https://cdn.ipadbiz.cn/oa/crowd-node.svg"
}
},
controlConfig: {
start: {
text: "开始",
desc: "描述文字",
color: "#9283ed",
img: "https://cdn.ipadbiz.cn/oa/advertisement-node.svg"
},
stop: {
text: "中止",
desc: "描述文字",
color: "#ed8383",
img: "https://cdn.ipadbiz.cn/oa/coupon-node.svg"
},
end: {
text: "结束",
desc: "描述文字",
color: "#92dba8",
img: "https://cdn.ipadbiz.cn/oa/crowd-node.svg"
}
},
dialogUserFormVisible: false,
activeName: "node",
attr_radio: "基础属性",
......@@ -412,6 +449,7 @@ export default {
model.style = model.style || {};
model.labelCfg = model.labelCfg || { style: {} };
model.data = model.data ? model.data : {};
state.detailModel = model;
editor.openModel();
}
......@@ -526,12 +564,27 @@ export default {
* @param {type} type - The type of the event.
*/
function handleAfterAdd(model: myObj, type: string) {
// TODO: 因为resize会重新绘制,所以可能需要保存操作
if (type === 'node') {
console.log(`新增节点`);
}
if (type === "edge") {
console.log(`新增连接线`);
}
}
/**
* 拖动节点结束回调
*
* @param {myEvent} e - The event object containing information about the drag and drop.
*/
function onDragEndNode(e: myEvent) {
// TODO:可能需要接口保存相应位置,避免拖动窗口时数据丢失
const model = e.item.get("model");
console.log("onDragEndNode", model);
}
/**
* 打印流程图数据结构
*
* @return {void} No return value.
......@@ -568,6 +621,7 @@ export default {
showMultipleSelect: true, // 编辑器是否可以多选
onDblclickNode,
onDragEndNode,
onDblClickEdge,
cancel,
save,
......
......@@ -17,6 +17,14 @@ export const AppData = {
"activity": "coupon",
},
{
"id": "23234567891",
"x": 130,
"y": 130,
"text": "开始",
"desc": "发布",
"control": "start",
},
{
"data": {},
"id": "start-node",
"label": "开始",
......
......@@ -9,7 +9,7 @@
<script>
import {VueFlowEditorProvider} from "@/editor/editor";
import {formatNodeModel, formatPos} from "../utils/utils";
import {formatNodeModel, formatPos,formatNodeModel_control} from "../utils/utils";
export default {
name: "vue-flow-edit-menu",
......@@ -34,8 +34,13 @@
},
drop: async e => {
let model = {...this.model}
// TAG: 自定义节点
// TAG: 自定义节点 更新Model
if (model.activity) {
formatNodeModel(model, this[VueFlowEditorProvider].props.activityConfig)
}
if (model.control) {
formatNodeModel_control(model, this[VueFlowEditorProvider].props.controlConfig)
}
let {id, shape, size} = model
......
......@@ -4,7 +4,7 @@ import {G6} from "@/g6/g6";
import {useBehavior} from "@/behavior";
import {GraphStyle} from "@/utils/styles";
import {registerShape} from "@/shape";
import {formatNodeModel} from "@/utils/utils";
import {formatNodeModel,formatNodeModel_control} from "@/utils/utils";
registerShape(G6)
......@@ -59,8 +59,9 @@ export default {
let {nodes, edges} = data || {}
nodes = nodes || []
edges = edges || []
// TAG: 自定义节点
// TAG: 自定义节点 更新Model
nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig))
nodes.forEach(node => formatNodeModel_control(node, editorProps.controlConfig))
data = {nodes, edges}
......
import {defineComponent,computed, getCurrentInstance, onBeforeUnmount, provide, reactive} from "vue";
import './vue-flow-editor.scss'
import {useCanvasProps, useEditorCommander, useEditorStyles, VueFlowEditorProvider} from "@/editor/editor";
import {formatNodeModel, suffixSize} from "@/utils/utils";
import {formatNodeModel, suffixSize,formatNodeModel_control} from "@/utils/utils";
export default {
name: 'vue-flow-editor',
......@@ -28,8 +28,9 @@ export default {
afterDelete: {type: Function}, // 删除后动作
beforeAdd: {type: Function}, // 添加前校验
afterAdd: {type: Function}, // 添加后动作
// TAG: 自定义节点 - 前端注入类型
activityConfig: {type: Object}, // 注册活动节点
controlConfig: {type: Object}, // 注册活动节点
},
setup(props, context) {
......@@ -58,6 +59,9 @@ export default {
graph.on('node:dblclick', (e) => {
context.emit('dblclick-node', e)
})
graph.on('node:dragend', (e) => {
context.emit('dragend-node', e)
})
graph.on('edge:dblclick', (e) => {
context.emit('dblclick-edge', e)
})
......@@ -79,8 +83,10 @@ export default {
editorState.showModel = false
},
updateModel: (model) => {
// TAG: 自定义节点
// TAG: 自定义节点 更新Model
formatNodeModel(model, props.activityConfig)
formatNodeModel_control(model, props.controlConfig)
commander.commands.update(model)
},
openPreview: () => {
......
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-11-13 17:25:41
* @LastEditTime: 2023-11-14 10:21:38
* @FilePath: /vue-flow-editor/src/shape/activity.ts
* @Description: 文件描述
*/
import {BASE_COLOR} from "@/utils/styles";
// TAG: 自定义节点
// TAG: 自定义节点 - 定义节点
export function registerActivity(G6) {
G6.registerNode('activity', {
options: {
......
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-11-14 11:16:04
* @FilePath: /vue-flow-editor/src/shape/control.ts
* @Description: 自定义控制节点
*/
import {BASE_COLOR} from "@/utils/styles";
// TAG: 自定义节点 - 定义节点
export function registerControl(G6) {
G6.registerNode('control', {
options: {
style: {},
stateStyles: {},
},
setState() {},
drawShape(cfg, group) { // 继承了基类,可以使用drawShape,如果没有继承,必须要有draw
let {text, desc, img, color} = cfg
color = color || BASE_COLOR
desc = desc || '无描述'
const [width, height] = (cfg.size || [200, 80]) as [number, number]
// 定义节点里的图形,名字和配置可以自定义
const shapes = {
keyShape: {
type: 'rect',
attrs: {fill: 'white', x: -width / 2, y: -height / 2, width, height, shadowColor: '#BFC5D2', shadowBlur: 50},
},
sideRect: {
type: 'rect',
attrs: {x: -width / 2, y: -height / 2, width: 6, height, fill: color},
},
img: {
type: 'image',
attrs: {x: height / 4 - width / 2, y: height / 4 - height / 2, width: height / 2, height: height / 2, img},
},
label: {
type: 'text',
attrs: {text, x: height - width / 2, y: height * (3 / 8) - height / 2, fontSize: 16, textAlign: 'left', textBaseline: 'middle', fill: 'black'},
},
desc: {
type: 'text',
attrs: {text: desc, x: height - width / 2, y: height * (5 / 8) - height / 2, fontSize: 12, textAlign: 'left', textBaseline: 'middle', fill: '#999'},
},
}
const addShapes = {}
let keyShape;
Object.keys(shapes).forEach((shapeName) => {
const {type, attrs} = shapes[shapeName]
addShapes[shapeName] = group.addShape(type, {
attrs,
name: shapeName,
draggable: true,
})
if (shapeName === 'keyShape') {
keyShape = addShapes[shapeName]
}
})
group.shapes = addShapes
// 好像是必须返回一个名字叫keyShape,用于寻找节点
return keyShape
},
update(cfg, group) {
group = group.getContainer()
group.shapes.sideRect.attr({fill: cfg.color})
group.shapes.img.attr({img: cfg.img})
group.shapes.label.attr({text: cfg.text})
group.shapes.desc.attr({text: cfg.desc})
},
}, 'single-shape') // 继承了 single-shape 的基类
}
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-11-13 16:31:31
* @LastEditTime: 2023-11-14 11:17:20
* @FilePath: /vue-flow-editor/src/shape/index.ts
* @Description: 文件描述
*/
import {registerAnchor} from "@/shape/anchor";
import {registerEdge} from "@/shape/edge";
// TAG: 自定义节点
// TAG: 自定义节点 - 引入节点
import {registerActivity} from "@/shape/activity";
import {registerControl} from "@/shape/control";
export function registerShape(G6) {
registerAnchor(G6)
registerEdge(G6)
registerActivity(G6)
registerControl(G6)
}
......
......@@ -35,8 +35,9 @@ export const DEFAULT_SIZE = { // 不同类型的默认宽高
circle: [80, 80],
triangle: [80, 80],
star: [80, 80],
// TAG: 自定义节点
// TAG: 自定义节点 - 自定义属性
activity: [200, 80],
control: [200, 80],
}
export function formatPos(option: { x: number, y: number, size: [number, number], shape: string }): { x: number, y: number, size: [number, number], shape } {
......@@ -78,7 +79,7 @@ export function formatPos(option: { x: number, y: number, size: [number, number]
shape: option.shape,
}
}
// TAG: 自定义节点
// TAG: 自定义节点 - 挂载额外属性到model上
export function formatNodeModel(model, activityConfig) {
let {shape, size, activity} = model
......@@ -113,5 +114,50 @@ export function formatNodeModel(model, activityConfig) {
model.shape = 'activity'
model.img = activityConfig[activity].img
model.color = activityConfig[activity].color
}
}
/**
* 自定义节点 Control - 挂载额外属性到model上
*
* @param {any} model - The node model to format.
* @param {object} controlConfig - The configuration for the control.
*/
export function formatNodeModel_control(model: any, controlConfig: object) {
let {shape, size, control} = model
/**
* {
"id": "123456",
"x": 590,
"y": 100,
"text": "广告宣传",
"desc": "通过广告短频宣传",
"control": "advertisement", // 自定义节点的类型
}
*/
if (!!control) {
shape = 'control'
}
model.size = size || DEFAULT_SIZE[shape]
/**
* advertisement: {
text: "广告宣传1",
desc: "通过广告宣传新品",
color: "#9283ed",
img: "https://cdn.ipadbiz.cn/oa/advertisement-node.svg"
}
*/
if (!!control && !!controlConfig[control]) {
// 把 control 的配置挂到model上
model.shape = 'control'
model.img = controlConfig[control].img
model.color = controlConfig[control].color
}
}
......