feat: 增强工作流侧边栏,支持移动端显示和组件点击事件处理

This commit is contained in:
jxxghp
2025-04-10 21:12:48 +08:00
parent f58d4fcb7e
commit f85ac34753
6 changed files with 512 additions and 107 deletions

View File

@@ -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>

View File

@@ -44,7 +44,7 @@ onMounted(() => {
<VCardItem>
<template v-slot:prepend>
<VAvatar>
<VIcon icon="mdi-download-box-outline" size="x-large"></VIcon>
<VIcon icon="mdi-download" size="x-large"></VIcon>
</VAvatar>
</template>
<VCardTitle>添加下载</VCardTitle>

View File

@@ -19,7 +19,7 @@ defineProps({
<VCardItem>
<template v-slot:prepend>
<VAvatar>
<VIcon icon="mdi-star-check" size="x-large"></VIcon>
<VIcon icon="mdi-star-plus" size="x-large"></VIcon>
</VAvatar>
</template>
<VCardTitle>添加订阅</VCardTitle>

View File

@@ -110,7 +110,7 @@ onMounted(() => {
<VCardItem>
<template v-slot:prepend>
<VAvatar>
<VIcon icon="mdi-multimedia" size="x-large"></VIcon>
<VIcon icon="mdi-movie-search" size="x-large"></VIcon>
</VAvatar>
</template>
<VCardTitle>获取媒体数据</VCardTitle>

View File

@@ -20,7 +20,7 @@ defineProps({
<VCardItem>
<template v-slot:prepend>
<VAvatar>
<VIcon icon="mdi-file-move" size="x-large"></VIcon>
<VIcon icon="mdi-folder-search" size="x-large"></VIcon>
</VAvatar>
</template>
<VCardTitle>扫描目录</VCardTitle>