Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
vue-flow-editor
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
hookehuyr
2024-02-20 11:05:58 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
1b4086e23c30b934afadc7aca6204690b5c11d30
1b4086e2
1 parent
405d3a83
新增预览模式展示效果
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
910 additions
and
18 deletions
doc/App.vue
src/behavior/drag-node.ts
src/behavior/hover-anchor-active.ts
src/editor/index.ts
src/editor/vue-flow-editor-canvas-form.tsx
src/editor/vue-flow-editor-canvas.tsx
src/editor/vue-flow-editor-form.tsx
src/editor/vue-flow-editor.scss
src/index.ts
doc/App.vue
View file @
1b4086e
...
...
@@ -43,20 +43,20 @@
</vue-flow-edit-menu>
</vue-flow-edit-menu-group> -->
<vue-flow-edit-menu
v-for="(value, key) in state.controlList"
:key="key"
:model="{ control: key, text: value.text, desc: value.desc }"
>
<template v-slot:content>
<div v-if="key === 'flow'" style="border-left: 1px solid #e6e6e6; width: 2px; height: 35px; position: absolute; top: 10px;"></div>
<el-tooltip :content="value.desc">
<div :class="['vue-flow-editor-toolbar-item']">
<img style="width: 15px; height: 15px; margin-bottom: 0; margin-top: 3px;" :src="value.img" />
<span style="font-size: 12px; transform: scale(0.8); margin-top: 2px;">{{ value.text }}</span>
</div>
</el-tooltip>
</template>
</vue-flow-edit-menu>
v-for="(value, key) in state.controlList"
:key="key"
:model="{ control: key, text: value.text, desc: value.desc }"
>
<template v-slot:content>
<div v-if="key === 'flow'" style="border-left: 1px solid #e6e6e6; width: 2px; height: 35px; position: absolute; top: 10px;"></div>
<el-tooltip :content="value.desc">
<div :class="['vue-flow-editor-toolbar-item']">
<img style="width: 15px; height: 15px; margin-bottom: 0; margin-top: 3px;" :src="value.img" />
<span style="font-size: 12px; transform: scale(0.8); margin-top: 2px;">{{ value.text }}</span>
</div>
</el-tooltip>
</template>
</vue-flow-edit-menu>
<!-- <vue-flow-edit-menu-group
v-for="(group, groupIndex) in state.menuData"
:label="group.label"
...
...
@@ -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,
...
...
src/behavior/drag-node.ts
View file @
1b4086e
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
;
}
...
...
src/behavior/hover-anchor-active.ts
View file @
1b4086e
/*
* @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
;
item
.
showAnchor
(
this
.
graph
)
// 打开预览模式显示锚点
if
(
!
$
(
'.preview-container'
).
length
)
{
item
.
showAnchor
(
this
.
graph
)
}
},
onLeaveNode
(
e
)
{
try
{
...
...
src/editor/index.ts
View file @
1b4086e
/*
* @Date: 2023-10-27 09:29:59
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 202
3-12-18 15:00:56
* @LastEditTime: 202
4-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
,
...
...
src/editor/vue-flow-editor-canvas-form.tsx
0 → 100644
View file @
1b4086e
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>
)
},
}
src/editor/vue-flow-editor-canvas.tsx
View file @
1b4086e
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() {
...
...
src/editor/vue-flow-editor-form.tsx
0 → 100644
View file @
1b4086e
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>
)
},
}
src/editor/vue-flow-editor.scss
View file @
1b4086e
...
...
@@ -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
;
}
}
}
}
...
...
src/index.ts
View file @
1b4086e
/*
* @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
,
...
...
@@ -15,4 +24,4 @@ export default {
Vue
.
component
(
Component
.
name
,
Component
)
})
}
}
\ No newline at end of file
}
...
...
Please
register
or
login
to post a comment