automated_admin/src/modules/flow/components/index.vue

276 lines
5.8 KiB
Vue
Raw Normal View History

2025-01-09 17:58:24 +08:00
<template>
<div class="cl-flow" id="cl-flow">
<vue-flow
:default-viewport="{ zoom: 1.0 }"
:min-zoom="0.25"
:max-zoom="2"
@connect="onConnect"
@node-mouse-enter="onNodeMouseEnter"
@node-mouse-leave="onNodeMouseLeave"
@node-click="onNodeClick"
@node-drag-stop="onNodeDragStop"
@node-context-menu="refs.contextMenu?.onNode"
@edge-mouse-enter="onEdgeMouseEnter"
@edge-mouse-leave="onEdgeMouseLeave"
@pane-context-menu="refs.contextMenu?.onPane"
@pane-click="onPaneClick"
@pane-scroll="onPaneScroll"
@pane-ready="onPaneReady"
>
<!-- 自定义顶部栏 -->
<tools-head />
<!-- 自定义选择框 -->
<tools-selection />
<!-- 自定义面板 -->
<tools-panel />
<!-- 自定义节点添加面板 -->
<tools-node-add />
<!-- 自定义右键菜单 -->
<tools-context-menu :ref="setRefs('contextMenu')" />
<!-- 自定义控制器 -->
<tools-controls />
<!-- 自定义连接线按钮 -->
<template
#edge-button="{
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
markerEnd,
style,
data
}"
>
<tools-edge-button
:id="id"
:source-x="sourceX"
:source-y="sourceY"
:target-x="targetX"
:target-y="targetY"
:source-position="sourcePosition"
:target-position="targetPosition"
:marker-end="markerEnd"
:style="style"
:data="data"
/>
</template>
<!-- 自定义节点 -->
<template #[item.name!]="{ id }" v-for="item in flow.CustomNodes" :key="item.name">
<tools-card :node-id="id" />
</template>
<!-- 背景 -->
<background pattern-color="#aaa" :gap="16" />
<!-- 小图 -->
<mini-map position="bottom-right" :height="100" :width="150" />
</vue-flow>
</div>
</template>
<script setup lang="ts">
import {
VueFlow,
Connection,
NodeDragEvent,
EdgeMouseEvent,
type NodeMouseEvent,
type VueFlowStore
} from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { MiniMap } from "@vue-flow/minimap";
import { useFlow } from "../hooks";
import { useCool } from "/@/cool";
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";
import ToolsPanel from "./tools/panel/index.vue";
import ToolsCard from "./tools/card.vue";
import ToolsEdgeButton from "./tools/edge-button.vue";
import ToolsControls from "./tools/controls.vue";
import ToolsHead from "./tools/head.vue";
import ToolsContextMenu from "./tools/context-menu.vue";
import ToolsSelection from "./tools/selection.vue";
import ToolsNodeAdd from "./tools/node-add.vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { onBeforeRouteLeave } from "vue-router";
import { onMounted, onUnmounted } from "vue";
import type { FlowNode } from "../types";
import { nextTick } from "vue";
const props = defineProps({
flowId: Number
});
const { mitt, refs, setRefs } = useCool();
const flow = useFlow();
// 加载完
function onPaneReady(store: VueFlowStore) {
flow.init();
flow.get(props.flowId!);
}
// 滚轮滚动
function onPaneScroll(e: WheelEvent | undefined) {
refs.contextMenu?.close();
}
// 线连接
function onConnect(connection: Connection) {
flow.addEdge(connection);
}
// 节点点击
async function onNodeClick(e: NodeMouseEvent) {
// 如果节点相同则不执行事件
if (flow.node?.id == e.node.id) return false;
const node = flow.findNode(e.node.id);
flow.setNode(node);
// 视图定位
// flow.setViewportByNode(flow.node!);
refs.contextMenu?.close();
}
// 节点鼠标移入
function onNodeMouseEnter(e: NodeMouseEvent) {
flow.activeEdge(e.node.id, true);
}
// 节点鼠标移入
function onNodeMouseLeave(e: NodeMouseEvent) {
flow.activeEdge(e.node.id, false);
}
// 节点拖拽结束
function onNodeDragStop(e: NodeDragEvent) {
flow.updateNode(e.node.id, {
position: e.node.position
});
}
// 鼠标移入线
function onEdgeMouseEnter(e: EdgeMouseEvent) {
e.edge.data.show = true;
}
// 鼠标移出线
function onEdgeMouseLeave(e: EdgeMouseEvent) {
e.edge.data.show = false;
}
// 空白处点击
function onPaneClick() {
refs.contextMenu?.close();
flow.enableDrag();
flow.clearNode();
}
// 打开表单
function openForm(node: FlowNode) {
closeForm();
if (node) {
flow.updateChildrenPosition("open", node);
setTimeout(() => {
mitt.emit("flow.openForm", node);
}, 100);
}
}
// 关闭表单
function closeForm() {
flow.updateChildrenPosition("close", flow.node!);
mitt.emit("flow.closeForm", flow.node);
}
// 保存
async function save() {
await flow.save();
ElMessage.success("数据保存成功");
}
onMounted(() => {
mitt.on("flow.setNode", openForm);
mitt.on("flow.clearNode", closeForm);
window.addEventListener("beforeunload", save);
});
onUnmounted(() => {
mitt.off("flow.setNode", openForm);
mitt.off("flow.clearNode", closeForm);
window.removeEventListener("beforeunload", save);
});
let lock = false;
onBeforeRouteLeave((to, from, next) => {
if (lock) {
return next();
}
lock = true;
ElMessageBox.confirm("退出前是否保存当前数据?", "提示", {
type: "warning",
confirmButtonText: "保存",
cancelButtonText: "不保存",
distinguishCancelAndClose: true,
beforeClose(action, instance, done) {
done();
if (action == "confirm") {
save();
next();
} else if (action == "cancel") {
next();
} else {
lock = false;
next(false);
}
}
}).catch(() => null);
});
</script>
<style lang="scss">
.cl-flow {
height: 100%;
.vue-flow {
height: 100%;
}
.vue-flow__minimap {
margin: 0;
bottom: 50px;
right: 10px;
height: 100px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0px 0 6px 1px rgba(16, 24, 40, 0.08);
}
.vue-flow__node:has(.is-moving) {
transition: transform 0.1s;
}
}
</style>