|
|
|
|
@@ -10,7 +10,7 @@ import WorkflowSidebar from '@/layouts/components/WorkflowSidebar.vue'
|
|
|
|
|
import DropzoneBackground from '@/layouts/components/DropzoneBackground.vue'
|
|
|
|
|
import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
|
|
|
|
|
|
|
|
|
|
const { onConnect, addEdges, nodes, edges } = useVueFlow()
|
|
|
|
|
const { onConnect, addEdges, nodes, edges, addNodes, screenToFlowCoordinate } = useVueFlow()
|
|
|
|
|
|
|
|
|
|
const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
|
|
|
|
|
|
|
|
|
|
@@ -98,6 +98,43 @@ const $toast = useToast()
|
|
|
|
|
// 导入代码对话框
|
|
|
|
|
const importCodeDialog = ref(false)
|
|
|
|
|
|
|
|
|
|
// 为移动端生成节点ID
|
|
|
|
|
function getId() {
|
|
|
|
|
return 'act_' + Math.random().toString(36).substr(2, 9)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理移动端组件点击事件
|
|
|
|
|
function handleComponentClick(action: any) {
|
|
|
|
|
// 计算当前视图中心点
|
|
|
|
|
const centerX = window.innerWidth / 2
|
|
|
|
|
const centerY = window.innerHeight / 3
|
|
|
|
|
|
|
|
|
|
// 转换为画布坐标
|
|
|
|
|
const position = screenToFlowCoordinate({
|
|
|
|
|
x: centerX,
|
|
|
|
|
y: centerY,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 生成一个新节点ID
|
|
|
|
|
const nodeId = getId()
|
|
|
|
|
|
|
|
|
|
// 创建新节点
|
|
|
|
|
const newNode = {
|
|
|
|
|
id: nodeId,
|
|
|
|
|
type: action.type,
|
|
|
|
|
name: action.name,
|
|
|
|
|
description: action.desc || '',
|
|
|
|
|
position,
|
|
|
|
|
data: {},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加节点到画布
|
|
|
|
|
addNodes(newNode)
|
|
|
|
|
|
|
|
|
|
// 显示提示
|
|
|
|
|
$toast.success('已添加组件到画布')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用API 编辑任务
|
|
|
|
|
async function updateWorkflow() {
|
|
|
|
|
// 更新节点和流程
|
|
|
|
|
@@ -157,32 +194,34 @@ const isMacOS = computed(() => {
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<VDialog scrollable fullscreen :scrim="false" transition="dialog-bottom-transition">
|
|
|
|
|
<VCard>
|
|
|
|
|
<VCard class="workflow-dialog">
|
|
|
|
|
<!-- Toolbar -->
|
|
|
|
|
<div>
|
|
|
|
|
<VToolbar color="primary">
|
|
|
|
|
<VToolbarItems>
|
|
|
|
|
<VBtn icon @click="emit('close')" class="ms-3">
|
|
|
|
|
<VIcon size="large" color="white" icon="mdi-close" />
|
|
|
|
|
</VBtn>
|
|
|
|
|
</VToolbarItems>
|
|
|
|
|
<VToolbarTitle> 编辑流程 - {{ workflow?.name }} </VToolbarTitle>
|
|
|
|
|
<VToolbarItems>
|
|
|
|
|
<VBtn icon @click="importCodeDialog = true">
|
|
|
|
|
<VIcon size="large" color="white" icon="mdi-import" />
|
|
|
|
|
</VBtn>
|
|
|
|
|
<VBtn icon @click="shareWorkflow">
|
|
|
|
|
<VIcon size="large" color="white" icon="mdi-share" />
|
|
|
|
|
</VBtn>
|
|
|
|
|
<VBtn icon @click="updateWorkflow" class="mx-5">
|
|
|
|
|
<VIcon size="large" color="white" icon="mdi-content-save" />
|
|
|
|
|
</VBtn>
|
|
|
|
|
</VToolbarItems>
|
|
|
|
|
</VToolbar>
|
|
|
|
|
</div>
|
|
|
|
|
<VDivider />
|
|
|
|
|
<VCardText class="px-0 py-0">
|
|
|
|
|
<div class="dnd-flow" @drop="onDrop">
|
|
|
|
|
<VToolbar color="primary">
|
|
|
|
|
<VToolbarItems>
|
|
|
|
|
<VBtn icon @click="emit('close')" class="ms-3">
|
|
|
|
|
<VIcon size="large" color="white" icon="mdi-close" />
|
|
|
|
|
</VBtn>
|
|
|
|
|
</VToolbarItems>
|
|
|
|
|
<VToolbarTitle class="text-truncate"> 编辑流程 - {{ workflow?.name }} </VToolbarTitle>
|
|
|
|
|
<VSpacer></VSpacer>
|
|
|
|
|
<VToolbarItems>
|
|
|
|
|
<VBtn icon variant="text" @click="importCodeDialog = true" class="ms-2">
|
|
|
|
|
<VIcon size="24" color="white" icon="mdi-import" />
|
|
|
|
|
<VTooltip activator="parent" location="bottom">导入流程代码</VTooltip>
|
|
|
|
|
</VBtn>
|
|
|
|
|
<VBtn icon variant="text" @click="shareWorkflow" class="ms-2">
|
|
|
|
|
<VIcon size="24" color="white" icon="mdi-share" />
|
|
|
|
|
<VTooltip activator="parent" location="bottom">分享流程代码</VTooltip>
|
|
|
|
|
</VBtn>
|
|
|
|
|
<VBtn icon variant="text" @click="updateWorkflow" class="ms-2 me-3">
|
|
|
|
|
<VIcon size="24" color="white" icon="mdi-content-save" />
|
|
|
|
|
<VTooltip activator="parent" location="bottom">保存流程</VTooltip>
|
|
|
|
|
</VBtn>
|
|
|
|
|
</VToolbarItems>
|
|
|
|
|
</VToolbar>
|
|
|
|
|
|
|
|
|
|
<VCardText class="workflow-content pa-0">
|
|
|
|
|
<div class="workflow-canvas" @drop="onDrop">
|
|
|
|
|
<VueFlow
|
|
|
|
|
:nodes="nodes"
|
|
|
|
|
:edges="edges"
|
|
|
|
|
@@ -204,10 +243,11 @@ const isMacOS = computed(() => {
|
|
|
|
|
>
|
|
|
|
|
</DropzoneBackground>
|
|
|
|
|
</VueFlow>
|
|
|
|
|
<WorkflowSidebar />
|
|
|
|
|
<WorkflowSidebar @component-click="handleComponentClick" />
|
|
|
|
|
</div>
|
|
|
|
|
</VCardText>
|
|
|
|
|
</VCard>
|
|
|
|
|
|
|
|
|
|
<ImportCodeDialog
|
|
|
|
|
v-if="importCodeDialog"
|
|
|
|
|
v-model="importCodeDialog"
|
|
|
|
|
@@ -218,88 +258,43 @@ const isMacOS = computed(() => {
|
|
|
|
|
/>
|
|
|
|
|
</VDialog>
|
|
|
|
|
</template>
|
|
|
|
|
<style>
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
@import '@vue-flow/core/dist/style.css';
|
|
|
|
|
@import '@vue-flow/core/dist/theme-default.css';
|
|
|
|
|
@import '@vue-flow/controls/dist/style.css';
|
|
|
|
|
@import '@vue-flow/minimap/dist/style.css';
|
|
|
|
|
@import '@vue-flow/node-resizer/dist/style.css';
|
|
|
|
|
|
|
|
|
|
.vue-flow__minimap {
|
|
|
|
|
transform: scale(75%);
|
|
|
|
|
transform-origin: bottom right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dnd-flow {
|
|
|
|
|
.workflow-dialog {
|
|
|
|
|
display: flex;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
block-size: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dnd-flow aside {
|
|
|
|
|
background: #10b981bf;
|
|
|
|
|
border-inline-end: 1px solid #eee;
|
|
|
|
|
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 30%);
|
|
|
|
|
box-shadow: 0 5px 10px #0000004d;
|
|
|
|
|
color: #fff;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
padding-block: 15px;
|
|
|
|
|
padding-inline: 10px;
|
|
|
|
|
.workflow-content {
|
|
|
|
|
position: relative;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dnd-flow aside .nodes > * {
|
|
|
|
|
box-shadow: 5px 5px 10px 2px rgba(0, 0, 0, 25%);
|
|
|
|
|
box-shadow: 5px 5px 10px 2px #00000040;
|
|
|
|
|
cursor: grab;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-block-end: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dnd-flow aside .description {
|
|
|
|
|
margin-block-end: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dnd-flow .vue-flow-wrapper {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
block-size: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media screen and (width >= 640px) {
|
|
|
|
|
.dnd-flow {
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dnd-flow aside {
|
|
|
|
|
max-inline-size: 25%;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media screen and (width <= 639px) {
|
|
|
|
|
.dnd-flow aside .nodes {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
gap: 5px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dropzone-background {
|
|
|
|
|
.workflow-canvas {
|
|
|
|
|
position: relative;
|
|
|
|
|
block-size: 100%;
|
|
|
|
|
inline-size: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dropzone-background .overlay {
|
|
|
|
|
position: absolute;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
block-size: 100%;
|
|
|
|
|
inline-size: 100%;
|
|
|
|
|
inset-block-start: 0;
|
|
|
|
|
inset-inline-start: 0;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
.vue-flow__minimap {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background-color: rgba(var(--v-theme-surface), 0.8);
|
|
|
|
|
box-shadow: 0 4px 15px rgba(var(--v-shadow-key-umbra-color), 0.1);
|
|
|
|
|
inset-block-end: 20px;
|
|
|
|
|
inset-inline-end: 20px;
|
|
|
|
|
transform: scale(75%);
|
|
|
|
|
transform-origin: bottom right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.vue-flow__handle {
|
|
|
|
|
@@ -320,4 +315,39 @@ const isMacOS = computed(() => {
|
|
|
|
|
.vue-flow__handle-right {
|
|
|
|
|
background-color: rgb(var(--v-theme-error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 自定义节点样式
|
|
|
|
|
.vue-flow__node {
|
|
|
|
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
box-shadow: 0 8px 16px rgba(var(--v-shadow-key-umbra-color), 0.15) !important;
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.selected {
|
|
|
|
|
box-shadow: 0 0 0 1px rgb(var(--v-theme-primary)) !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 自定义动作连线样式
|
|
|
|
|
.vue-flow__edge.animation {
|
|
|
|
|
.vue-flow__edge-path {
|
|
|
|
|
stroke: rgb(var(--v-theme-primary));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.selected {
|
|
|
|
|
.vue-flow__edge-path {
|
|
|
|
|
stroke: rgb(var(--v-theme-primary));
|
|
|
|
|
stroke-width: 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media screen and (width <= 600px) {
|
|
|
|
|
.vue-flow__minimap {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|