From ead5fd21f05953f9fce3652c2b2d7e9e5d1c24b6 Mon Sep 17 00:00:00 2001 From: liuziting Date: Wed, 21 Jan 2026 16:29:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B0=8F=E7=AA=97=E9=A2=84?= =?UTF-8?q?=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 15 +++++ package.json | 1 + src/App.vue | 150 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 162 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 151337c..88de3fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.48.1", + "@vue-flow/minimap": "^1.5.4", "@vue-flow/node-resizer": "^1.5.0", "axios": "^1.6.7", "html-to-image": "^1.11.13", @@ -997,6 +998,20 @@ "vue": "^3.3.0" } }, + "node_modules/@vue-flow/minimap": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vue-flow/minimap/-/minimap-1.5.4.tgz", + "integrity": "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==", + "license": "MIT", + "dependencies": { + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + }, + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, "node_modules/@vue-flow/node-resizer": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@vue-flow/node-resizer/-/node-resizer-1.5.0.tgz", diff --git a/package.json b/package.json index 7e16de6..5fdfab6 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.48.1", + "@vue-flow/minimap": "^1.5.4", "@vue-flow/node-resizer": "^1.5.0", "axios": "^1.6.7", "html-to-image": "^1.11.13", diff --git a/src/App.vue b/src/App.vue index dd6989a..60d736e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -24,16 +24,23 @@ import { X, Maximize2, Terminal, - ChevronRight + ChevronRight, + LayoutDashboard, + Focus, + Target, + Map } from 'lucide-vue-next' import { VueFlow, useVueFlow, Position, MarkerType, Handle } from '@vue-flow/core' import { Background, BackgroundVariant } from '@vue-flow/background' import { Controls } from '@vue-flow/controls' +import { MiniMap } from '@vue-flow/minimap' import { toPng } from 'html-to-image' // 导入 VueFlow 样式 import '@vue-flow/core/dist/style.css' import '@vue-flow/core/dist/theme-default.css' +import '@vue-flow/minimap/dist/style.css' +import '@vue-flow/controls/dist/style.css' // API 配置 const API_KEY = import.meta.env.VITE_ZHIPU_AI_API_KEY @@ -49,6 +56,24 @@ const focusedNodeId = ref(null) const draggingNodeId = ref(null) const previewImageUrl = ref(null) +// 画布控制状态 +const panOnDrag = ref(true) +const isSpacePressed = ref(false) + +// 键盘监听:按住空格键开启抓手拖拽 +window.addEventListener('keydown', e => { + if (e.code === 'Space' && !['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName)) { + isSpacePressed.value = true + panOnDrag.value = true + } +}) + +window.addEventListener('keyup', e => { + if (e.code === 'Space') { + isSpacePressed.value = false + } +}) + // 拖拽监听 onNodeDragStart(e => { draggingNodeId.value = e.node.id @@ -120,7 +145,8 @@ const config = reactive({ edgeColor: '#fed7aa', edgeStyle: 'smoothstep', backgroundVariant: BackgroundVariant.Lines, - showControls: true + showControls: true, + showMiniMap: true }) const lastAppliedStatus = ref('') @@ -158,6 +184,56 @@ watch( { immediate: true } ) +/** + * 聚焦到根节点 + */ +const centerRoot = () => { + const rootNode = flowNodes.value.find(n => n.data.type === 'root') + if (rootNode) { + fitView({ nodes: [rootNode.id], padding: 2, duration: 800 }) + } +} + +/** + * 重置布局 + */ +const resetLayout = () => { + // 找到根节点 + const rootNode = flowNodes.value.find(n => n.data.type === 'root') + if (!rootNode) return + + // 重新计算所有节点位置 + const visited = new Set() + const layoutNode = (nodeId: string, x: number, y: number) => { + if (visited.has(nodeId)) return + visited.add(nodeId) + + const node = flowNodes.value.find(n => n.id === nodeId) + if (node) { + node.position = { x, y } + + // 找到所有子节点 + const childEdges = flowEdges.value.filter(e => e.source === nodeId) + childEdges.forEach((edge, index) => { + const offsetX = 450 + // 计算子节点垂直分布 + const totalHeight = (childEdges.length - 1) * 280 + const startY = y - totalHeight / 2 + const offsetY = index * 280 + + layoutNode(edge.target, x + offsetX, startY + offsetY) + }) + } + } + + layoutNode(rootNode.id, 50, 300) + + // 动画过渡到合适视图 + setTimeout(() => { + fitView({ padding: 0.2, duration: 800 }) + }, 100) +} + /** * 导出为图片 */ @@ -198,7 +274,7 @@ const generateNodeImage = async (nodeId: string, prompt: string) => { }, body: JSON.stringify({ model: 'cogview-3-flash', - prompt: `一张精美的、极简插画风格的图片,表现主题:${prompt}。要求:构图简洁,色彩明快,适合作为思维导图的视觉辅助。` + prompt: `生成一张图片,表现主题:${prompt}。要求:构图简洁,色彩明快,适合作为思维导图的视觉辅助。` }) }) @@ -410,6 +486,24 @@ const startNewSession = () => {
+ + + + + + + +
+
@@ -425,6 +519,19 @@ const startNewSession = () => {
+ + + +
+