This commit is contained in:
wfz
2025-12-22 23:54:48 +08:00
parent 9829b91321
commit 378fb65c76
8 changed files with 166 additions and 47 deletions

View File

@@ -26,6 +26,12 @@
"title": "设计中心", "title": "设计中心",
"content": "新窗口内容", "content": "新窗口内容",
"materialId": "DesignCenter" "materialId": "DesignCenter"
},
{
"id": "m9fv3nb",
"title": "测试组件A",
"content": "新窗口内容",
"materialId": "TestWidget1"
} }
], ],
"activeTabId": "j70ckww" "activeTabId": "j70ckww"
@@ -141,5 +147,5 @@
"activeTabId": "mxfx11j" "activeTabId": "mxfx11j"
} }
}, },
"lastUpdated": "2025-12-22T15:06:06.209Z" "lastUpdated": "2025-12-22T15:54:13.388Z"
} }

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch, onMounted, onUnmounted } from 'vue' import { computed, ref, watch } from 'vue'
import { useDragStore } from '../../plugins' import { useDragStore } from '../../plugins'
import type { DropDirection } from '../../plugins' import type { DropDirection } from '../../plugins'
@@ -38,23 +38,6 @@ const isRowLayout = computed(() => {
return dragStore.selectedNode?.type === 'er' return dragStore.selectedNode?.type === 'er'
}) })
// 处理区域悬停
const handleZoneEnter = (direction: DropDirection) => {
dragStore.setHoverDirection(direction)
}
// 处理区域离开
const handleZoneLeave = () => {
dragStore.setHoverDirection(null)
}
// 处理区域点击(确认放置)
const handleZoneClick = (direction: DropDirection) => {
dragStore.setHoverDirection(direction)
dragStore.confirmDrop()
dragStore.endDrag()
}
// 获取方向文本 // 获取方向文本
const getDirectionText = (direction: DropDirection): string => { const getDirectionText = (direction: DropDirection): string => {
const texts: Record<DropDirection, string> = { const texts: Record<DropDirection, string> = {
@@ -94,9 +77,6 @@ const getDirectionIcon = (direction: DropDirection): string => {
`zone-${direction}`, `zone-${direction}`,
{ 'is-active': dragStore.hoverDirection === direction } { 'is-active': dragStore.hoverDirection === direction }
]" ]"
@mouseenter="handleZoneEnter(direction)"
@mouseleave="handleZoneLeave"
@click.stop="handleZoneClick(direction)"
> >
<span class="zone-icon">{{ getDirectionIcon(direction) }}</span> <span class="zone-icon">{{ getDirectionIcon(direction) }}</span>
<span class="zone-text">{{ getDirectionText(direction) }}</span> <span class="zone-text">{{ getDirectionText(direction) }}</span>
@@ -107,14 +87,10 @@ const getDirectionIcon = (direction: DropDirection): string => {
<style scoped> <style scoped>
.drop-zone-container { .drop-zone-container {
position: absolute; pointer-events: none; /* 容器和所有子元素都不拦截事件 */
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
pointer-events: none;
z-index: 100; z-index: 100;
display: flex;
/* 不再设置 position, top, left 等,直接使用 :style 绑定的值 */
} }
/* 行布局:上下排列 */ /* 行布局:上下排列 */
@@ -137,7 +113,7 @@ const getDirectionIcon = (direction: DropDirection): string => {
background: rgba(64, 158, 255, 0.1); background: rgba(64, 158, 255, 0.1);
border: 2px dashed rgba(64, 158, 255, 0.5); border: 2px dashed rgba(64, 158, 255, 0.5);
transition: all 0.2s; transition: all 0.2s;
pointer-events: auto; pointer-events: none; /* 不拦截事件,让底层元素可以接收 mouseenter */
cursor: pointer; cursor: pointer;
} }

View File

@@ -50,21 +50,11 @@ const bindElementEvents = (element: HTMLElement, type: ElementType, path: string
const handleMouseEnter = (e: MouseEvent) => { const handleMouseEnter = (e: MouseEvent) => {
e.stopPropagation() e.stopPropagation()
// 如果正在拖拽,更新层级节点 // 更新交互store的悬停状态无论是否拖拽
if (dragStore.isDragging) { interactionStore.onHover(target)
dragStore.updateHierarchy(element)
} else {
interactionStore.onHover(target)
element.classList.add('fauto-hover')
}
}
// 鼠标移动(拖拽时持续更新层级) if (!dragStore.isDragging) {
const handleMouseMove = (e: MouseEvent) => { element.classList.add('fauto-hover')
// 只在拖拽状态下处理
if (dragStore.isDragging) {
e.stopPropagation()
dragStore.updateHierarchy(element)
} }
} }
@@ -107,7 +97,6 @@ const bindElementEvents = (element: HTMLElement, type: ElementType, path: string
// 绑定事件 // 绑定事件
element.addEventListener('mouseenter', handleMouseEnter) element.addEventListener('mouseenter', handleMouseEnter)
element.addEventListener('mousemove', handleMouseMove) // 新增:拖拽时持续更新
element.addEventListener('mouseleave', handleMouseLeave) element.addEventListener('mouseleave', handleMouseLeave)
element.addEventListener('click', handleClick) element.addEventListener('click', handleClick)
element.addEventListener('mousedown', handleMouseDown) element.addEventListener('mousedown', handleMouseDown)
@@ -117,7 +106,6 @@ const bindElementEvents = (element: HTMLElement, type: ElementType, path: string
// 返回清理函数 // 返回清理函数
return () => { return () => {
element.removeEventListener('mouseenter', handleMouseEnter) element.removeEventListener('mouseenter', handleMouseEnter)
element.removeEventListener('mousemove', handleMouseMove)
element.removeEventListener('mouseleave', handleMouseLeave) element.removeEventListener('mouseleave', handleMouseLeave)
element.removeEventListener('click', handleClick) element.removeEventListener('click', handleClick)
element.removeEventListener('mousedown', handleMouseDown) element.removeEventListener('mousedown', handleMouseDown)
@@ -290,6 +278,13 @@ watch(() => props.component, () => {
}, 100) }, 100)
}) })
}) })
// 监听悬停目标变化,拖拽时更新层级
watch(() => interactionStore.hoverTarget, (target) => {
if (dragStore.isDragging && target?.element) {
dragStore.updateHierarchy(target.element)
}
})
</script> </script>
<template> <template>

View File

@@ -78,6 +78,9 @@ const getNodeIcon = (data: TreeNodeData) => {
} }
onMounted(() => { onMounted(() => {
// 初始化Vue文件选择store
vueFileStore.initialize()
// 扫描文件
scanViewsDirectory() scanViewsDirectory()
}) })
</script> </script>

View File

@@ -139,8 +139,64 @@ export const useDragStore = defineStore('drag', () => {
// 按深度从深到浅排序(最深的在前面) // 按深度从深到浅排序(最深的在前面)
nodes.sort((a, b) => b.depth - a.depth) nodes.sort((a, b) => b.depth - a.depth)
// 检查层级结构是否改变(比较路径列表)
const oldPaths = hierarchyNodes.value.map(n => n.path).join(',')
const newPaths = nodes.map(n => n.path).join(',')
const hierarchyChanged = oldPaths !== newPaths
// 记录旧的选择深度和路径
const oldSelectedNode = hierarchyNodes.value[selectedHierarchyIndex.value]
const oldSelectedDepth = oldSelectedNode?.depth
const oldSelectedPath = oldSelectedNode?.path
// 始终更新层级节点列表
hierarchyNodes.value = nodes hierarchyNodes.value = nodes
selectedHierarchyIndex.value = 0 // 默认选中最深层级
// 如果层级结构改变,尝试保持相同深度或路径的选择
if (hierarchyChanged) {
// 1. 优先尝试找到相同路径的节点
if (oldSelectedPath) {
const samePathIndex = nodes.findIndex(n => n.path === oldSelectedPath)
if (samePathIndex !== -1) {
selectedHierarchyIndex.value = samePathIndex
console.log('[DragStore] 保持相同路径选择:', oldSelectedPath)
return
}
}
// 2. 尝试找到相同深度的节点
if (oldSelectedDepth !== undefined) {
const sameDepthIndex = nodes.findIndex(n => n.depth === oldSelectedDepth)
if (sameDepthIndex !== -1) {
selectedHierarchyIndex.value = sameDepthIndex
console.log('[DragStore] 保持相同深度选择:', oldSelectedDepth)
return
}
}
// 3. 找到最接近的深度
if (oldSelectedDepth !== undefined && nodes.length > 0) {
let closestIndex = 0
let minDiff = Math.abs(nodes[0].depth - oldSelectedDepth)
for (let i = 1; i < nodes.length; i++) {
const diff = Math.abs(nodes[i].depth - oldSelectedDepth)
if (diff < minDiff) {
minDiff = diff
closestIndex = i
}
}
selectedHierarchyIndex.value = closestIndex
console.log('[DragStore] 选择最接近深度:', nodes[closestIndex].path)
} else {
// 4. 默认选中最深层级
selectedHierarchyIndex.value = 0
console.log('[DragStore] 默认选择最深层级')
}
} else {
// 层级结构未变,保持当前索引(确保不超出范围)
selectedHierarchyIndex.value = Math.min(selectedHierarchyIndex.value, nodes.length - 1)
console.log('[DragStore] 保持当前选择索引:', selectedHierarchyIndex.value)
}
console.log('[DragStore] 更新层级:', nodes.map(n => n.path)) console.log('[DragStore] 更新层级:', nodes.map(n => n.path))
} }

View File

@@ -10,6 +10,20 @@ export interface VueFileNode {
} }
export const useVueFileStore = defineStore('vueFile', () => { export const useVueFileStore = defineStore('vueFile', () => {
// 从 API 加载上次选中的文件
const loadFromApi = async () => {
try {
const response = await fetch('/api/vue-file-selection')
if (response.ok) {
const data = await response.json()
return data
}
} catch (error) {
console.error('加载Vue文件选择失败:', error)
}
return { path: null, name: null }
}
// 当前选中的Vue文件路径 // 当前选中的Vue文件路径
const selectedFilePath = ref<string | null>(null) const selectedFilePath = ref<string | null>(null)
@@ -19,10 +33,26 @@ export const useVueFileStore = defineStore('vueFile', () => {
// 文件树结构 // 文件树结构
const fileTree = ref<VueFileNode[]>([]) const fileTree = ref<VueFileNode[]>([])
// 是否已加载
const isLoaded = ref(false)
// 初始化加载
const initialize = async () => {
const stored = await loadFromApi()
selectedFilePath.value = stored.path
selectedFileName.value = stored.name
isLoaded.value = true
console.log('初始化Vue文件选择:', stored)
}
// 设置选中的文件 // 设置选中的文件
const selectFile = (path: string, name: string) => { const selectFile = (path: string, name: string) => {
selectedFilePath.value = path selectedFilePath.value = path
selectedFileName.value = name selectedFileName.value = name
// 保存到 API
saveToApi({ path, name })
console.log('选中文件:', { path, name }) console.log('选中文件:', { path, name })
} }
@@ -30,6 +60,22 @@ export const useVueFileStore = defineStore('vueFile', () => {
const clearSelection = () => { const clearSelection = () => {
selectedFilePath.value = null selectedFilePath.value = null
selectedFileName.value = null selectedFileName.value = null
// 清除 API 数据
saveToApi({ path: null, name: null })
}
// 保存到 API
const saveToApi = async (data: { path: string | null, name: string | null }) => {
try {
await fetch('/api/vue-file-selection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
} catch (error) {
console.error('保存Vue文件选择失败:', error)
}
} }
// 构建文件树从import.meta.glob结果 // 构建文件树从import.meta.glob结果
@@ -103,6 +149,8 @@ export const useVueFileStore = defineStore('vueFile', () => {
selectedFilePath, selectedFilePath,
selectedFileName, selectedFileName,
fileTree, fileTree,
isLoaded,
initialize,
selectFile, selectFile,
clearSelection, clearSelection,
buildFileTree buildFileTree

View File

@@ -8,6 +8,7 @@ import type { Plugin } from 'vite'
const CONFIG_FILE = path.resolve(__dirname, 'config.json') const CONFIG_FILE = path.resolve(__dirname, 'config.json')
const DESIGN_STATE_FILE = path.resolve(__dirname, 'design-state.json') const DESIGN_STATE_FILE = path.resolve(__dirname, 'design-state.json')
const MATERIAL_STATES_FILE = path.resolve(__dirname, 'material-states.json') const MATERIAL_STATES_FILE = path.resolve(__dirname, 'material-states.json')
const VUE_FILE_SELECTION_FILE = path.resolve(__dirname, 'vue-file-selection.json')
const DESIGN_COMPONENTS_DIR = path.resolve(__dirname, 'src/designComponents') const DESIGN_COMPONENTS_DIR = path.resolve(__dirname, 'src/designComponents')
// 通用JSON文件读写处理器 // 通用JSON文件读写处理器
@@ -52,6 +53,7 @@ function configApiPlugin(): Plugin {
const configHandler = createJsonHandler(CONFIG_FILE) const configHandler = createJsonHandler(CONFIG_FILE)
const designStateHandler = createJsonHandler(DESIGN_STATE_FILE) const designStateHandler = createJsonHandler(DESIGN_STATE_FILE)
const materialStatesHandler = createJsonHandler(MATERIAL_STATES_FILE) const materialStatesHandler = createJsonHandler(MATERIAL_STATES_FILE)
const vueFileSelectionHandler = createJsonHandler(VUE_FILE_SELECTION_FILE)
return { return {
name: 'config-api', name: 'config-api',
@@ -158,6 +160,35 @@ function configApiPlugin(): Plugin {
next() next()
} }
}) })
// Vue文件选择状态 API
server.middlewares.use('/api/vue-file-selection', (req, res, next) => {
if (req.method === 'GET') {
try {
res.setHeader('Content-Type', 'application/json')
res.end(vueFileSelectionHandler.read())
} catch (error) {
res.statusCode = 500
res.end(JSON.stringify({ error: 'Failed to read vue file selection' }))
}
} else if (req.method === 'POST') {
let body = ''
req.on('data', (chunk) => body += chunk.toString())
req.on('end', () => {
try {
const selection = JSON.parse(body)
vueFileSelectionHandler.write(JSON.stringify(selection, null, 2))
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ success: true }))
} catch (error) {
res.statusCode = 500
res.end(JSON.stringify({ error: 'Failed to save vue file selection' }))
}
})
} else {
next()
}
})
} }
} }
} }

View File

@@ -0,0 +1,4 @@
{
"path": "../../../views/TestPage1.vue",
"name": "TestPage1.vue"
}