fix flowchart
This commit is contained in:
@@ -98,4 +98,4 @@ const config: ReturnType<typeof defineConfig> = defineConfig(({ envMode }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
@@ -16,14 +16,6 @@ body {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid rgba(148, 163, 184, 0.24);
|
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background:
|
|
||||||
linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px),
|
|
||||||
linear-gradient(135deg, #ffffff 0%, #f8fafc 52%, #eef6ff 100%);
|
|
||||||
background-size:
|
|
||||||
24px 24px,
|
|
||||||
24px 24px,
|
|
||||||
auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.oj-mermaid-surface > svg {
|
.oj-mermaid-surface > svg {
|
||||||
|
|||||||
@@ -69,8 +69,6 @@ const page = ref(1)
|
|||||||
// ==================== WebSocket 相关函数 ====================
|
// ==================== WebSocket 相关函数 ====================
|
||||||
// 处理 WebSocket 消息
|
// 处理 WebSocket 消息
|
||||||
const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => {
|
const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => {
|
||||||
console.log("收到流程图评分更新:", data)
|
|
||||||
|
|
||||||
if (data.type === "flowchart_evaluation_completed") {
|
if (data.type === "flowchart_evaluation_completed") {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
latestRating.value = {
|
latestRating.value = {
|
||||||
@@ -79,11 +77,8 @@ const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => {
|
|||||||
}
|
}
|
||||||
message.success(`流程图评分完成!得分: ${data.score}分 (${data.grade}级)`)
|
message.success(`流程图评分完成!得分: ${data.score}分 (${data.grade}级)`)
|
||||||
} else if (data.type === "flowchart_evaluation_failed") {
|
} else if (data.type === "flowchart_evaluation_failed") {
|
||||||
console.log("处理评分失败消息")
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
message.error(`流程图评分失败: ${data.error}`)
|
message.error(`流程图评分失败: ${data.error}`)
|
||||||
} else {
|
|
||||||
console.log("未知的消息类型:", data.type)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +89,6 @@ const { connect, disconnect, subscribe } = useFlowchartWebSocket(
|
|||||||
|
|
||||||
// 订阅提交更新
|
// 订阅提交更新
|
||||||
function subscribeToSubmission(submissionId: string) {
|
function subscribeToSubmission(submissionId: string) {
|
||||||
console.log("开始订阅WebSocket更新")
|
|
||||||
subscribe(submissionId)
|
subscribe(submissionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,16 +281,14 @@ onUnmounted(() => {
|
|||||||
<n-grid :cols="5" :x-gap="16">
|
<n-grid :cols="5" :x-gap="16">
|
||||||
<!-- 左侧:流程图预览区域 -->
|
<!-- 左侧:流程图预览区域 -->
|
||||||
<n-gi :span="3">
|
<n-gi :span="3">
|
||||||
<n-card title="流程图预览">
|
<div class="flowchart">
|
||||||
<div class="flowchart">
|
<n-spin :show="rendering">
|
||||||
<n-spin :show="rendering">
|
<n-alert v-if="renderError" type="error" title="流程图渲染失败">
|
||||||
<n-alert v-if="renderError" type="error" title="流程图渲染失败">
|
{{ renderError }}
|
||||||
{{ renderError }}
|
</n-alert>
|
||||||
</n-alert>
|
<div class="flowchart" v-else ref="mermaidContainer"></div>
|
||||||
<div class="flowchart" v-else ref="mermaidContainer"></div>
|
</n-spin>
|
||||||
</n-spin>
|
</div>
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
<!-- 加载到编辑器按钮 -->
|
<!-- 加载到编辑器按钮 -->
|
||||||
<n-flex style="margin-top: 16px" justify="center">
|
<n-flex style="margin-top: 16px" justify="center">
|
||||||
<n-button @click="loadToEditor" type="primary">
|
<n-button @click="loadToEditor" type="primary">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-button v-if="showLink" type="info" text @click="goto">
|
<n-button v-if="showLink" type="info" text @click="handleClick">
|
||||||
{{ flowchart.id.slice(0, 12) }}
|
{{ flowchart.id.slice(0, 12) }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-text v-else class="flowchart-id" @click="handleClick">
|
<n-text v-else class="flowchart-id" @click="handleClick">
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
</n-text>
|
</n-text>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { FlowchartSubmissionListItem } from "utils/types"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { FlowchartSubmissionListItem } from "utils/types"
|
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
@@ -27,10 +27,6 @@ const showLink = computed(() => {
|
|||||||
return props.flowchart.username === userStore.user?.username
|
return props.flowchart.username === userStore.user?.username
|
||||||
})
|
})
|
||||||
|
|
||||||
function goto() {
|
|
||||||
emit("showDetail", props.flowchart.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
emit("showDetail", props.flowchart.id)
|
emit("showDetail", props.flowchart.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
:class="{ 'is-hovered': isHovered, 'is-editing': isEditing }"
|
:class="{ 'is-hovered': isHovered, 'is-editing': isEditing }"
|
||||||
:data-node-type="nodeType"
|
:data-node-type="nodeType"
|
||||||
:draggable="!isEditing"
|
:draggable="!isEditing"
|
||||||
@mouseenter="isHovered = true"
|
@mouseenter="handleMouseEnter"
|
||||||
@mouseleave="handleMouseLeave"
|
@mouseleave="handleMouseLeave"
|
||||||
@dblclick="handleDoubleClick"
|
@dblclick="handleDoubleClick"
|
||||||
@dragstart="handleDragStart"
|
@dragstart="handleDragStart"
|
||||||
@@ -53,11 +53,17 @@ import { getNodeTypeConfig } from "./useNodeStyles"
|
|||||||
import NodeHandles from "./NodeHandles.vue"
|
import NodeHandles from "./NodeHandles.vue"
|
||||||
import NodeActions from "./NodeActions.vue"
|
import NodeActions from "./NodeActions.vue"
|
||||||
|
|
||||||
// 类型定义
|
interface NodeData {
|
||||||
|
label: string
|
||||||
|
color: string
|
||||||
|
originalType: string
|
||||||
|
customLabel?: string
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string
|
id: string
|
||||||
type: string
|
type: string
|
||||||
data: any
|
data: NodeData
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -147,6 +153,7 @@ const handleCancelEdit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
|
isHovered.value = true
|
||||||
if (hideTimeout) {
|
if (hideTimeout) {
|
||||||
clearTimeout(hideTimeout)
|
clearTimeout(hideTimeout)
|
||||||
hideTimeout = null
|
hideTimeout = null
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { getNodeTypeConfig } from "./useNodeStyles"
|
import { getNodeTypeConfig } from "./useNodeStyles"
|
||||||
|
import { currentDragNodeType } from "./useDnD"
|
||||||
|
|
||||||
// 拖拽开始处理
|
// 拖拽开始处理
|
||||||
const onDragStart = (event: DragEvent, type: string) => {
|
const onDragStart = (event: DragEvent, type: string) => {
|
||||||
@@ -8,6 +9,17 @@ const onDragStart = (event: DragEvent, type: string) => {
|
|||||||
|
|
||||||
event.dataTransfer.setData("application/vueflow", type)
|
event.dataTransfer.setData("application/vueflow", type)
|
||||||
event.dataTransfer.effectAllowed = "move"
|
event.dataTransfer.effectAllowed = "move"
|
||||||
|
currentDragNodeType.value = type
|
||||||
|
|
||||||
|
// 隐藏浏览器默认拖影,改用 canvas 跟随预览
|
||||||
|
const emptyImg = new Image(1, 1)
|
||||||
|
emptyImg.src =
|
||||||
|
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||||
|
event.dataTransfer.setDragImage(emptyImg, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
currentDragNodeType.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
@@ -42,8 +54,7 @@ const nodeTypes = computed(() =>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 获取保存状态标题
|
const saveStatusTitle = computed(() => {
|
||||||
const getSaveStatusTitle = () => {
|
|
||||||
if (props.isSaving) {
|
if (props.isSaving) {
|
||||||
return "正在保存..."
|
return "正在保存..."
|
||||||
} else if (props.hasUnsavedChanges) {
|
} else if (props.hasUnsavedChanges) {
|
||||||
@@ -53,7 +64,7 @@ const getSaveStatusTitle = () => {
|
|||||||
} else {
|
} else {
|
||||||
return "已保存"
|
return "已保存"
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
@@ -68,7 +79,7 @@ const getSaveStatusTitle = () => {
|
|||||||
unsaved: props.hasUnsavedChanges && !props.isSaving,
|
unsaved: props.hasUnsavedChanges && !props.isSaving,
|
||||||
saved: !props.hasUnsavedChanges && !props.isSaving,
|
saved: !props.hasUnsavedChanges && !props.isSaving,
|
||||||
}"
|
}"
|
||||||
:title="getSaveStatusTitle()"
|
:title="saveStatusTitle"
|
||||||
>
|
>
|
||||||
<span v-if="props.isSaving" class="spinner">⏳</span>
|
<span v-if="props.isSaving" class="spinner">⏳</span>
|
||||||
<span v-else-if="props.hasUnsavedChanges">●</span>
|
<span v-else-if="props.hasUnsavedChanges">●</span>
|
||||||
@@ -86,6 +97,7 @@ const getSaveStatusTitle = () => {
|
|||||||
class="node-item"
|
class="node-item"
|
||||||
:draggable="true"
|
:draggable="true"
|
||||||
@dragstart="onDragStart($event, nodeType.type)"
|
@dragstart="onDragStart($event, nodeType.type)"
|
||||||
|
@dragend="onDragEnd"
|
||||||
:style="{ borderColor: nodeType.color }"
|
:style="{ borderColor: nodeType.color }"
|
||||||
:title="`${nodeType.label} - ${nodeType.description}`"
|
:title="`${nodeType.label} - ${nodeType.description}`"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
import { Controls } from "@vue-flow/controls"
|
import { Controls } from "@vue-flow/controls"
|
||||||
import { Background } from "@vue-flow/background"
|
import { Background } from "@vue-flow/background"
|
||||||
|
|
||||||
import { useDnD } from "./useDnD"
|
import { useDnD, currentDragNodeType } from "./useDnD"
|
||||||
|
import { getNodeTypeConfig } from "./useNodeStyles"
|
||||||
import { useHistory } from "./useHistory"
|
import { useHistory } from "./useHistory"
|
||||||
import { useFlowOperations } from "./useFlowOperations"
|
import { useFlowOperations } from "./useFlowOperations"
|
||||||
import { useCache } from "./useCache"
|
import { useCache } from "./useCache"
|
||||||
@@ -42,17 +43,35 @@ const { canUndo, canRedo, saveState, undo, redo } = useHistory()
|
|||||||
const problemStore = useProblemStore()
|
const problemStore = useProblemStore()
|
||||||
const { problem } = storeToRefs(problemStore)
|
const { problem } = storeToRefs(problemStore)
|
||||||
// 缓存管理
|
// 缓存管理
|
||||||
const { isSaving, lastSaved, hasUnsavedChanges, loadFromCache, clearCache } =
|
const {
|
||||||
useCache(
|
isSaving,
|
||||||
nodes,
|
lastSaved,
|
||||||
edges,
|
hasUnsavedChanges,
|
||||||
problem.value?._id
|
saveToCache,
|
||||||
? `flowchart-editor-data-problem-${problem.value!._id}`
|
loadFromCache,
|
||||||
: "flowchart-editor-data",
|
clearCache,
|
||||||
)
|
} = useCache(
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
problem.value?._id
|
||||||
|
? `flowchart-editor-data-problem-${problem.value!._id}`
|
||||||
|
: "flowchart-editor-data",
|
||||||
|
)
|
||||||
|
|
||||||
// 拖拽处理
|
// 拖拽处理
|
||||||
const { onDragOver, onDragLeave, onDrop } = useDnD()
|
const { onDragOver, onDragLeave, onDrop, isDragOver, screenDragPos } = useDnD()
|
||||||
|
|
||||||
|
const dragPreviewStyle = computed(() => {
|
||||||
|
if (!screenDragPos.value || !currentDragNodeType.value) return null
|
||||||
|
const config = getNodeTypeConfig(currentDragNodeType.value)
|
||||||
|
const type = currentDragNodeType.value
|
||||||
|
return {
|
||||||
|
left: `${screenDragPos.value.x}px`,
|
||||||
|
top: `${screenDragPos.value.y}px`,
|
||||||
|
background: config.color,
|
||||||
|
borderRadius: type === "start" || type === "end" ? "20px" : "8px",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 流程操作
|
// 流程操作
|
||||||
const {
|
const {
|
||||||
@@ -93,16 +112,18 @@ const handleDrop = (event: DragEvent) => {
|
|||||||
const handleUndo = () => {
|
const handleUndo = () => {
|
||||||
const state = undo()
|
const state = undo()
|
||||||
if (state) {
|
if (state) {
|
||||||
nodes.value = [...state.nodes]
|
nodes.value = state.nodes
|
||||||
edges.value = [...state.edges]
|
edges.value = state.edges
|
||||||
|
saveToCache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRedo = () => {
|
const handleRedo = () => {
|
||||||
const state = redo()
|
const state = redo()
|
||||||
if (state) {
|
if (state) {
|
||||||
nodes.value = [...state.nodes]
|
nodes.value = state.nodes
|
||||||
edges.value = [...state.edges]
|
edges.value = state.edges
|
||||||
|
saveToCache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +203,19 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container" :style="{ height }">
|
<div class="container" :style="{ height }">
|
||||||
|
<!-- 拖拽时跟随鼠标的节点预览 -->
|
||||||
|
<Transition name="drag-preview">
|
||||||
|
<div
|
||||||
|
v-if="isDragOver && dragPreviewStyle && currentDragNodeType"
|
||||||
|
class="drag-node-preview"
|
||||||
|
:style="dragPreviewStyle"
|
||||||
|
>
|
||||||
|
<span class="preview-icon">{{
|
||||||
|
getNodeTypeConfig(currentDragNodeType).icon
|
||||||
|
}}</span>
|
||||||
|
<span>{{ getNodeTypeConfig(currentDragNodeType).label }}</span>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
<VueFlow
|
<VueFlow
|
||||||
v-model:nodes="nodes"
|
v-model:nodes="nodes"
|
||||||
v-model:edges="edges"
|
v-model:edges="edges"
|
||||||
@@ -191,7 +225,7 @@ defineExpose({
|
|||||||
@connect="handleConnect"
|
@connect="handleConnect"
|
||||||
@edge-click="handleEdgeClick"
|
@edge-click="handleEdgeClick"
|
||||||
:default-edge-options="{
|
:default-edge-options="{
|
||||||
type: 'step',
|
type: 'default',
|
||||||
style: {
|
style: {
|
||||||
stroke: '#6366f1',
|
stroke: '#6366f1',
|
||||||
strokeWidth: 2.5,
|
strokeWidth: 2.5,
|
||||||
@@ -269,4 +303,36 @@ defineExpose({
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drag-node-preview {
|
||||||
|
position: fixed;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 8px 18px;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
opacity: 0.55;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 2px dashed rgba(255, 255, 255, 0.6);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-preview-enter-active,
|
||||||
|
.drag-preview-leave-active {
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
}
|
||||||
|
.drag-preview-enter-from,
|
||||||
|
.drag-preview-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,28 +7,36 @@ import {
|
|||||||
} from "./useNodeStyles"
|
} from "./useNodeStyles"
|
||||||
import { getRandomId } from "utils/functions"
|
import { getRandomId } from "utils/functions"
|
||||||
|
|
||||||
|
// 模块级共享:当前拖拽的节点类型(Toolbar 写入,canvas 读取)
|
||||||
|
export const currentDragNodeType = ref<string | null>(null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简化的拖拽处理
|
* 简化的拖拽处理
|
||||||
*/
|
*/
|
||||||
export function useDnD() {
|
export function useDnD() {
|
||||||
const { addNodes, screenToFlowCoordinate } = useVueFlow()
|
const { addNodes, screenToFlowCoordinate } = useVueFlow()
|
||||||
const isDragOver = ref(false)
|
const isDragOver = ref(false)
|
||||||
|
const screenDragPos = ref<{ x: number; y: number } | null>(null)
|
||||||
|
|
||||||
// 拖拽悬停处理
|
// 拖拽悬停处理
|
||||||
const onDragOver = (event: DragEvent) => {
|
const onDragOver = (event: DragEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
isDragOver.value = true
|
isDragOver.value = true
|
||||||
|
screenDragPos.value = { x: event.clientX, y: event.clientY }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拖拽离开处理
|
// 拖拽离开处理
|
||||||
const onDragLeave = () => {
|
const onDragLeave = () => {
|
||||||
isDragOver.value = false
|
isDragOver.value = false
|
||||||
|
screenDragPos.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拖拽放置处理
|
// 拖拽放置处理
|
||||||
const onDrop = (event: DragEvent) => {
|
const onDrop = (event: DragEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
isDragOver.value = false
|
isDragOver.value = false
|
||||||
|
screenDragPos.value = null
|
||||||
|
currentDragNodeType.value = null
|
||||||
|
|
||||||
const type = event.dataTransfer?.getData("application/vueflow")
|
const type = event.dataTransfer?.getData("application/vueflow")
|
||||||
if (!type) return
|
if (!type) return
|
||||||
@@ -68,6 +76,7 @@ export function useDnD() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isDragOver,
|
isDragOver,
|
||||||
|
screenDragPos,
|
||||||
onDragOver,
|
onDragOver,
|
||||||
onDragLeave,
|
onDragLeave,
|
||||||
onDrop,
|
onDrop,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Ref } from "vue"
|
import type { Ref } from "vue"
|
||||||
import type { Node, Edge } from "@vue-flow/core"
|
import type { Node, Edge, Connection } from "@vue-flow/core"
|
||||||
import { getRandomId } from "utils/functions"
|
import { getRandomId } from "utils/functions"
|
||||||
|
|
||||||
export function useFlowOperations(
|
export function useFlowOperations(
|
||||||
@@ -11,12 +11,11 @@ export function useFlowOperations(
|
|||||||
removeEdges: (edgeIds: string[]) => void,
|
removeEdges: (edgeIds: string[]) => void,
|
||||||
saveState: (nodes: Node[], edges: Edge[]) => void,
|
saveState: (nodes: Node[], edges: Edge[]) => void,
|
||||||
) {
|
) {
|
||||||
// 根据节点类型和handle自动推断标签
|
|
||||||
const getAutoLabel = (
|
const getAutoLabel = (
|
||||||
sourceNode: any,
|
sourceNode: Node | undefined,
|
||||||
targetNode: any,
|
targetNode: Node | undefined,
|
||||||
sourceHandle: string,
|
sourceHandle: string | null | undefined,
|
||||||
targetHandle: string,
|
targetHandle: string | null | undefined,
|
||||||
) => {
|
) => {
|
||||||
const sourceType = sourceNode?.data?.originalType || sourceNode?.type
|
const sourceType = sourceNode?.data?.originalType || sourceNode?.type
|
||||||
const targetType = targetNode?.data?.originalType || targetNode?.type
|
const targetType = targetNode?.data?.originalType || targetNode?.type
|
||||||
@@ -51,9 +50,7 @@ export function useFlowOperations(
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接处理
|
const handleConnect = (params: Connection) => {
|
||||||
const handleConnect = (params: any) => {
|
|
||||||
// 获取源节点和目标节点
|
|
||||||
const sourceNode = nodes.value.find((node) => node.id === params.source)
|
const sourceNode = nodes.value.find((node) => node.id === params.source)
|
||||||
const targetNode = nodes.value.find((node) => node.id === params.target)
|
const targetNode = nodes.value.find((node) => node.id === params.target)
|
||||||
|
|
||||||
@@ -79,9 +76,8 @@ export function useFlowOperations(
|
|||||||
saveState(nodes.value, edges.value)
|
saveState(nodes.value, edges.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 边点击处理 - 单击删除
|
const handleEdgeClick = ({ edge }: { edge: Edge }) => {
|
||||||
const handleEdgeClick = (event: any) => {
|
removeEdges([edge.id])
|
||||||
removeEdges([event.edge.id])
|
|
||||||
saveState(nodes.value, edges.value)
|
saveState(nodes.value, edges.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,12 +111,7 @@ export function useFlowOperations(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 Vue Flow 的更新方法
|
|
||||||
nodes.value[nodeIndex] = updatedNode
|
nodes.value[nodeIndex] = updatedNode
|
||||||
|
|
||||||
// 强制触发响应式更新
|
|
||||||
nodes.value = [...nodes.value]
|
|
||||||
|
|
||||||
saveState(nodes.value, edges.value)
|
saveState(nodes.value, edges.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { ref, computed } from "vue"
|
import { shallowRef, computed } from "vue"
|
||||||
import type { Node, Edge } from "@vue-flow/core"
|
import type { Node, Edge } from "@vue-flow/core"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简化的历史记录管理
|
* 简化的历史记录管理
|
||||||
*/
|
*/
|
||||||
export function useHistory() {
|
export function useHistory() {
|
||||||
const history = ref<{ nodes: Node[]; edges: Edge[] }[]>([])
|
const history = shallowRef<{ nodes: Node[]; edges: Edge[] }[]>([])
|
||||||
const historyIndex = ref(-1)
|
const historyIndex = ref(-1)
|
||||||
|
|
||||||
// 是否可以撤销
|
// 是否可以撤销
|
||||||
@@ -14,21 +14,30 @@ export function useHistory() {
|
|||||||
// 是否可以重做
|
// 是否可以重做
|
||||||
const canRedo = computed(() => historyIndex.value < history.value.length - 1)
|
const canRedo = computed(() => historyIndex.value < history.value.length - 1)
|
||||||
|
|
||||||
|
const deepCopyState = (
|
||||||
|
nodes: Node[],
|
||||||
|
edges: Edge[],
|
||||||
|
): { nodes: Node[]; edges: Edge[] } =>
|
||||||
|
JSON.parse(JSON.stringify({ nodes, edges })) as {
|
||||||
|
nodes: Node[]
|
||||||
|
edges: Edge[]
|
||||||
|
}
|
||||||
|
|
||||||
// 保存状态到历史记录
|
// 保存状态到历史记录
|
||||||
const saveState = (nodes: Node[], edges: Edge[]) => {
|
const saveState = (nodes: Node[], edges: Edge[]) => {
|
||||||
const currentState = { nodes: [...nodes], edges: [...edges] }
|
const currentState = deepCopyState(nodes, edges)
|
||||||
|
|
||||||
// 如果当前不在历史记录的末尾,删除后面的记录
|
// 如果当前不在历史记录的末尾,删除后面的记录
|
||||||
if (historyIndex.value < history.value.length - 1) {
|
if (historyIndex.value < history.value.length - 1) {
|
||||||
history.value = history.value.slice(0, historyIndex.value + 1)
|
history.value = history.value.slice(0, historyIndex.value + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
history.value.push(currentState)
|
history.value = [...history.value, currentState]
|
||||||
historyIndex.value = history.value.length - 1
|
historyIndex.value = history.value.length - 1
|
||||||
|
|
||||||
// 限制历史记录数量
|
// 限制历史记录数量
|
||||||
if (history.value.length > 20) {
|
if (history.value.length > 20) {
|
||||||
history.value.shift()
|
history.value = history.value.slice(1)
|
||||||
historyIndex.value--
|
historyIndex.value--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +47,7 @@ export function useHistory() {
|
|||||||
if (canUndo.value) {
|
if (canUndo.value) {
|
||||||
historyIndex.value--
|
historyIndex.value--
|
||||||
const state = history.value[historyIndex.value]
|
const state = history.value[historyIndex.value]
|
||||||
return state
|
return deepCopyState(state.nodes, state.edges)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -48,7 +57,7 @@ export function useHistory() {
|
|||||||
if (canRedo.value) {
|
if (canRedo.value) {
|
||||||
historyIndex.value++
|
historyIndex.value++
|
||||||
const state = history.value[historyIndex.value]
|
const state = history.value[historyIndex.value]
|
||||||
return state
|
return deepCopyState(state.nodes, state.edges)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user