276 lines
5.8 KiB
Vue
276 lines
5.8 KiB
Vue
![]() |
<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>
|