1 parent
23844ae301
commit
d25b5dbe6d
268 files changed
+3684
-8861
No files matched your search
@@ -87,9 +87,7 @@ let globalClickHandler: ((event: MouseEvent) => void) | null = null
|
||||
// 计算属性
|
||||
const nodeType = computed(() => props.data.originalType || props.type)
|
||||
const nodeConfig = computed(() => getNodeTypeConfig(nodeType.value))
|
||||
const displayLabel = computed(
|
||||
() => props.data.customLabel || nodeConfig.value.label,
|
||||
)
|
||||
const displayLabel = computed(() => props.data.customLabel || nodeConfig.value.label)
|
||||
|
||||
// 事件处理器
|
||||
const handleDelete = () => emit("delete", props.id)
|
||||
@@ -164,10 +162,7 @@ const addGlobalClickHandler = () => {
|
||||
if (globalClickHandler) return
|
||||
|
||||
globalClickHandler = (event: MouseEvent) => {
|
||||
if (
|
||||
isEditing.value &&
|
||||
!(event.target as Element)?.closest(".custom-node")
|
||||
) {
|
||||
if (isEditing.value && !(event.target as Element)?.closest(".custom-node")) {
|
||||
handleSaveEdit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="node-actions"
|
||||
@mouseenter="$emit('mouseenter')"
|
||||
@mouseleave="$emit('mouseleave')"
|
||||
>
|
||||
<button
|
||||
class="action-btn delete-btn"
|
||||
@click.stop="$emit('delete')"
|
||||
title="删除节点"
|
||||
>
|
||||
<div class="node-actions" @mouseenter="$emit('mouseenter')" @mouseleave="$emit('mouseleave')">
|
||||
<button class="action-btn delete-btn" @click.stop="$emit('delete')" title="删除节点">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
||||
|
||||
@@ -14,8 +14,7 @@ const onDragStart = (event: DragEvent, type: string) => {
|
||||
|
||||
// 隐藏浏览器默认拖影,改用 canvas 跟随预览
|
||||
const emptyImg = new Image(1, 1)
|
||||
emptyImg.src =
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||
emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||
event.dataTransfer.setDragImage(emptyImg, 0, 0)
|
||||
}
|
||||
|
||||
@@ -44,15 +43,13 @@ const emit = defineEmits<{
|
||||
|
||||
// 节点类型定义 - 优化性能
|
||||
const nodeTypes = computed(() =>
|
||||
["start", "input", "default", "decision", "loop", "output", "end"].map(
|
||||
(type) => {
|
||||
const config = getNodeTypeConfig(type)
|
||||
return {
|
||||
type,
|
||||
...config,
|
||||
}
|
||||
},
|
||||
),
|
||||
["start", "input", "default", "decision", "loop", "output", "end"].map((type) => {
|
||||
const config = getNodeTypeConfig(type)
|
||||
return {
|
||||
type,
|
||||
...config,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const saveStatusTitle = computed(() => {
|
||||
@@ -135,11 +132,7 @@ const saveStatusTitle = computed(() => {
|
||||
<span class="btn-text">重做</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="action-btn clear-btn"
|
||||
@click="$emit('clear')"
|
||||
title="清空画布"
|
||||
>
|
||||
<button class="action-btn clear-btn" @click="$emit('clear')" title="清空画布">
|
||||
<span class="btn-icon">🗑️</span>
|
||||
<span class="btn-text">清空画布</span>
|
||||
</button>
|
||||
|
||||
@@ -4,13 +4,7 @@ import Toolbar from "./Toolbar.vue"
|
||||
import "@vue-flow/core/dist/style.css"
|
||||
import "@vue-flow/core/dist/theme-default.css"
|
||||
import "@vue-flow/controls/dist/style.css"
|
||||
import {
|
||||
useVueFlow,
|
||||
VueFlow,
|
||||
type Node,
|
||||
type Edge,
|
||||
MarkerType,
|
||||
} from "@vue-flow/core"
|
||||
import { useVueFlow, VueFlow, type Node, type Edge, MarkerType } from "@vue-flow/core"
|
||||
import { Controls } from "@vue-flow/controls"
|
||||
import { Background } from "@vue-flow/background"
|
||||
|
||||
@@ -49,18 +43,16 @@ const cacheKey = computed(() =>
|
||||
? `flowchart-editor-data-problem-${problem.value!._id}`
|
||||
: "flowchart-editor-data",
|
||||
)
|
||||
const {
|
||||
isSaving,
|
||||
lastSaved,
|
||||
hasUnsavedChanges,
|
||||
saveToCache,
|
||||
loadFromCache,
|
||||
clearCache,
|
||||
} = useCache(nodes, edges, cacheKey, () => {
|
||||
// 换题后画布已经被换成新题的草稿,历史必须跟着重建,
|
||||
// 否则一次撤销就会把上一题的图还原到这一题里
|
||||
resetHistory(nodes.value, edges.value)
|
||||
})
|
||||
const { isSaving, lastSaved, hasUnsavedChanges, saveToCache, loadFromCache, clearCache } = useCache(
|
||||
nodes,
|
||||
edges,
|
||||
cacheKey,
|
||||
() => {
|
||||
// 换题后画布已经被换成新题的草稿,历史必须跟着重建,
|
||||
// 否则一次撤销就会把上一题的图还原到这一题里
|
||||
resetHistory(nodes.value, edges.value)
|
||||
},
|
||||
)
|
||||
|
||||
// 拖拽处理
|
||||
const { onDragOver, onDragLeave, onDrop, isDragOver, screenDragPos } = useDnD()
|
||||
@@ -85,14 +77,7 @@ const {
|
||||
handleNodeUpdate,
|
||||
clearCanvas,
|
||||
deleteSelected,
|
||||
} = useFlowOperations(
|
||||
nodes,
|
||||
edges,
|
||||
addEdges,
|
||||
removeNodes,
|
||||
removeEdges,
|
||||
saveState,
|
||||
)
|
||||
} = useFlowOperations(nodes, edges, addEdges, removeNodes, removeEdges, saveState)
|
||||
|
||||
const handleDrop = async (event: DragEvent) => {
|
||||
const newNode = onDrop(event)
|
||||
@@ -140,10 +125,7 @@ const handleClear = () => {
|
||||
|
||||
// 键盘事件
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
event.target instanceof HTMLInputElement ||
|
||||
event.target instanceof HTMLTextAreaElement
|
||||
)
|
||||
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement)
|
||||
return
|
||||
|
||||
if (event.key === "Delete" || event.key === "Backspace") {
|
||||
@@ -220,9 +202,7 @@ defineExpose({
|
||||
class="drag-node-preview"
|
||||
:style="dragPreviewStyle"
|
||||
>
|
||||
<span class="preview-icon">{{
|
||||
getNodeTypeConfig(currentDragNodeType).icon
|
||||
}}</span>
|
||||
<span class="preview-icon">{{ getNodeTypeConfig(currentDragNodeType).icon }}</span>
|
||||
<span>{{ getNodeTypeConfig(currentDragNodeType).label }}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -272,12 +252,7 @@ defineExpose({
|
||||
orient="auto"
|
||||
markerUnits="strokeWidth"
|
||||
>
|
||||
<path
|
||||
d="M0,0 L0,6 L10,3 z"
|
||||
fill="#6366f1"
|
||||
stroke="#6366f1"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
<path d="M0,0 L0,6 L10,3 z" fill="#6366f1" stroke="#6366f1" strokeWidth="0.5" />
|
||||
</marker>
|
||||
</defs>
|
||||
<template #node-custom="{ data, id, type }">
|
||||
|
||||
@@ -53,9 +53,7 @@ export function useCache(
|
||||
if (storedData.value.nodes?.length || storedData.value.edges?.length) {
|
||||
nodes.value = storedData.value.nodes
|
||||
edges.value = storedData.value.edges
|
||||
lastSaved.value = storedData.value.timestamp
|
||||
? new Date(storedData.value.timestamp)
|
||||
: null
|
||||
lastSaved.value = storedData.value.timestamp ? new Date(storedData.value.timestamp) : null
|
||||
hasUnsavedChanges.value = false
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { ref } from "vue"
|
||||
import { useVueFlow } from "@vue-flow/core"
|
||||
import {
|
||||
getNodeTypeConfig,
|
||||
createNodeStyle,
|
||||
getNodeDimensions,
|
||||
} from "./useNodeStyles"
|
||||
import { getNodeTypeConfig, createNodeStyle, getNodeDimensions } from "./useNodeStyles"
|
||||
import { getRandomId } from "utils/functions"
|
||||
|
||||
// 模块级共享:当前拖拽的节点类型(Toolbar 写入,canvas 读取)
|
||||
|
||||
@@ -57,12 +57,7 @@ export function useFlowOperations(
|
||||
const targetNode = nodes.value.find((node) => node.id === params.target)
|
||||
|
||||
// 自动推断标签
|
||||
const autoLabel = getAutoLabel(
|
||||
sourceNode,
|
||||
targetNode,
|
||||
params.sourceHandle,
|
||||
params.targetHandle,
|
||||
)
|
||||
const autoLabel = getAutoLabel(sourceNode, targetNode, params.sourceHandle, params.targetHandle)
|
||||
|
||||
const newEdge: Edge = {
|
||||
id: `edge-${getRandomId()}`,
|
||||
|
||||
@@ -15,10 +15,7 @@ export function useHistory() {
|
||||
// 是否可以重做
|
||||
const canRedo = computed(() => historyIndex.value < history.value.length - 1)
|
||||
|
||||
const deepCopyState = (
|
||||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
): { nodes: Node[]; edges: Edge[] } =>
|
||||
const deepCopyState = (nodes: Node[], edges: Edge[]): { nodes: Node[]; edges: Edge[] } =>
|
||||
// 先裁掉 vue-flow 的运行时内部字段再深拷贝:20 份快照 × 每个节点几百字节的
|
||||
// handleBounds/dimensions,纯属白拷
|
||||
JSON.parse(
|
||||
|
||||
Reference in new issue
Block a user