hookehuyr

新增预览模式展示效果

...@@ -323,6 +323,9 @@ ...@@ -323,6 +323,9 @@
323 <div style="position: absolute; top: 15px; right: 160px; width: 80px;"> 323 <div style="position: absolute; top: 15px; right: 160px; width: 80px;">
324 <div @click="saveData" style="border: 1px solid #009688; width: 100%; height: 25px; border-radius: 5px; background-color: #009688; color: #fff; text-align: center; line-height: 25px; cursor: pointer;">保存</div> 324 <div @click="saveData" style="border: 1px solid #009688; width: 100%; height: 25px; border-radius: 5px; background-color: #009688; color: #fff; text-align: center; line-height: 25px; cursor: pointer;">保存</div>
325 </div> 325 </div>
326 + <div style="position: absolute; top: 15px; right: 260px; width: 80px;">
327 + <div @click="openPreview" style="border: 1px solid #009688; width: 100%; height: 25px; border-radius: 5px; background-color: #009688; color: #fff; text-align: center; line-height: 25px; cursor: pointer;">测试</div>
328 + </div>
326 <div class="select-version-wrapper"> 329 <div class="select-version-wrapper">
327 <el-dropdown trigger="click"> 330 <el-dropdown trigger="click">
328 <div class="select-version-show"> 331 <div class="select-version-show">
...@@ -474,6 +477,32 @@ ...@@ -474,6 +477,32 @@
474 </span> 477 </span>
475 </template> 478 </template>
476 </el-dialog> 479 </el-dialog>
480 +
481 + <el-dialog v-model="state.dialogPreviewVisible" title="预览" width="80%" center>
482 + <div class="preview-container" style="height: 500px; overflow: scroll;">
483 + <vue-flow-editor-form
484 + ref="editor"
485 + height="500px"
486 + :data="flowData"
487 + :grid="showGrid"
488 + :miniMap="showMiniMap"
489 + :onRef="onRef"
490 + :multipleSelect="showMultipleSelect"
491 + :loading="state.editorLoading"
492 + :beforeAdd="handlePreviewBeforeAdd"
493 + @dragstart-node="onDragStartNode"
494 + @click-node="onClickNodePreview"
495 + :controlConfig="state.controlConfig"
496 + :toolbarButtonHandler="toolbarButtonHandler"
497 + ></vue-flow-editor-form>
498 + </div>
499 + <template #footer>
500 + <span class="dialog-footer">
501 + <el-button @click="state.dialogPreviewVisible = false">取消</el-button>
502 + <el-button color="#009688" @click="confirmSort">确认</el-button>
503 + </span>
504 + </template>
505 + </el-dialog>
477 </template> 506 </template>
478 507
479 <script lang="ts"> 508 <script lang="ts">
...@@ -577,6 +606,7 @@ export default { ...@@ -577,6 +606,7 @@ export default {
577 desc: '开始', 606 desc: '开始',
578 color: '#9283ed', 607 color: '#9283ed',
579 img: 'https://cdn.ipadbiz.cn/oa/flow/icon-start1.png', 608 img: 'https://cdn.ipadbiz.cn/oa/flow/icon-start1.png',
609 + type: 'start'
580 }, 610 },
581 flow: { 611 flow: {
582 text: '流程节点', 612 text: '流程节点',
...@@ -602,6 +632,7 @@ export default { ...@@ -602,6 +632,7 @@ export default {
602 }, 632 },
603 search_auth_value: '', 633 search_auth_value: '',
604 dialogSortVisible: false, 634 dialogSortVisible: false,
635 + dialogPreviewVisible: false,
605 dialogUserFormVisible: false, 636 dialogUserFormVisible: false,
606 sortNodes: [], 637 sortNodes: [],
607 dialogUserTags: [], // 同步到用户列表的数据 638 dialogUserTags: [], // 同步到用户列表的数据
...@@ -640,6 +671,7 @@ export default { ...@@ -640,6 +671,7 @@ export default {
640 showConfirmation: true, 671 showConfirmation: true,
641 node_attr: {}, 672 node_attr: {},
642 node_tree: {}, 673 node_tree: {},
674 + show_preview: false,
643 }); 675 });
644 676
645 const setNodeTree = (id: string, data: object) => { 677 const setNodeTree = (id: string, data: object) => {
...@@ -752,6 +784,7 @@ export default { ...@@ -752,6 +784,7 @@ export default {
752 editor.editorState.graph.read(flowData.value) 784 editor.editorState.graph.read(flowData.value)
753 }); 785 });
754 } 786 }
787 +
755 } else { 788 } else {
756 state.reloadLoading = false; 789 state.reloadLoading = false;
757 } 790 }
...@@ -1414,6 +1447,9 @@ export default { ...@@ -1414,6 +1447,9 @@ export default {
1414 * @param {Event} e - The event object representing the click event. 1447 * @param {Event} e - The event object representing the click event.
1415 */ 1448 */
1416 const onClickNode = async (e: myEvent) => { 1449 const onClickNode = async (e: myEvent) => {
1450 + // TODO: 有一个预览状态可以看到节点相应的表单内容
1451 + console.warn('点击节点,如果预览状态,可以预览表单内容');
1452 +
1417 const model = G6.Util.clone(e.item.get('model')); // 节点的基本属性 1453 const model = G6.Util.clone(e.item.get('model')); // 节点的基本属性
1418 model.style = model.style || {} 1454 model.style = model.style || {}
1419 model.labelCfg = model.labelCfg || { style: {} } 1455 model.labelCfg = model.labelCfg || { style: {} }
...@@ -2203,6 +2239,85 @@ export default { ...@@ -2203,6 +2239,85 @@ export default {
2203 } 2239 }
2204 } 2240 }
2205 2241
2242 + const openPreview = () => {
2243 + state.dialogPreviewVisible = true;
2244 + // 创建一个resize事件
2245 + const resizeEvent = new Event('resize');
2246 +
2247 + // 触发resize事件
2248 + window.dispatchEvent(resizeEvent);
2249 + }
2250 +
2251 + /**
2252 + * 单击节点预览回调
2253 + * @param {Event} e - The event object representing the click event.
2254 + */
2255 + const onClickNodePreview = async (e: myEvent) => {
2256 + // TODO: 有一个预览状态可以看到节点相应的表单内容
2257 + console.warn('点击节点,如果预览状态,可以预览表单内容');
2258 +
2259 + const model = G6.Util.clone(e.item.get('model')); // 节点的基本属性
2260 + model.style = model.style || {}
2261 + model.labelCfg = model.labelCfg || { style: {} }
2262 +
2263 + model.data = model.data ? model.data : {};
2264 +
2265 + // 判断是否是流程节点
2266 + let model_id = model.id;
2267 +
2268 + if (model_id !== 'end-node') {
2269 + // 判断是否是开始节点, 不设置负责人
2270 + if (model_id ==='start-node') {
2271 + state.user_attr_set = false;
2272 + } else {
2273 + state.user_attr_set = true;
2274 + }
2275 +
2276 + // 判断是否是抄送节点
2277 + if (model.control === 'cc') {
2278 + state.select_attr_set = false;
2279 + } else {
2280 + state.select_attr_set = true;
2281 + }
2282 +
2283 + flowData.value.nodes.forEach((ele: any, idx: number) => {
2284 + if (ele.id === model.id) {
2285 + state.node_idx = idx; // 详情里显示节点索引
2286 + }
2287 + });
2288 +
2289 + } else {
2290 + state.detailModel = null;
2291 + editor.closeModel();
2292 + }
2293 + }
2294 +
2295 + /**
2296 + * 预览添加前校验
2297 + *
2298 + * @param {object} model - The model object.
2299 + * @param {string} type - The type of the model.
2300 + * @return {Promise} A promise that resolves to a result or rejects with an error.
2301 + */
2302 + function handlePreviewBeforeAdd(model: myObj, type: string): Promise<any> {
2303 + const source = model.source;
2304 + const target = model.target;
2305 + let { nodes, edges } = editor.editorState.graph.save();
2306 +
2307 + if (type === 'edge') {
2308 + ElNotification.error('预览模式禁止操作')
2309 + return Promise.reject('reject')
2310 + }
2311 + }
2312 +
2313 + function onDragStartNode(e) {
2314 + // const source = model.source;
2315 + // const target = model.target;
2316 + // let { nodes, edges } = editor.editorState.graph.save();
2317 + ElNotification.error('预览模式禁止操作')
2318 +
2319 + }
2320 +
2206 return { 2321 return {
2207 state, 2322 state,
2208 rules, 2323 rules,
...@@ -2259,6 +2374,10 @@ export default { ...@@ -2259,6 +2374,10 @@ export default {
2259 saveData, 2374 saveData,
2260 startFlow, 2375 startFlow,
2261 toolbarButtonHandler, 2376 toolbarButtonHandler,
2377 + openPreview,
2378 + onClickNodePreview,
2379 + handlePreviewBeforeAdd,
2380 + onDragStartNode,
2262 2381
2263 onRef: (e: any) => (editor = e), 2382 onRef: (e: any) => (editor = e),
2264 staticPath, 2383 staticPath,
......
1 import {G6} from "@/g6/g6"; 1 import {G6} from "@/g6/g6";
2 +import $ from 'jquery';
2 3
3 const isString = G6.Util.isString 4 const isString = G6.Util.isString
4 const deepMix = G6.Util.deepMix 5 const deepMix = G6.Util.deepMix
...@@ -31,6 +32,12 @@ export function dragNode(G6) { ...@@ -31,6 +32,12 @@ export function dragNode(G6) {
31 }; 32 };
32 }, 33 },
33 onDragStart(e) { 34 onDragStart(e) {
35 +
36 + // 打开预览模式不能拖动
37 + if ($('.preview-container').length) {
38 + return;
39 + }
40 +
34 if (!this.shouldBegin.call(this, e)) { 41 if (!this.shouldBegin.call(this, e)) {
35 return; 42 return;
36 } 43 }
......
1 +/*
2 + * @Date: 2023-10-27 09:29:59
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-02-20 11:02:23
5 + * @FilePath: /vue-flow-editor/src/behavior/hover-anchor-active.ts
6 + * @Description: 文件描述
7 + */
8 +import $ from 'jquery';
1 export function hoverAnchorActive(G6) { 9 export function hoverAnchorActive(G6) {
2 G6.registerBehavior('hover-anchor-active', { 10 G6.registerBehavior('hover-anchor-active', {
3 getEvents() { 11 getEvents() {
...@@ -26,7 +34,10 @@ export function hoverAnchorActive(G6) { ...@@ -26,7 +34,10 @@ export function hoverAnchorActive(G6) {
26 }, 34 },
27 onEnterNode(e) { 35 onEnterNode(e) {
28 const item = e.item; 36 const item = e.item;
37 + // 打开预览模式显示锚点
38 + if (!$('.preview-container').length) {
29 item.showAnchor(this.graph) 39 item.showAnchor(this.graph)
40 + }
30 }, 41 },
31 onLeaveNode(e) { 42 onLeaveNode(e) {
32 try { 43 try {
......
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-12-18 15:00:56 4 + * @LastEditTime: 2024-02-19 18:17:41
5 * @FilePath: /vue-flow-editor/src/editor/index.ts 5 * @FilePath: /vue-flow-editor/src/editor/index.ts
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 import './iconfont' 8 import './iconfont'
9 9
10 import canvas from './vue-flow-editor-canvas' 10 import canvas from './vue-flow-editor-canvas'
11 +import canvasForm from './vue-flow-editor-canvas-form'
11 import editor from './vue-flow-editor' 12 import editor from './vue-flow-editor'
13 +import editorForm from './vue-flow-editor-form'
12 import menu from './vue-flow-editor-menu' 14 import menu from './vue-flow-editor-menu'
13 import toolbar from './vue-flow-editor-toolbar' 15 import toolbar from './vue-flow-editor-toolbar'
14 import editMenu from './vue-flow-edit-menu.vue' 16 import editMenu from './vue-flow-edit-menu.vue'
...@@ -18,7 +20,9 @@ import preview1 from './vue-flow-editor-preview1.vue' ...@@ -18,7 +20,9 @@ import preview1 from './vue-flow-editor-preview1.vue'
18 20
19 export const EditorComponent = [ 21 export const EditorComponent = [
20 canvas, 22 canvas,
23 + canvasForm,
21 editor, 24 editor,
25 + editorForm,
22 menu, 26 menu,
23 toolbar, 27 toolbar,
24 editMenu, 28 editMenu,
......
1 +import {inject, onBeforeUnmount, onMounted,getCurrentInstance, nextTick} from "vue";
2 +import {useEditorPlugins, VueFlowEditorProvider} from "@/editor/editor";
3 +import {G6} from "@/g6/g6";
4 +import {useBehavior} from "@/behavior";
5 +import {GraphStyle} from "@/utils/styles";
6 +import {registerShape} from "@/shape";
7 +import {formatNodeModel,formatNodeModel_control} from "@/utils/utils";
8 +import $ from "jquery";
9 +registerShape(G6)
10 +
11 +export default {
12 + name: 'vue-flow-editor-canvas-form',
13 + props: {
14 + data: {type: Object}, // 渲染的数据
15 + miniMap: {type: [Boolean, Object], default: true}, // 是否需要缩略图
16 + grid: {type: [Boolean, Object], default: true}, // 是否需要网格
17 + },
18 + setup(props, context) {
19 + const { proxy } = getCurrentInstance() as any
20 +
21 + const {editorState, commander, props: editorProps} = inject(VueFlowEditorProvider) as any
22 +
23 + function onMouseenter(e: MouseEvent) {commander.initEvent()}
24 +
25 + function onMouseout(e: MouseEvent) {commander.destroyEvent()}
26 +
27 + function refresh() {
28 + if (!!editorState.graph) {
29 + editorState.graph.destroy()
30 + }
31 + nextTick(() => {
32 + const target = proxy.$refs.target as HTMLElement
33 +
34 + const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement
35 +
36 +
37 +
38 + const behaviors = useBehavior({
39 + multipleSelect: editorState.props.multipleSelect,
40 + dragEdge: {
41 + disabled: editorState.props.disabledDragEdge,
42 + beforeAdd: editorState.props.beforeAdd,
43 + afterAdd: editorState.props.afterAdd,
44 + }
45 + })
46 +
47 + const graph = new G6.Graph({
48 + container: target as HTMLElement,
49 + width,
50 + height,
51 +
52 + modes: {
53 + edit: [
54 + ...behaviors,
55 + ],
56 + },
57 +
58 + ...GraphStyle.default,
59 + })
60 +
61 +
62 + const $read = graph.read
63 + graph.read = (data) => {
64 + let {nodes, edges} = data || {}
65 + nodes = nodes || []
66 + edges = edges || []
67 + // TAG: 自定义节点 更新Model
68 + nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig))
69 + nodes.forEach(node => formatNodeModel_control(node, editorProps.controlConfig))
70 +
71 + data = {nodes, edges}
72 +
73 + $read.apply(graph, [data])
74 + }
75 +
76 + graph.setMode('edit')
77 + graph.read(props.data)
78 + useEditorPlugins(props, graph)
79 + editorState.setGraph(graph)
80 + })
81 + // const target = proxy.$refs.target as HTMLElement
82 +
83 + // const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement
84 +
85 +
86 +
87 + // const behaviors = useBehavior({
88 + // multipleSelect: editorState.props.multipleSelect,
89 + // dragEdge: {
90 + // disabled: editorState.props.disabledDragEdge,
91 + // beforeAdd: editorState.props.beforeAdd,
92 + // afterAdd: editorState.props.afterAdd,
93 + // }
94 + // })
95 +
96 + // const graph = new G6.Graph({
97 + // container: target as HTMLElement,
98 + // width,
99 + // height,
100 +
101 + // modes: {
102 + // edit: [
103 + // ...behaviors,
104 + // ],
105 + // },
106 +
107 + // ...GraphStyle.default,
108 + // })
109 +
110 +
111 + // const $read = graph.read
112 + // graph.read = (data) => {
113 + // let {nodes, edges} = data || {}
114 + // nodes = nodes || []
115 + // edges = edges || []
116 + // // TAG: 自定义节点 更新Model
117 + // nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig))
118 + // nodes.forEach(node => formatNodeModel_control(node, editorProps.controlConfig))
119 +
120 + // data = {nodes, edges}
121 +
122 + // $read.apply(graph, [data])
123 + // }
124 +
125 + // graph.setMode('edit')
126 + // graph.read(props.data)
127 + // useEditorPlugins(props, graph)
128 + // editorState.setGraph(graph)
129 + }
130 +
131 + function onResize() {
132 + refresh()
133 + }
134 +
135 + onMounted(() => {
136 + const target = proxy.$refs.target as HTMLElement
137 + target.addEventListener('mouseenter', onMouseenter)
138 + target.addEventListener('mouseout', onMouseout)
139 + window.addEventListener('resize', onResize)
140 +
141 + refresh()
142 +
143 + })
144 +
145 + onBeforeUnmount(() => {
146 + const target = proxy.$refs.target as HTMLElement
147 + target.removeEventListener('mouseenter', onMouseenter)
148 + target.removeEventListener('mouseout', onMouseout)
149 + window.removeEventListener('resize', onResize)
150 +
151 + commander.destroyEvent()
152 + })
153 +
154 + return () => (
155 + <div class="vue-flow-editor-canvas" ref="root">
156 + <div class="vue-flow-editor-canvas-target" ref="target"/>
157 + </div>
158 + )
159 + },
160 +}
1 -import {inject, onBeforeUnmount, onMounted,getCurrentInstance} from "vue"; 1 +import {inject, onBeforeUnmount, onMounted,getCurrentInstance, onBeforeUpdate} from "vue";
2 import {useEditorPlugins, VueFlowEditorProvider} from "@/editor/editor"; 2 import {useEditorPlugins, VueFlowEditorProvider} from "@/editor/editor";
3 import {G6} from "@/g6/g6"; 3 import {G6} from "@/g6/g6";
4 import {useBehavior} from "@/behavior"; 4 import {useBehavior} from "@/behavior";
...@@ -30,6 +30,7 @@ export default { ...@@ -30,6 +30,7 @@ export default {
30 } 30 }
31 const target = proxy.$refs.target as HTMLElement 31 const target = proxy.$refs.target as HTMLElement
32 const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement 32 const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement
33 +
33 const behaviors = useBehavior({ 34 const behaviors = useBehavior({
34 multipleSelect: editorState.props.multipleSelect, 35 multipleSelect: editorState.props.multipleSelect,
35 dragEdge: { 36 dragEdge: {
...@@ -72,6 +73,7 @@ export default { ...@@ -72,6 +73,7 @@ export default {
72 graph.read(props.data) 73 graph.read(props.data)
73 useEditorPlugins(props, graph) 74 useEditorPlugins(props, graph)
74 editorState.setGraph(graph) 75 editorState.setGraph(graph)
76 +
75 } 77 }
76 78
77 function onResize() { 79 function onResize() {
......
1 +import {defineComponent,computed, getCurrentInstance, onBeforeUnmount, provide, reactive} from "vue";
2 +import './vue-flow-editor.scss'
3 +import {useCanvasProps, useEditorCommander, useEditorStyles, VueFlowEditorProvider} from "@/editor/editor";
4 +import {formatNodeModel, suffixSize,formatNodeModel_control} from "@/utils/utils";
5 +
6 +export default {
7 + name: 'vue-flow-editor-form',
8 + props: {
9 + data: {type: Object}, // 渲染的数据
10 + grid: {type: Boolean, default: true}, // 是否需要网格
11 + miniMap: {type: Boolean, default: false}, // 是否需要缩略图
12 + disabledDragEdge:{type: Boolean}, // 禁用拖拽连线功能
13 + disabledUndo: {type: Boolean}, // 禁用撤销以及重做功能
14 + editorTitle: {type: String}, // 编辑器标题
15 +
16 + height: {type: [String, Number], default: '100%'}, // 画布高度
17 + toolbarHeight: {type: [String, Number], default: '56'}, // 顶部工具栏高度
18 + menuWidth: {type: [String, Number], default: '250'}, // 左侧菜单栏高度
19 + modelWidth: {type: [String, Number], default: '500px'}, // model 弹框的宽度
20 +
21 + onRef: {type: Function}, // 获取引用函数
22 + toolbarButtonHandler: {type: Function}, // 工具栏按钮格式化函数
23 + loading: {type: Boolean}, // 是否开启编辑器的loading状态
24 +
25 + multipleSelect: {type: Boolean, default: false}, // 是否可以多选
26 +
27 + beforeDelete: {type: Function}, // 删除前校验
28 + afterDelete: {type: Function}, // 删除后动作
29 + beforeAdd: {type: Function}, // 添加前校验
30 + afterAdd: {type: Function}, // 添加后动作
31 + // TAG: 自定义节点 - 前端注入类型
32 + activityConfig: {type: Object}, // 注册活动节点
33 + controlConfig: {type: Object}, // 注册活动节点
34 + },
35 + setup(props, context) {
36 +
37 + const styles = useEditorStyles(props)
38 +
39 + const canvasProps = useCanvasProps(props)
40 +
41 +
42 + const modelBodyStyle = computed(() => ({
43 + width: suffixSize(props.modelWidth)
44 + }))
45 +
46 + const editorState = reactive({
47 + graph: null as any, // graph 对象,canvas组件挂载初始化完毕会给这个属性赋值
48 + canvasProps, // 传给canvas组件的属性,同时可以修改
49 + props, // 当前组件接收得到的属性,供子组件通过inject获取
50 + canvasKey: 0, // canvas组件的key,以刷新canvas组件
51 + showModel: false, // 详情对话框是否显示
52 + showPreview: false, // 预览对话框显示
53 + showPreview1: false, // 预览对话框显示
54 + data: null,
55 + refreshCanvas: () => { // 刷新canvas组件
56 + editorState.canvasKey++
57 + },
58 + setGraph: (graph) => {
59 +
60 + editorState.graph = graph
61 + commander.init(graph)
62 +
63 + graph.on('canvas:click', (e) => {
64 + context.emit('click-canvas', e)
65 + })
66 + graph.on('node:mousedown', (e) => {
67 + context.emit('click-node-mousedown', e)
68 + })
69 + graph.on('node:click', (e) => {
70 + context.emit('click-node', e)
71 + })
72 + graph.on('node:dblclick', (e) => {
73 + context.emit('dblclick-node', e)
74 + })
75 + graph.on('node:dragstart', (e) => {
76 + context.emit('dragstart-node', e)
77 + })
78 + graph.on('node:dragend', (e) => {
79 + context.emit('dragend-node', e)
80 + })
81 + graph.on('edge:click', (e) => {
82 + context.emit('click-edge', e)
83 + })
84 + graph.on('edge:dblclick', (e) => {
85 + context.emit('dblclick-edge', e)
86 + })
87 + graph.on('select-change', (e) => {
88 + context.emit('select-change', e)
89 + })
90 + }
91 + })
92 +
93 + const commander = useEditorCommander(editorState)
94 +
95 + const provideContext = {
96 + props,
97 + editorState,
98 + commander,
99 + openModel: () => {
100 + editorState.showModel = true
101 + },
102 + closeModel: () => {
103 + editorState.showModel = false
104 + },
105 + addNode: (model) => {
106 + // TAG: 新增节点
107 + editorState.graph.add('node', model)
108 + },
109 + updateModel: (model) => {
110 + // TAG: 自定义节点 更新Model
111 + formatNodeModel(model, props.activityConfig)
112 + formatNodeModel_control(model, props.controlConfig)
113 +
114 + commander.commands.update(model)
115 + },
116 + openPreview: () => {
117 + editorState.data = editorState.graph.save()
118 + editorState.showPreview = true
119 + },
120 + openPreview1: () => {
121 + editorState.data = editorState.graph.save()
122 + editorState.showPreview1 = true
123 + },
124 + read: (data) => {
125 + if (!!editorState.graph) {
126 + editorState.graph.read(data)
127 + } else {
128 + console.warn('graph is not initialized')
129 + }
130 + },
131 + clearStates: (id) => {
132 + let item = editorState.graph.findById(id)
133 + item.clearStates('selected')
134 + }
135 + }
136 + provide(VueFlowEditorProvider, provideContext)
137 +
138 + if (!!props.onRef) {
139 + props.onRef(provideContext);
140 + }
141 +
142 + const ctx = getCurrentInstance()
143 + Object.assign(ctx, provideContext)
144 +
145 + onBeforeUnmount(() => {
146 + commander.destroy()
147 + if (!!props.onRef) {
148 + props.onRef(null)
149 + }
150 + })
151 +
152 + return () => (
153 + <div class="vue-flow-editor" style={styles.value.root} v-loading={props.loading}>
154 + <div class="vue-flow-editor-right">
155 + <vue-flow-editor-canvas-form data={canvasProps.data} grid={canvasProps.grid} miniMap={canvasProps.miniMap} key={String(editorState.canvasKey) + String(props.multipleSelect) + String(props.disabledDragEdge)}/>
156 + </div>
157 + </div>
158 + )
159 + },
160 +
161 +}
...@@ -428,3 +428,422 @@ $transition: all 300ms cubic-bezier(0.23, 1, 0.32, 1); ...@@ -428,3 +428,422 @@ $transition: all 300ms cubic-bezier(0.23, 1, 0.32, 1);
428 } 428 }
429 } 429 }
430 } 430 }
431 +.vue-flow-editor-form {
432 + height: 100%;
433 + position: relative;
434 + display: flex;
435 + flex-direction: row;
436 + align-items: stretch;
437 + overflow: hidden;
438 +
439 + .vue-flow-editor-left {
440 + overflow: hidden;
441 + box-shadow: $boxshadow;
442 + }
443 +
444 + .vue-flow-editor-right {
445 + flex: 1;
446 + display: flex;
447 + flex-direction: column;
448 +
449 + overflow: hidden;
450 + }
451 +
452 + .vue-flow-editor-menu {
453 + height: 100%;
454 + width: 100%;
455 + display: flex;
456 + flex-direction: column;
457 +
458 + .vue-flow-editor-menu-header {
459 + display: flex;
460 + align-items: center;
461 + justify-content: center;
462 + letter-spacing: 2px;
463 + color: black;
464 + box-shadow: $boxshadow;
465 + box-sizing: border-box;
466 +
467 + img {
468 + height: 100%;
469 + }
470 + }
471 +
472 + .vue-flow-editor-menu-list {
473 + flex: 1;
474 + overflow: hidden;
475 + background-color: #f9f9f9;
476 + user-select: none;
477 +
478 + .vue-flow-editor-menu-list-content {
479 + height: 100%;
480 + width: 100%;
481 + overflow-y: auto;
482 + overflow-x: hidden;
483 + }
484 +
485 + .vue-flow-edit-menu-group {
486 + .vue-flow-edit-menu-group-title {
487 + font-size: 14px;
488 + font-weight: 500;
489 + color: #777;
490 + background-color: white;
491 + padding: 0 16px;
492 + height: 40px;
493 + margin-top: 2px;
494 + display: flex;
495 + justify-content: space-between;
496 + align-items: center;
497 + cursor: pointer;
498 + }
499 +
500 + .vue-flow-edit-menu-group-content {
501 + box-sizing: border-box;
502 + padding: 6px;
503 + }
504 +
505 + &.vue-flow-edit-menu-group-expanded {
506 + .vue-flow-edit-menu-group-title {
507 + i {
508 + transform: rotate(180deg);
509 + }
510 + }
511 + }
512 + }
513 +
514 + .vue-flow-edit-menu {
515 + padding: 9px 16px;
516 + box-sizing: border-box;
517 + background-color: white;
518 + margin-bottom: 2px;
519 + cursor: move;
520 + font-size: 14px;
521 + color: #777;
522 + display: flex;
523 + justify-content: space-between;
524 + align-items: center;
525 + transition: all 300ms linear;
526 +
527 + &:hover {
528 + background-color: rgba(#1F74FF, 0.08);
529 + color: black;
530 + }
531 +
532 + &:active {
533 + background-color: rgba(#1F74FF, 0.08);
534 + }
535 +
536 + &:first-child {
537 + margin-top: 2px;
538 + }
539 + }
540 + }
541 + }
542 +
543 + .vue-flow-editor-toolbar {
544 + display: flex;
545 + align-items: center;
546 + justify-content: flex-start;
547 + padding: 0 16px;
548 + box-shadow: $boxshadow;
549 + user-select: none;
550 +
551 + font-size: 14px;
552 +
553 + & > * {
554 + cursor: pointer;
555 + color: #777;
556 + }
557 +
558 + .vue-flow-editor-toolbar-item {
559 + width: 60px;
560 + height: 48px;
561 + outline: none;
562 + display: inline-flex;
563 + flex-direction: column;
564 + align-items: center;
565 + justify-content: center;
566 +
567 + img {
568 + width: 16px;
569 + height: 16px;
570 + margin-bottom: 4px;
571 + }
572 +
573 + span {
574 + font-size: 12px;
575 + transform: scale(0.8);
576 + }
577 +
578 + &:hover {
579 + background-color: #f6f6f6;
580 + border-radius: 2px;
581 + }
582 +
583 + &.vue-flow-editor-toolbar-item-disabled {
584 + opacity: 0.5;
585 + background-color: transparent;
586 + cursor: not-allowed;
587 + }
588 + }
589 +
590 + .vue-flow-editor-toolbar-divider {
591 + height: 18px;
592 + border-left: solid 1px #ddd;
593 + }
594 + }
595 +
596 + .vue-flow-editor-canvas-form {
597 + flex: 1;
598 + overflow: hidden;
599 + user-select: none;
600 +
601 + .vue-flow-editor-canvas-target {
602 + position: relative;
603 +
604 + .g6-minimap {
605 + position: absolute;
606 + bottom: 0;
607 + right: 0;
608 + background-color: rgba(black, 0.1);
609 + }
610 + }
611 + }
612 +
613 + .vue-flow-editor-model {
614 + // position: absolute;
615 + top: 0;
616 + left: 0;
617 + right: 0;
618 + bottom: 0;
619 + z-index: 2;
620 + transition: $transition;
621 + transition-duration: 500ms;
622 +
623 + &:before {
624 + // position: absolute;
625 + top: 0;
626 + bottom: 0;
627 + left: 0;
628 + right: 0;
629 + background-color: rgba(black, 0.1);
630 + content: '';
631 + transition: $transition;
632 + transition-duration: 500ms;
633 + }
634 +
635 + .vue-flow-editor-model-body {
636 + background-color: white;
637 + position: absolute;
638 + top: 57px;
639 + bottom: 0;
640 + right: 0;
641 + display: flex;
642 + flex-direction: column;
643 + border-left: solid 1px #ddd;
644 + box-shadow: $boxshadow;
645 + transition: $transition;
646 + transition-duration: 500ms;
647 +
648 + .vue-flow-editor-model-head {
649 + display: flex;
650 + justify-content: flex-end;
651 + align-items: center;
652 + padding: 0 16px;
653 + box-shadow: $boxshadow;
654 +
655 + & > i {
656 + cursor: pointer;
657 + }
658 + }
659 +
660 + .vue-flow-editor-model-content {
661 + flex: 1;
662 + overflow: auto;
663 + }
664 +
665 + .vue-flow-editor-model-foot {
666 + height: 50px;
667 + display: flex;
668 + justify-content: center;
669 + align-items: center;
670 + border-top: solid 1px #eee;
671 + box-shadow: $boxshadow;
672 + }
673 + }
674 +
675 + &.vue-flow-editor-transition-enter-active, &.vue-flow-editor-transition-leave-active {
676 + .vue-flow-editor-model-body {
677 + transform: translateX(0);
678 + }
679 +
680 + &:before {
681 + opacity: 1;
682 + }
683 + }
684 +
685 + &.vue-flow-editor-transition-enter, &.vue-flow-editor-transition-leave-to {
686 + .vue-flow-editor-model-body {
687 + transform: translateX(100%);
688 + }
689 +
690 + &:before {
691 + opacity: 0;
692 + }
693 + }
694 + }
695 +
696 + .vue-flow-editor-preview {
697 + position: fixed;
698 + top: 0;
699 + bottom: 0;
700 + left: 0;
701 + right: 0;
702 + z-index: 2;
703 + display: flex;
704 + align-items: center;
705 + justify-content: center;
706 + transition: $transition;
707 + transition-duration: 500ms;
708 + user-select: none;
709 +
710 + &:before {
711 + position: absolute;
712 + top: 0;
713 + left: 0;
714 + right: 0;
715 + bottom: 0;
716 + content: '';
717 + background-color: rgba(black, 0.1);
718 + transition: $transition;
719 + transition-duration: 500ms;
720 + }
721 +
722 + .vue-flow-editor-preview-body {
723 + width: 80%;
724 + height: 80%;
725 + background-color: white;
726 + border-radius: 12px;
727 + position: relative;
728 + z-index: 1;
729 + transition: $transition;
730 + transition-duration: 500ms;
731 +
732 + .vue-flow-editor-preview-close {
733 + position: absolute;
734 + top: -20px;
735 + right: -20px;
736 + font-size: 20px;
737 + background-color: white;
738 + height: 40px;
739 + width: 40px;
740 + border-radius: 20px;
741 + display: flex;
742 + align-items: center;
743 + justify-content: center;
744 + box-shadow: $boxshadow;
745 + border: 1px solid #eee;
746 + color: #999;
747 + cursor: pointer;
748 + }
749 + }
750 +
751 + &.vue-flow-editor-preview-transition-enter-active, &.vue-flow-editor-preview-transition-leave-active {
752 + .vue-flow-editor-preview-body {
753 + transform: translateX(0);
754 + }
755 +
756 + &:before {
757 + opacity: 1;
758 + }
759 + }
760 +
761 + &.vue-flow-editor-preview-transition-enter, &.vue-flow-editor-preview-transition-leave-to {
762 + .vue-flow-editor-preview-body {
763 + transform: translateY(-15%);
764 + opacity: 0;
765 + }
766 +
767 + &:before {
768 + opacity: 0;
769 + }
770 + }
771 + }
772 + .vue-flow-editor-preview1 {
773 + position: fixed;
774 + top: 0;
775 + bottom: 0;
776 + left: 0;
777 + right: 0;
778 + z-index: 99;
779 + display: flex;
780 + align-items: center;
781 + justify-content: center;
782 + transition: $transition;
783 + transition-duration: 500ms;
784 + user-select: none;
785 +
786 + &:before {
787 + position: absolute;
788 + top: 0;
789 + left: 0;
790 + right: 0;
791 + bottom: 0;
792 + content: '';
793 + // background-color: rgba(black, 0.1);
794 + background-color: #fff;
795 + transition: $transition;
796 + transition-duration: 500ms;
797 + }
798 +
799 + .vue-flow-editor-preview-body {
800 + width: 80%;
801 + height: 80%;
802 + background-color: white;
803 + border-radius: 12px;
804 + position: relative;
805 + z-index: 1;
806 + transition: $transition;
807 + transition-duration: 500ms;
808 +
809 + .vue-flow-editor-preview-close {
810 + position: absolute;
811 + top: -20px;
812 + right: -20px;
813 + font-size: 20px;
814 + background-color: white;
815 + height: 40px;
816 + width: 40px;
817 + border-radius: 20px;
818 + display: flex;
819 + align-items: center;
820 + justify-content: center;
821 + box-shadow: $boxshadow;
822 + border: 1px solid #eee;
823 + color: #999;
824 + cursor: pointer;
825 + }
826 + }
827 +
828 + &.vue-flow-editor-preview-transition-enter-active, &.vue-flow-editor-preview-transition-leave-active {
829 + .vue-flow-editor-preview-body {
830 + transform: translateX(0);
831 + }
832 +
833 + &:before {
834 + opacity: 1;
835 + }
836 + }
837 +
838 + &.vue-flow-editor-preview-transition-enter, &.vue-flow-editor-preview-transition-leave-to {
839 + .vue-flow-editor-preview-body {
840 + transform: translateY(-15%);
841 + opacity: 0;
842 + }
843 +
844 + &:before {
845 + opacity: 0;
846 + }
847 + }
848 + }
849 +}
......
1 +/*
2 + * @Date: 2023-10-27 09:29:59
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-02-19 17:17:22
5 + * @FilePath: /vue-flow-editor/src/index.ts
6 + * @Description: 文件描述
7 + */
1 import {EditorComponent} from './editor' 8 import {EditorComponent} from './editor'
2 import {externalComponents} from "@/components"; 9 import {externalComponents} from "@/components";
3 import VueFlowEditor from './editor/vue-flow-editor' 10 import VueFlowEditor from './editor/vue-flow-editor'
11 +import VueFlowEditorPreview from './editor/vue-flow-editor-form'
4 import VueFLowEditMenu from './editor/vue-flow-edit-menu.vue' 12 import VueFLowEditMenu from './editor/vue-flow-edit-menu.vue'
5 import VueFLowEditMenuGroup from './editor/vue-flow-edit-menu-group.vue' 13 import VueFLowEditMenuGroup from './editor/vue-flow-edit-menu-group.vue'
6 import {formatPos} from "@/utils/utils"; 14 import {formatPos} from "@/utils/utils";
7 15
8 export default { 16 export default {
9 VueFlowEditor, 17 VueFlowEditor,
18 + VueFlowEditorPreview,
10 VueFLowEditMenu, 19 VueFLowEditMenu,
11 VueFLowEditMenuGroup, 20 VueFLowEditMenuGroup,
12 formatPos, 21 formatPos,
......