hookehuyr

新增预览模式展示效果

......@@ -323,6 +323,9 @@
<div style="position: absolute; top: 15px; right: 160px; width: 80px;">
<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>
</div>
<div style="position: absolute; top: 15px; right: 260px; width: 80px;">
<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>
</div>
<div class="select-version-wrapper">
<el-dropdown trigger="click">
<div class="select-version-show">
......@@ -474,6 +477,32 @@
</span>
</template>
</el-dialog>
<el-dialog v-model="state.dialogPreviewVisible" title="预览" width="80%" center>
<div class="preview-container" style="height: 500px; overflow: scroll;">
<vue-flow-editor-form
ref="editor"
height="500px"
:data="flowData"
:grid="showGrid"
:miniMap="showMiniMap"
:onRef="onRef"
:multipleSelect="showMultipleSelect"
:loading="state.editorLoading"
:beforeAdd="handlePreviewBeforeAdd"
@dragstart-node="onDragStartNode"
@click-node="onClickNodePreview"
:controlConfig="state.controlConfig"
:toolbarButtonHandler="toolbarButtonHandler"
></vue-flow-editor-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="state.dialogPreviewVisible = false">取消</el-button>
<el-button color="#009688" @click="confirmSort">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts">
......@@ -577,6 +606,7 @@ export default {
desc: '开始',
color: '#9283ed',
img: 'https://cdn.ipadbiz.cn/oa/flow/icon-start1.png',
type: 'start'
},
flow: {
text: '流程节点',
......@@ -602,6 +632,7 @@ export default {
},
search_auth_value: '',
dialogSortVisible: false,
dialogPreviewVisible: false,
dialogUserFormVisible: false,
sortNodes: [],
dialogUserTags: [], // 同步到用户列表的数据
......@@ -640,6 +671,7 @@ export default {
showConfirmation: true,
node_attr: {},
node_tree: {},
show_preview: false,
});
const setNodeTree = (id: string, data: object) => {
......@@ -752,6 +784,7 @@ export default {
editor.editorState.graph.read(flowData.value)
});
}
} else {
state.reloadLoading = false;
}
......@@ -1414,6 +1447,9 @@ export default {
* @param {Event} e - The event object representing the click event.
*/
const onClickNode = async (e: myEvent) => {
// TODO: 有一个预览状态可以看到节点相应的表单内容
console.warn('点击节点,如果预览状态,可以预览表单内容');
const model = G6.Util.clone(e.item.get('model')); // 节点的基本属性
model.style = model.style || {}
model.labelCfg = model.labelCfg || { style: {} }
......@@ -2203,6 +2239,85 @@ export default {
}
}
const openPreview = () => {
state.dialogPreviewVisible = true;
// 创建一个resize事件
const resizeEvent = new Event('resize');
// 触发resize事件
window.dispatchEvent(resizeEvent);
}
/**
* 单击节点预览回调
* @param {Event} e - The event object representing the click event.
*/
const onClickNodePreview = async (e: myEvent) => {
// TODO: 有一个预览状态可以看到节点相应的表单内容
console.warn('点击节点,如果预览状态,可以预览表单内容');
const model = G6.Util.clone(e.item.get('model')); // 节点的基本属性
model.style = model.style || {}
model.labelCfg = model.labelCfg || { style: {} }
model.data = model.data ? model.data : {};
// 判断是否是流程节点
let model_id = model.id;
if (model_id !== 'end-node') {
// 判断是否是开始节点, 不设置负责人
if (model_id ==='start-node') {
state.user_attr_set = false;
} else {
state.user_attr_set = true;
}
// 判断是否是抄送节点
if (model.control === 'cc') {
state.select_attr_set = false;
} else {
state.select_attr_set = true;
}
flowData.value.nodes.forEach((ele: any, idx: number) => {
if (ele.id === model.id) {
state.node_idx = idx; // 详情里显示节点索引
}
});
} else {
state.detailModel = null;
editor.closeModel();
}
}
/**
* 预览添加前校验
*
* @param {object} model - The model object.
* @param {string} type - The type of the model.
* @return {Promise} A promise that resolves to a result or rejects with an error.
*/
function handlePreviewBeforeAdd(model: myObj, type: string): Promise<any> {
const source = model.source;
const target = model.target;
let { nodes, edges } = editor.editorState.graph.save();
if (type === 'edge') {
ElNotification.error('预览模式禁止操作')
return Promise.reject('reject')
}
}
function onDragStartNode(e) {
// const source = model.source;
// const target = model.target;
// let { nodes, edges } = editor.editorState.graph.save();
ElNotification.error('预览模式禁止操作')
}
return {
state,
rules,
......@@ -2259,6 +2374,10 @@ export default {
saveData,
startFlow,
toolbarButtonHandler,
openPreview,
onClickNodePreview,
handlePreviewBeforeAdd,
onDragStartNode,
onRef: (e: any) => (editor = e),
staticPath,
......
import {G6} from "@/g6/g6";
import $ from 'jquery';
const isString = G6.Util.isString
const deepMix = G6.Util.deepMix
......@@ -31,6 +32,12 @@ export function dragNode(G6) {
};
},
onDragStart(e) {
// 打开预览模式不能拖动
if ($('.preview-container').length) {
return;
}
if (!this.shouldBegin.call(this, e)) {
return;
}
......
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-02-20 11:02:23
* @FilePath: /vue-flow-editor/src/behavior/hover-anchor-active.ts
* @Description: 文件描述
*/
import $ from 'jquery';
export function hoverAnchorActive(G6) {
G6.registerBehavior('hover-anchor-active', {
getEvents() {
......@@ -26,7 +34,10 @@ export function hoverAnchorActive(G6) {
},
onEnterNode(e) {
const item = e.item;
// 打开预览模式显示锚点
if (!$('.preview-container').length) {
item.showAnchor(this.graph)
}
},
onLeaveNode(e) {
try {
......
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-12-18 15:00:56
* @LastEditTime: 2024-02-19 18:17:41
* @FilePath: /vue-flow-editor/src/editor/index.ts
* @Description: 文件描述
*/
import './iconfont'
import canvas from './vue-flow-editor-canvas'
import canvasForm from './vue-flow-editor-canvas-form'
import editor from './vue-flow-editor'
import editorForm from './vue-flow-editor-form'
import menu from './vue-flow-editor-menu'
import toolbar from './vue-flow-editor-toolbar'
import editMenu from './vue-flow-edit-menu.vue'
......@@ -18,7 +20,9 @@ import preview1 from './vue-flow-editor-preview1.vue'
export const EditorComponent = [
canvas,
canvasForm,
editor,
editorForm,
menu,
toolbar,
editMenu,
......
import {inject, onBeforeUnmount, onMounted,getCurrentInstance, nextTick} from "vue";
import {useEditorPlugins, VueFlowEditorProvider} from "@/editor/editor";
import {G6} from "@/g6/g6";
import {useBehavior} from "@/behavior";
import {GraphStyle} from "@/utils/styles";
import {registerShape} from "@/shape";
import {formatNodeModel,formatNodeModel_control} from "@/utils/utils";
import $ from "jquery";
registerShape(G6)
export default {
name: 'vue-flow-editor-canvas-form',
props: {
data: {type: Object}, // 渲染的数据
miniMap: {type: [Boolean, Object], default: true}, // 是否需要缩略图
grid: {type: [Boolean, Object], default: true}, // 是否需要网格
},
setup(props, context) {
const { proxy } = getCurrentInstance() as any
const {editorState, commander, props: editorProps} = inject(VueFlowEditorProvider) as any
function onMouseenter(e: MouseEvent) {commander.initEvent()}
function onMouseout(e: MouseEvent) {commander.destroyEvent()}
function refresh() {
if (!!editorState.graph) {
editorState.graph.destroy()
}
nextTick(() => {
const target = proxy.$refs.target as HTMLElement
const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement
const behaviors = useBehavior({
multipleSelect: editorState.props.multipleSelect,
dragEdge: {
disabled: editorState.props.disabledDragEdge,
beforeAdd: editorState.props.beforeAdd,
afterAdd: editorState.props.afterAdd,
}
})
const graph = new G6.Graph({
container: target as HTMLElement,
width,
height,
modes: {
edit: [
...behaviors,
],
},
...GraphStyle.default,
})
const $read = graph.read
graph.read = (data) => {
let {nodes, edges} = data || {}
nodes = nodes || []
edges = edges || []
// TAG: 自定义节点 更新Model
nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig))
nodes.forEach(node => formatNodeModel_control(node, editorProps.controlConfig))
data = {nodes, edges}
$read.apply(graph, [data])
}
graph.setMode('edit')
graph.read(props.data)
useEditorPlugins(props, graph)
editorState.setGraph(graph)
})
// const target = proxy.$refs.target as HTMLElement
// const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement
// const behaviors = useBehavior({
// multipleSelect: editorState.props.multipleSelect,
// dragEdge: {
// disabled: editorState.props.disabledDragEdge,
// beforeAdd: editorState.props.beforeAdd,
// afterAdd: editorState.props.afterAdd,
// }
// })
// const graph = new G6.Graph({
// container: target as HTMLElement,
// width,
// height,
// modes: {
// edit: [
// ...behaviors,
// ],
// },
// ...GraphStyle.default,
// })
// const $read = graph.read
// graph.read = (data) => {
// let {nodes, edges} = data || {}
// nodes = nodes || []
// edges = edges || []
// // TAG: 自定义节点 更新Model
// nodes.forEach(node => formatNodeModel(node, editorProps.activityConfig))
// nodes.forEach(node => formatNodeModel_control(node, editorProps.controlConfig))
// data = {nodes, edges}
// $read.apply(graph, [data])
// }
// graph.setMode('edit')
// graph.read(props.data)
// useEditorPlugins(props, graph)
// editorState.setGraph(graph)
}
function onResize() {
refresh()
}
onMounted(() => {
const target = proxy.$refs.target as HTMLElement
target.addEventListener('mouseenter', onMouseenter)
target.addEventListener('mouseout', onMouseout)
window.addEventListener('resize', onResize)
refresh()
})
onBeforeUnmount(() => {
const target = proxy.$refs.target as HTMLElement
target.removeEventListener('mouseenter', onMouseenter)
target.removeEventListener('mouseout', onMouseout)
window.removeEventListener('resize', onResize)
commander.destroyEvent()
})
return () => (
<div class="vue-flow-editor-canvas" ref="root">
<div class="vue-flow-editor-canvas-target" ref="target"/>
</div>
)
},
}
import {inject, onBeforeUnmount, onMounted,getCurrentInstance} from "vue";
import {inject, onBeforeUnmount, onMounted,getCurrentInstance, onBeforeUpdate} from "vue";
import {useEditorPlugins, VueFlowEditorProvider} from "@/editor/editor";
import {G6} from "@/g6/g6";
import {useBehavior} from "@/behavior";
......@@ -30,6 +30,7 @@ export default {
}
const target = proxy.$refs.target as HTMLElement
const {offsetHeight: height, offsetWidth: width} = proxy.$refs.root as HTMLElement
const behaviors = useBehavior({
multipleSelect: editorState.props.multipleSelect,
dragEdge: {
......@@ -72,6 +73,7 @@ export default {
graph.read(props.data)
useEditorPlugins(props, graph)
editorState.setGraph(graph)
}
function onResize() {
......
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,formatNodeModel_control} from "@/utils/utils";
export default {
name: 'vue-flow-editor-form',
props: {
data: {type: Object}, // 渲染的数据
grid: {type: Boolean, default: true}, // 是否需要网格
miniMap: {type: Boolean, default: false}, // 是否需要缩略图
disabledDragEdge:{type: Boolean}, // 禁用拖拽连线功能
disabledUndo: {type: Boolean}, // 禁用撤销以及重做功能
editorTitle: {type: String}, // 编辑器标题
height: {type: [String, Number], default: '100%'}, // 画布高度
toolbarHeight: {type: [String, Number], default: '56'}, // 顶部工具栏高度
menuWidth: {type: [String, Number], default: '250'}, // 左侧菜单栏高度
modelWidth: {type: [String, Number], default: '500px'}, // model 弹框的宽度
onRef: {type: Function}, // 获取引用函数
toolbarButtonHandler: {type: Function}, // 工具栏按钮格式化函数
loading: {type: Boolean}, // 是否开启编辑器的loading状态
multipleSelect: {type: Boolean, default: false}, // 是否可以多选
beforeDelete: {type: Function}, // 删除前校验
afterDelete: {type: Function}, // 删除后动作
beforeAdd: {type: Function}, // 添加前校验
afterAdd: {type: Function}, // 添加后动作
// TAG: 自定义节点 - 前端注入类型
activityConfig: {type: Object}, // 注册活动节点
controlConfig: {type: Object}, // 注册活动节点
},
setup(props, context) {
const styles = useEditorStyles(props)
const canvasProps = useCanvasProps(props)
const modelBodyStyle = computed(() => ({
width: suffixSize(props.modelWidth)
}))
const editorState = reactive({
graph: null as any, // graph 对象,canvas组件挂载初始化完毕会给这个属性赋值
canvasProps, // 传给canvas组件的属性,同时可以修改
props, // 当前组件接收得到的属性,供子组件通过inject获取
canvasKey: 0, // canvas组件的key,以刷新canvas组件
showModel: false, // 详情对话框是否显示
showPreview: false, // 预览对话框显示
showPreview1: false, // 预览对话框显示
data: null,
refreshCanvas: () => { // 刷新canvas组件
editorState.canvasKey++
},
setGraph: (graph) => {
editorState.graph = graph
commander.init(graph)
graph.on('canvas:click', (e) => {
context.emit('click-canvas', e)
})
graph.on('node:mousedown', (e) => {
context.emit('click-node-mousedown', e)
})
graph.on('node:click', (e) => {
context.emit('click-node', e)
})
graph.on('node:dblclick', (e) => {
context.emit('dblclick-node', e)
})
graph.on('node:dragstart', (e) => {
context.emit('dragstart-node', e)
})
graph.on('node:dragend', (e) => {
context.emit('dragend-node', e)
})
graph.on('edge:click', (e) => {
context.emit('click-edge', e)
})
graph.on('edge:dblclick', (e) => {
context.emit('dblclick-edge', e)
})
graph.on('select-change', (e) => {
context.emit('select-change', e)
})
}
})
const commander = useEditorCommander(editorState)
const provideContext = {
props,
editorState,
commander,
openModel: () => {
editorState.showModel = true
},
closeModel: () => {
editorState.showModel = false
},
addNode: (model) => {
// TAG: 新增节点
editorState.graph.add('node', model)
},
updateModel: (model) => {
// TAG: 自定义节点 更新Model
formatNodeModel(model, props.activityConfig)
formatNodeModel_control(model, props.controlConfig)
commander.commands.update(model)
},
openPreview: () => {
editorState.data = editorState.graph.save()
editorState.showPreview = true
},
openPreview1: () => {
editorState.data = editorState.graph.save()
editorState.showPreview1 = true
},
read: (data) => {
if (!!editorState.graph) {
editorState.graph.read(data)
} else {
console.warn('graph is not initialized')
}
},
clearStates: (id) => {
let item = editorState.graph.findById(id)
item.clearStates('selected')
}
}
provide(VueFlowEditorProvider, provideContext)
if (!!props.onRef) {
props.onRef(provideContext);
}
const ctx = getCurrentInstance()
Object.assign(ctx, provideContext)
onBeforeUnmount(() => {
commander.destroy()
if (!!props.onRef) {
props.onRef(null)
}
})
return () => (
<div class="vue-flow-editor" style={styles.value.root} v-loading={props.loading}>
<div class="vue-flow-editor-right">
<vue-flow-editor-canvas-form data={canvasProps.data} grid={canvasProps.grid} miniMap={canvasProps.miniMap} key={String(editorState.canvasKey) + String(props.multipleSelect) + String(props.disabledDragEdge)}/>
</div>
</div>
)
},
}
......@@ -428,3 +428,422 @@ $transition: all 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
}
}
.vue-flow-editor-form {
height: 100%;
position: relative;
display: flex;
flex-direction: row;
align-items: stretch;
overflow: hidden;
.vue-flow-editor-left {
overflow: hidden;
box-shadow: $boxshadow;
}
.vue-flow-editor-right {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.vue-flow-editor-menu {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
.vue-flow-editor-menu-header {
display: flex;
align-items: center;
justify-content: center;
letter-spacing: 2px;
color: black;
box-shadow: $boxshadow;
box-sizing: border-box;
img {
height: 100%;
}
}
.vue-flow-editor-menu-list {
flex: 1;
overflow: hidden;
background-color: #f9f9f9;
user-select: none;
.vue-flow-editor-menu-list-content {
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.vue-flow-edit-menu-group {
.vue-flow-edit-menu-group-title {
font-size: 14px;
font-weight: 500;
color: #777;
background-color: white;
padding: 0 16px;
height: 40px;
margin-top: 2px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.vue-flow-edit-menu-group-content {
box-sizing: border-box;
padding: 6px;
}
&.vue-flow-edit-menu-group-expanded {
.vue-flow-edit-menu-group-title {
i {
transform: rotate(180deg);
}
}
}
}
.vue-flow-edit-menu {
padding: 9px 16px;
box-sizing: border-box;
background-color: white;
margin-bottom: 2px;
cursor: move;
font-size: 14px;
color: #777;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 300ms linear;
&:hover {
background-color: rgba(#1F74FF, 0.08);
color: black;
}
&:active {
background-color: rgba(#1F74FF, 0.08);
}
&:first-child {
margin-top: 2px;
}
}
}
}
.vue-flow-editor-toolbar {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 16px;
box-shadow: $boxshadow;
user-select: none;
font-size: 14px;
& > * {
cursor: pointer;
color: #777;
}
.vue-flow-editor-toolbar-item {
width: 60px;
height: 48px;
outline: none;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 16px;
height: 16px;
margin-bottom: 4px;
}
span {
font-size: 12px;
transform: scale(0.8);
}
&:hover {
background-color: #f6f6f6;
border-radius: 2px;
}
&.vue-flow-editor-toolbar-item-disabled {
opacity: 0.5;
background-color: transparent;
cursor: not-allowed;
}
}
.vue-flow-editor-toolbar-divider {
height: 18px;
border-left: solid 1px #ddd;
}
}
.vue-flow-editor-canvas-form {
flex: 1;
overflow: hidden;
user-select: none;
.vue-flow-editor-canvas-target {
position: relative;
.g6-minimap {
position: absolute;
bottom: 0;
right: 0;
background-color: rgba(black, 0.1);
}
}
}
.vue-flow-editor-model {
// position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
transition: $transition;
transition-duration: 500ms;
&:before {
// position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(black, 0.1);
content: '';
transition: $transition;
transition-duration: 500ms;
}
.vue-flow-editor-model-body {
background-color: white;
position: absolute;
top: 57px;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
border-left: solid 1px #ddd;
box-shadow: $boxshadow;
transition: $transition;
transition-duration: 500ms;
.vue-flow-editor-model-head {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 16px;
box-shadow: $boxshadow;
& > i {
cursor: pointer;
}
}
.vue-flow-editor-model-content {
flex: 1;
overflow: auto;
}
.vue-flow-editor-model-foot {
height: 50px;
display: flex;
justify-content: center;
align-items: center;
border-top: solid 1px #eee;
box-shadow: $boxshadow;
}
}
&.vue-flow-editor-transition-enter-active, &.vue-flow-editor-transition-leave-active {
.vue-flow-editor-model-body {
transform: translateX(0);
}
&:before {
opacity: 1;
}
}
&.vue-flow-editor-transition-enter, &.vue-flow-editor-transition-leave-to {
.vue-flow-editor-model-body {
transform: translateX(100%);
}
&:before {
opacity: 0;
}
}
}
.vue-flow-editor-preview {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
transition: $transition;
transition-duration: 500ms;
user-select: none;
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
background-color: rgba(black, 0.1);
transition: $transition;
transition-duration: 500ms;
}
.vue-flow-editor-preview-body {
width: 80%;
height: 80%;
background-color: white;
border-radius: 12px;
position: relative;
z-index: 1;
transition: $transition;
transition-duration: 500ms;
.vue-flow-editor-preview-close {
position: absolute;
top: -20px;
right: -20px;
font-size: 20px;
background-color: white;
height: 40px;
width: 40px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $boxshadow;
border: 1px solid #eee;
color: #999;
cursor: pointer;
}
}
&.vue-flow-editor-preview-transition-enter-active, &.vue-flow-editor-preview-transition-leave-active {
.vue-flow-editor-preview-body {
transform: translateX(0);
}
&:before {
opacity: 1;
}
}
&.vue-flow-editor-preview-transition-enter, &.vue-flow-editor-preview-transition-leave-to {
.vue-flow-editor-preview-body {
transform: translateY(-15%);
opacity: 0;
}
&:before {
opacity: 0;
}
}
}
.vue-flow-editor-preview1 {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: center;
transition: $transition;
transition-duration: 500ms;
user-select: none;
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
// background-color: rgba(black, 0.1);
background-color: #fff;
transition: $transition;
transition-duration: 500ms;
}
.vue-flow-editor-preview-body {
width: 80%;
height: 80%;
background-color: white;
border-radius: 12px;
position: relative;
z-index: 1;
transition: $transition;
transition-duration: 500ms;
.vue-flow-editor-preview-close {
position: absolute;
top: -20px;
right: -20px;
font-size: 20px;
background-color: white;
height: 40px;
width: 40px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $boxshadow;
border: 1px solid #eee;
color: #999;
cursor: pointer;
}
}
&.vue-flow-editor-preview-transition-enter-active, &.vue-flow-editor-preview-transition-leave-active {
.vue-flow-editor-preview-body {
transform: translateX(0);
}
&:before {
opacity: 1;
}
}
&.vue-flow-editor-preview-transition-enter, &.vue-flow-editor-preview-transition-leave-to {
.vue-flow-editor-preview-body {
transform: translateY(-15%);
opacity: 0;
}
&:before {
opacity: 0;
}
}
}
}
......
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-02-19 17:17:22
* @FilePath: /vue-flow-editor/src/index.ts
* @Description: 文件描述
*/
import {EditorComponent} from './editor'
import {externalComponents} from "@/components";
import VueFlowEditor from './editor/vue-flow-editor'
import VueFlowEditorPreview from './editor/vue-flow-editor-form'
import VueFLowEditMenu from './editor/vue-flow-edit-menu.vue'
import VueFLowEditMenuGroup from './editor/vue-flow-edit-menu-group.vue'
import {formatPos} from "@/utils/utils";
export default {
VueFlowEditor,
VueFlowEditorPreview,
VueFLowEditMenu,
VueFLowEditMenuGroup,
formatPos,
......