hookehuyr

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

...@@ -13,9 +13,11 @@ ...@@ -13,9 +13,11 @@
13 :afterDelete="handleAfterDelete" 13 :afterDelete="handleAfterDelete"
14 :beforeAdd="handleBeforeAdd" 14 :beforeAdd="handleBeforeAdd"
15 :afterAdd="handleAfterAdd" 15 :afterAdd="handleAfterAdd"
16 + @dragend-node="onDragEndNode"
16 @dblclick-node="onDblclickNode" 17 @dblclick-node="onDblclickNode"
17 @dblclick-edge="onDblClickEdge" 18 @dblclick-edge="onDblClickEdge"
18 :activityConfig="state.activityConfig" 19 :activityConfig="state.activityConfig"
20 + :controlConfig="state.controlConfig"
19 > 21 >
20 <!-- 左侧菜单 --> 22 <!-- 左侧菜单 -->
21 <template v-slot:menu> 23 <template v-slot:menu>
...@@ -33,6 +35,21 @@ ...@@ -33,6 +35,21 @@
33 </template> 35 </template>
34 </vue-flow-edit-menu> 36 </vue-flow-edit-menu>
35 </vue-flow-edit-menu-group> 37 </vue-flow-edit-menu-group>
38 + <vue-flow-edit-menu-group label="控制节点" value>
39 + <!-- 注意 key 值的绑定 -->
40 + <vue-flow-edit-menu
41 + v-for="(value, key) in state.controlConfig"
42 + :key="key"
43 + :model="{ control: key, text: value.text, desc: value.desc }"
44 + >
45 + <template v-slot:content>
46 + <div class="activity-menu">
47 + <img :src="value.img" />
48 + <span>{{ value.text }}</span>
49 + </div>
50 + </template>
51 + </vue-flow-edit-menu>
52 + </vue-flow-edit-menu-group>
36 <vue-flow-edit-menu-group 53 <vue-flow-edit-menu-group
37 v-for="(group, groupIndex) in state.menuData" 54 v-for="(group, groupIndex) in state.menuData"
38 :label="group.label" 55 :label="group.label"
...@@ -297,6 +314,26 @@ export default { ...@@ -297,6 +314,26 @@ export default {
297 img: "https://cdn.ipadbiz.cn/oa/crowd-node.svg" 314 img: "https://cdn.ipadbiz.cn/oa/crowd-node.svg"
298 } 315 }
299 }, 316 },
317 + controlConfig: {
318 + start: {
319 + text: "开始",
320 + desc: "描述文字",
321 + color: "#9283ed",
322 + img: "https://cdn.ipadbiz.cn/oa/advertisement-node.svg"
323 + },
324 + stop: {
325 + text: "中止",
326 + desc: "描述文字",
327 + color: "#ed8383",
328 + img: "https://cdn.ipadbiz.cn/oa/coupon-node.svg"
329 + },
330 + end: {
331 + text: "结束",
332 + desc: "描述文字",
333 + color: "#92dba8",
334 + img: "https://cdn.ipadbiz.cn/oa/crowd-node.svg"
335 + }
336 + },
300 dialogUserFormVisible: false, 337 dialogUserFormVisible: false,
301 activeName: "node", 338 activeName: "node",
302 attr_radio: "基础属性", 339 attr_radio: "基础属性",
...@@ -412,6 +449,7 @@ export default { ...@@ -412,6 +449,7 @@ export default {
412 model.style = model.style || {}; 449 model.style = model.style || {};
413 model.labelCfg = model.labelCfg || { style: {} }; 450 model.labelCfg = model.labelCfg || { style: {} };
414 451
452 + model.data = model.data ? model.data : {};
415 state.detailModel = model; 453 state.detailModel = model;
416 editor.openModel(); 454 editor.openModel();
417 } 455 }
...@@ -526,12 +564,27 @@ export default { ...@@ -526,12 +564,27 @@ export default {
526 * @param {type} type - The type of the event. 564 * @param {type} type - The type of the event.
527 */ 565 */
528 function handleAfterAdd(model: myObj, type: string) { 566 function handleAfterAdd(model: myObj, type: string) {
567 + // TODO: 因为resize会重新绘制,所以可能需要保存操作
568 + if (type === 'node') {
569 + console.log(`新增节点`);
570 + }
529 if (type === "edge") { 571 if (type === "edge") {
530 console.log(`新增连接线`); 572 console.log(`新增连接线`);
531 } 573 }
532 } 574 }
533 575
534 /** 576 /**
577 + * 拖动节点结束回调
578 + *
579 + * @param {myEvent} e - The event object containing information about the drag and drop.
580 + */
581 + function onDragEndNode(e: myEvent) {
582 + // TODO:可能需要接口保存相应位置,避免拖动窗口时数据丢失
583 + const model = e.item.get("model");
584 + console.log("onDragEndNode", model);
585 + }
586 +
587 + /**
535 * 打印流程图数据结构 588 * 打印流程图数据结构
536 * 589 *
537 * @return {void} No return value. 590 * @return {void} No return value.
...@@ -568,6 +621,7 @@ export default { ...@@ -568,6 +621,7 @@ export default {
568 showMultipleSelect: true, // 编辑器是否可以多选 621 showMultipleSelect: true, // 编辑器是否可以多选
569 622
570 onDblclickNode, 623 onDblclickNode,
624 + onDragEndNode,
571 onDblClickEdge, 625 onDblClickEdge,
572 cancel, 626 cancel,
573 save, 627 save,
......
...@@ -17,6 +17,14 @@ export const AppData = { ...@@ -17,6 +17,14 @@ export const AppData = {
17 "activity": "coupon", 17 "activity": "coupon",
18 }, 18 },
19 { 19 {
20 + "id": "23234567891",
21 + "x": 130,
22 + "y": 130,
23 + "text": "开始",
24 + "desc": "发布",
25 + "control": "start",
26 + },
27 + {
20 "data": {}, 28 "data": {},
21 "id": "start-node", 29 "id": "start-node",
22 "label": "开始", 30 "label": "开始",
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 9
10 <script> 10 <script>
11 import {VueFlowEditorProvider} from "@/editor/editor"; 11 import {VueFlowEditorProvider} from "@/editor/editor";
12 - import {formatNodeModel, formatPos} from "../utils/utils"; 12 + import {formatNodeModel, formatPos,formatNodeModel_control} from "../utils/utils";
13 13
14 export default { 14 export default {
15 name: "vue-flow-edit-menu", 15 name: "vue-flow-edit-menu",
...@@ -34,8 +34,13 @@ ...@@ -34,8 +34,13 @@
34 }, 34 },
35 drop: async e => { 35 drop: async e => {
36 let model = {...this.model} 36 let model = {...this.model}
37 - // TAG: 自定义节点 37 + // TAG: 自定义节点 更新Model
38 + if (model.activity) {
38 formatNodeModel(model, this[VueFlowEditorProvider].props.activityConfig) 39 formatNodeModel(model, this[VueFlowEditorProvider].props.activityConfig)
40 + }
41 + if (model.control) {
42 + formatNodeModel_control(model, this[VueFlowEditorProvider].props.controlConfig)
43 + }
39 44
40 let {id, shape, size} = model 45 let {id, shape, size} = model
41 46
......
...@@ -4,7 +4,7 @@ import {G6} from "@/g6/g6"; ...@@ -4,7 +4,7 @@ import {G6} from "@/g6/g6";
4 import {useBehavior} from "@/behavior"; 4 import {useBehavior} from "@/behavior";
5 import {GraphStyle} from "@/utils/styles"; 5 import {GraphStyle} from "@/utils/styles";
6 import {registerShape} from "@/shape"; 6 import {registerShape} from "@/shape";
7 -import {formatNodeModel} from "@/utils/utils"; 7 +import {formatNodeModel,formatNodeModel_control} from "@/utils/utils";
8 8
9 registerShape(G6) 9 registerShape(G6)
10 10
...@@ -59,8 +59,9 @@ export default { ...@@ -59,8 +59,9 @@ export default {
59 let {nodes, edges} = data || {} 59 let {nodes, edges} = data || {}
60 nodes = nodes || [] 60 nodes = nodes || []
61 edges = edges || [] 61 edges = edges || []
62 - // TAG: 自定义节点 62 + // TAG: 自定义节点 更新Model
63 nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig)) 63 nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig))
64 + nodes.forEach(node => formatNodeModel_control(node, editorProps.controlConfig))
64 65
65 data = {nodes, edges} 66 data = {nodes, edges}
66 67
......
1 import {defineComponent,computed, getCurrentInstance, onBeforeUnmount, provide, reactive} from "vue"; 1 import {defineComponent,computed, getCurrentInstance, onBeforeUnmount, provide, reactive} from "vue";
2 import './vue-flow-editor.scss' 2 import './vue-flow-editor.scss'
3 import {useCanvasProps, useEditorCommander, useEditorStyles, VueFlowEditorProvider} from "@/editor/editor"; 3 import {useCanvasProps, useEditorCommander, useEditorStyles, VueFlowEditorProvider} from "@/editor/editor";
4 -import {formatNodeModel, suffixSize} from "@/utils/utils"; 4 +import {formatNodeModel, suffixSize,formatNodeModel_control} from "@/utils/utils";
5 5
6 export default { 6 export default {
7 name: 'vue-flow-editor', 7 name: 'vue-flow-editor',
...@@ -28,8 +28,9 @@ export default { ...@@ -28,8 +28,9 @@ export default {
28 afterDelete: {type: Function}, // 删除后动作 28 afterDelete: {type: Function}, // 删除后动作
29 beforeAdd: {type: Function}, // 添加前校验 29 beforeAdd: {type: Function}, // 添加前校验
30 afterAdd: {type: Function}, // 添加后动作 30 afterAdd: {type: Function}, // 添加后动作
31 - 31 + // TAG: 自定义节点 - 前端注入类型
32 activityConfig: {type: Object}, // 注册活动节点 32 activityConfig: {type: Object}, // 注册活动节点
33 + controlConfig: {type: Object}, // 注册活动节点
33 }, 34 },
34 setup(props, context) { 35 setup(props, context) {
35 36
...@@ -58,6 +59,9 @@ export default { ...@@ -58,6 +59,9 @@ export default {
58 graph.on('node:dblclick', (e) => { 59 graph.on('node:dblclick', (e) => {
59 context.emit('dblclick-node', e) 60 context.emit('dblclick-node', e)
60 }) 61 })
62 + graph.on('node:dragend', (e) => {
63 + context.emit('dragend-node', e)
64 + })
61 graph.on('edge:dblclick', (e) => { 65 graph.on('edge:dblclick', (e) => {
62 context.emit('dblclick-edge', e) 66 context.emit('dblclick-edge', e)
63 }) 67 })
...@@ -79,8 +83,10 @@ export default { ...@@ -79,8 +83,10 @@ export default {
79 editorState.showModel = false 83 editorState.showModel = false
80 }, 84 },
81 updateModel: (model) => { 85 updateModel: (model) => {
82 - // TAG: 自定义节点 86 + // TAG: 自定义节点 更新Model
83 formatNodeModel(model, props.activityConfig) 87 formatNodeModel(model, props.activityConfig)
88 + formatNodeModel_control(model, props.controlConfig)
89 +
84 commander.commands.update(model) 90 commander.commands.update(model)
85 }, 91 },
86 openPreview: () => { 92 openPreview: () => {
......
1 /* 1 /*
2 * @Date: 2023-10-27 09:29:59 2 * @Date: 2023-10-27 09:29:59
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2023-11-13 17:25:41 4 + * @LastEditTime: 2023-11-14 10:21:38
5 * @FilePath: /vue-flow-editor/src/shape/activity.ts 5 * @FilePath: /vue-flow-editor/src/shape/activity.ts
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 import {BASE_COLOR} from "@/utils/styles"; 8 import {BASE_COLOR} from "@/utils/styles";
9 -// TAG: 自定义节点 9 +// TAG: 自定义节点 - 定义节点
10 export function registerActivity(G6) { 10 export function registerActivity(G6) {
11 G6.registerNode('activity', { 11 G6.registerNode('activity', {
12 options: { 12 options: {
......
1 +/*
2 + * @Date: 2023-10-27 09:29:59
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-11-14 11:16:04
5 + * @FilePath: /vue-flow-editor/src/shape/control.ts
6 + * @Description: 自定义控制节点
7 + */
8 +import {BASE_COLOR} from "@/utils/styles";
9 +
10 +// TAG: 自定义节点 - 定义节点
11 +export function registerControl(G6) {
12 + G6.registerNode('control', {
13 + options: {
14 + style: {},
15 + stateStyles: {},
16 + },
17 + setState() {},
18 + drawShape(cfg, group) { // 继承了基类,可以使用drawShape,如果没有继承,必须要有draw
19 +
20 + let {text, desc, img, color} = cfg
21 + color = color || BASE_COLOR
22 + desc = desc || '无描述'
23 +
24 + const [width, height] = (cfg.size || [200, 80]) as [number, number]
25 + // 定义节点里的图形,名字和配置可以自定义
26 + const shapes = {
27 + keyShape: {
28 + type: 'rect',
29 + attrs: {fill: 'white', x: -width / 2, y: -height / 2, width, height, shadowColor: '#BFC5D2', shadowBlur: 50},
30 + },
31 + sideRect: {
32 + type: 'rect',
33 + attrs: {x: -width / 2, y: -height / 2, width: 6, height, fill: color},
34 + },
35 + img: {
36 + type: 'image',
37 + attrs: {x: height / 4 - width / 2, y: height / 4 - height / 2, width: height / 2, height: height / 2, img},
38 + },
39 + label: {
40 + type: 'text',
41 + attrs: {text, x: height - width / 2, y: height * (3 / 8) - height / 2, fontSize: 16, textAlign: 'left', textBaseline: 'middle', fill: 'black'},
42 + },
43 + desc: {
44 + type: 'text',
45 + attrs: {text: desc, x: height - width / 2, y: height * (5 / 8) - height / 2, fontSize: 12, textAlign: 'left', textBaseline: 'middle', fill: '#999'},
46 + },
47 + }
48 +
49 + const addShapes = {}
50 +
51 + let keyShape;
52 + Object.keys(shapes).forEach((shapeName) => {
53 + const {type, attrs} = shapes[shapeName]
54 + addShapes[shapeName] = group.addShape(type, {
55 + attrs,
56 + name: shapeName,
57 + draggable: true,
58 + })
59 +
60 + if (shapeName === 'keyShape') {
61 + keyShape = addShapes[shapeName]
62 + }
63 + })
64 +
65 + group.shapes = addShapes
66 + // 好像是必须返回一个名字叫keyShape,用于寻找节点
67 + return keyShape
68 + },
69 + update(cfg, group) {
70 + group = group.getContainer()
71 + group.shapes.sideRect.attr({fill: cfg.color})
72 + group.shapes.img.attr({img: cfg.img})
73 + group.shapes.label.attr({text: cfg.text})
74 + group.shapes.desc.attr({text: cfg.desc})
75 + },
76 + }, 'single-shape') // 继承了 single-shape 的基类
77 +}
1 /* 1 /*
2 * @Date: 2023-10-27 09:29:59 2 * @Date: 2023-10-27 09:29:59
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2023-11-13 16:31:31 4 + * @LastEditTime: 2023-11-14 11:17:20
5 * @FilePath: /vue-flow-editor/src/shape/index.ts 5 * @FilePath: /vue-flow-editor/src/shape/index.ts
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 import {registerAnchor} from "@/shape/anchor"; 8 import {registerAnchor} from "@/shape/anchor";
9 import {registerEdge} from "@/shape/edge"; 9 import {registerEdge} from "@/shape/edge";
10 -// TAG: 自定义节点 10 +// TAG: 自定义节点 - 引入节点
11 import {registerActivity} from "@/shape/activity"; 11 import {registerActivity} from "@/shape/activity";
12 +import {registerControl} from "@/shape/control";
12 13
13 export function registerShape(G6) { 14 export function registerShape(G6) {
14 registerAnchor(G6) 15 registerAnchor(G6)
15 registerEdge(G6) 16 registerEdge(G6)
16 registerActivity(G6) 17 registerActivity(G6)
18 + registerControl(G6)
17 } 19 }
......
...@@ -35,8 +35,9 @@ export const DEFAULT_SIZE = { // 不同类型的默认宽高 ...@@ -35,8 +35,9 @@ export const DEFAULT_SIZE = { // 不同类型的默认宽高
35 circle: [80, 80], 35 circle: [80, 80],
36 triangle: [80, 80], 36 triangle: [80, 80],
37 star: [80, 80], 37 star: [80, 80],
38 - // TAG: 自定义节点 38 + // TAG: 自定义节点 - 自定义属性
39 activity: [200, 80], 39 activity: [200, 80],
40 + control: [200, 80],
40 } 41 }
41 42
42 export function formatPos(option: { x: number, y: number, size: [number, number], shape: string }): { x: number, y: number, size: [number, number], shape } { 43 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] ...@@ -78,7 +79,7 @@ export function formatPos(option: { x: number, y: number, size: [number, number]
78 shape: option.shape, 79 shape: option.shape,
79 } 80 }
80 } 81 }
81 -// TAG: 自定义节点 82 +// TAG: 自定义节点 - 挂载额外属性到model上
82 export function formatNodeModel(model, activityConfig) { 83 export function formatNodeModel(model, activityConfig) {
83 let {shape, size, activity} = model 84 let {shape, size, activity} = model
84 85
...@@ -113,5 +114,50 @@ export function formatNodeModel(model, activityConfig) { ...@@ -113,5 +114,50 @@ export function formatNodeModel(model, activityConfig) {
113 model.shape = 'activity' 114 model.shape = 'activity'
114 model.img = activityConfig[activity].img 115 model.img = activityConfig[activity].img
115 model.color = activityConfig[activity].color 116 model.color = activityConfig[activity].color
117 +
118 + }
119 +}
120 +
121 +/**
122 + * 自定义节点 Control - 挂载额外属性到model上
123 + *
124 + * @param {any} model - The node model to format.
125 + * @param {object} controlConfig - The configuration for the control.
126 + */
127 +export function formatNodeModel_control(model: any, controlConfig: object) {
128 + let {shape, size, control} = model
129 +
130 + /**
131 + * {
132 + "id": "123456",
133 + "x": 590,
134 + "y": 100,
135 + "text": "广告宣传",
136 + "desc": "通过广告短频宣传",
137 + "control": "advertisement", // 自定义节点的类型
138 + }
139 + */
140 +
141 + if (!!control) {
142 + shape = 'control'
116 } 143 }
144 +
145 + model.size = size || DEFAULT_SIZE[shape]
146 +
147 + /**
148 + * advertisement: {
149 + text: "广告宣传1",
150 + desc: "通过广告宣传新品",
151 + color: "#9283ed",
152 + img: "https://cdn.ipadbiz.cn/oa/advertisement-node.svg"
153 + }
154 + */
155 +
156 + if (!!control && !!controlConfig[control]) {
157 + // 把 control 的配置挂到model上
158 + model.shape = 'control'
159 + model.img = controlConfig[control].img
160 + model.color = controlConfig[control].color
161 + }
162 +
117 } 163 }
......