This commit is contained in:
wfz
2025-12-22 23:20:40 +08:00
parent ff8a6a28f8
commit 9829b91321
8 changed files with 439 additions and 644 deletions

View File

@@ -60,18 +60,12 @@ const dragInfo = computed(() => {
if (source.type === 'design-component') {
return `拖拽: ${source.componentName}`
}
return `: ${source.path}`
return `: ${source.path}`
})
// 当前拖放目标信息
const dropTargetInfo = computed(() => {
if (!dragStore.isDragging) return null
if (!dragStore.selectedNode) return null
const node = dragStore.selectedNode
const typeText = node.type === 'er' ? 'Row' : 'Col'
return `目标: ${typeText} [${node.path}]`
})
// 删除不再使用的代码
// const dropTargetInfo = ...
// const hierarchyInfo = ...
// 最后一次拖放记录
const lastDropInfo = computed(() => {
@@ -98,11 +92,8 @@ onUnmounted(() => {
<span class="info-item drag-info">
🎯 {{ dragInfo }}
</span>
<span v-if="dropTargetInfo" class="info-item target-info">
{{ dropTargetInfo }}
</span>
<span v-if="dragStore.hoverDirection" class="info-item direction-info">
📍 {{ dragStore.hoverDirection === 'top' ? '上方' : dragStore.hoverDirection === 'bottom' ? '下方' : dragStore.hoverDirection === 'left' ? '左侧' : '右侧' }}
<span v-if="hoverInfo" class="info-item hover-info">
👁 {{ hoverInfo }}
</span>
</template>

View File

@@ -59,6 +59,15 @@ const bindElementEvents = (element: HTMLElement, type: ElementType, path: string
}
}
// 鼠标移动(拖拽时持续更新层级)
const handleMouseMove = (e: MouseEvent) => {
// 只在拖拽状态下处理
if (dragStore.isDragging) {
e.stopPropagation()
dragStore.updateHierarchy(element)
}
}
// 鼠标离开
const handleMouseLeave = (e: MouseEvent) => {
e.stopPropagation()
@@ -98,6 +107,7 @@ const bindElementEvents = (element: HTMLElement, type: ElementType, path: string
// 绑定事件
element.addEventListener('mouseenter', handleMouseEnter)
element.addEventListener('mousemove', handleMouseMove) // 新增:拖拽时持续更新
element.addEventListener('mouseleave', handleMouseLeave)
element.addEventListener('click', handleClick)
element.addEventListener('mousedown', handleMouseDown)
@@ -107,6 +117,7 @@ const bindElementEvents = (element: HTMLElement, type: ElementType, path: string
// 返回清理函数
return () => {
element.removeEventListener('mouseenter', handleMouseEnter)
element.removeEventListener('mousemove', handleMouseMove)
element.removeEventListener('mouseleave', handleMouseLeave)
element.removeEventListener('click', handleClick)
element.removeEventListener('mousedown', handleMouseDown)

View File

@@ -1,42 +1,11 @@
<script setup lang="ts">
import { defineAsyncComponent, markRaw, computed, watch } from 'vue'
import { useDesignStore } from '../../stores/designStore'
import { defineAsyncComponent, computed, watch } from 'vue'
import { useVueFileStore } from '../../stores/vueFileStore'
import InteractiveWrapper from './InteractiveWrapper.vue'
import config from './index.json'
const designStore = useDesignStore()
const vueFileStore = useVueFileStore()
// 自动扫描所有设计组件eager 模式确保同步加载)
const designComponentModules = import.meta.glob('../../designComponents/*/index.vue', { eager: true })
// 自动构建设计组件映射表
const designComponentMap: Record<string, any> = {}
for (const path in designComponentModules) {
// 从路径中提取组件 ID例如 '../../designComponents/TextInput/index.vue' => 'TextInput'
const match = path.match(/\/designComponents\/(.+)\/index\.vue$/)
if (match) {
const id = match[1]
const mod = designComponentModules[path] as any
designComponentMap[id] = markRaw(mod.default || mod)
}
}
const getComponent = (componentId: string) => {
return designComponentMap[componentId]
}
const handleSelect = (instanceId: string) => {
designStore.selectComponent(instanceId)
}
const handleRemove = (instanceId: string, event: Event) => {
event.stopPropagation()
designStore.removeComponent(instanceId)
}
// 扫描所有views目录下的Vue文件
const viewModules = import.meta.glob('../../../views/**/*.vue')
@@ -65,10 +34,7 @@ watch(() => vueFileStore.selectedFilePath, (newPath) => {
<div class="design-center">
<div class="center-header">
<span class="title">{{ config.name }}</span>
<span class="count" v-if="!vueFileStore.selectedFilePath">
{{ designStore.components.length }} 个实例
</span>
<span class="file-info" v-else>
<span class="file-info" v-if="vueFileStore.selectedFilePath">
📄 {{ vueFileStore.selectedFileName }}
</span>
</div>
@@ -78,35 +44,12 @@ watch(() => vueFileStore.selectedFilePath, (newPath) => {
<InteractiveWrapper :component="selectedPageComponent" />
</div>
<!-- 原有的设计组件实例列表 -->
<div v-else-if="designStore.components.length > 0" class="component-list">
<div
v-for="instance in designStore.components"
:key="instance.id"
class="component-row"
:class="{ selected: designStore.selectedId === instance.id }"
@click="handleSelect(instance.id)"
>
<div class="component-label">
<span class="component-name">{{ instance.name }}</span>
<button class="remove-btn" @click="handleRemove(instance.id, $event)">×</button>
</div>
<div class="component-preview">
<component
v-if="getComponent(instance.componentId)"
:is="getComponent(instance.componentId)"
v-bind="instance.props"
/>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else class="empty-tip">
<div class="empty-icon">🎨</div>
<div>暂无内容</div>
<div class="empty-hint">
点击页面管理选择Vue页面或从左侧列表添加设计组件
点击页面管理选择Vue页面开始设计
</div>
</div>
</div>
@@ -136,11 +79,6 @@ watch(() => vueFileStore.selectedFilePath, (newPath) => {
font-weight: 500;
}
.count {
color: #888888;
font-size: 11px;
}
.file-info {
color: #4fc3f7;
font-size: 12px;
@@ -161,67 +99,6 @@ watch(() => vueFileStore.selectedFilePath, (newPath) => {
overflow: auto;
}
.component-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.component-row {
margin-bottom: 12px;
border-radius: 6px;
border: 2px solid transparent;
transition: border-color 0.2s;
cursor: pointer;
}
.component-row:hover {
border-color: #3c3c3c;
}
.component-row.selected {
border-color: #007acc;
}
.component-label {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 10px;
background: #252526;
border-radius: 4px 4px 0 0;
}
.component-name {
color: #e0e0e0;
font-size: 12px;
}
.remove-btn {
width: 20px;
height: 20px;
background: transparent;
border: none;
color: #888888;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.remove-btn:hover {
background: #ff4444;
color: white;
}
.component-preview {
padding: 8px;
background: #1e1e1e;
border-radius: 0 0 4px 4px;
}
.empty-tip {
color: #666666;
text-align: center;

View File

@@ -1,13 +1,5 @@
import { defineStore } from 'pinia'
import { ref, watch, computed } from 'vue'
// 设计组件实例
export interface DesignComponentInstance {
id: string // 实例唯一ID
componentId: string // 设计组件类型ID
name: string // 显示名称
props: Record<string, any> // 属性值
}
import { ref } from 'vue'
// 设计组件定义
export interface DesignComponentMeta {
@@ -17,36 +9,12 @@ export interface DesignComponentMeta {
props: Record<string, any>
}
// 生成唯一ID
const generateId = () => Math.random().toString(36).substring(2, 9)
export const useDesignStore = defineStore('design', () => {
// 设计中心已添加的组件实例列表
const components = ref<DesignComponentInstance[]>([])
// 当前选中的组件实例ID
const selectedId = ref<string | null>(null)
// 设计组件元数据缓存
const componentMetas = ref<DesignComponentMeta[]>([])
// 自动扫描所有设计组件的 .json 配置文件
const designComponentMetaModules = import.meta.glob('../designComponents/*/index.json', { eager: true })
// 是否已加载
const isLoaded = ref(false)
// 当前选中的组件实例
const selectedComponent = computed(() => {
if (!selectedId.value) return null
return components.value.find(c => c.id === selectedId.value) || null
})
// 当前选中组件的元数据(属性定义)
const selectedComponentMeta = computed(() => {
if (!selectedComponent.value) return null
return componentMetas.value.find(m => m.id === selectedComponent.value!.componentId) || null
})
// 加载设计组件元数据(从本地文件自动扫描)
const loadComponentMetas = async () => {
@@ -71,132 +39,20 @@ export const useDesignStore = defineStore('design', () => {
}
componentMetas.value = metas
console.log('加载设计组件元数据:', metas.length, '个组件')
} catch (error) {
console.error('加载设计组件元数据失败:', error)
}
}
// 加载设计中心状态
const loadState = async () => {
try {
const response = await fetch('/api/design-state')
if (response.ok) {
const data = await response.json()
if (data.components) {
components.value = data.components
}
if (data.selectedId) {
selectedId.value = data.selectedId
}
}
} catch (error) {
console.log('使用默认设计状态')
}
isLoaded.value = true
}
// 保存设计中心状态
const saveState = async () => {
try {
await fetch('/api/design-state', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
components: components.value,
selectedId: selectedId.value,
lastUpdated: new Date().toISOString()
})
})
} catch (error) {
console.error('保存设计状态失败:', error)
}
}
// 监听变化自动保存
watch([components, selectedId], () => {
if (isLoaded.value) {
saveState()
}
}, { deep: true })
// 添加设计组件到设计中心
const addComponent = (componentId: string) => {
const meta = componentMetas.value.find(m => m.id === componentId)
if (!meta) {
console.warn('未找到设计组件:', componentId)
return
}
// 计算同类型组件的数量
const sameTypeCount = components.value.filter(c => c.componentId === componentId).length
const instance: DesignComponentInstance = {
id: generateId(),
componentId: componentId,
name: `${meta.name} ${sameTypeCount + 1}`,
props: JSON.parse(JSON.stringify(meta.props))
}
components.value.push(instance)
selectedId.value = instance.id
}
// 移除设计组件
const removeComponent = (instanceId: string) => {
const index = components.value.findIndex(c => c.id === instanceId)
if (index > -1) {
components.value.splice(index, 1)
if (selectedId.value === instanceId) {
selectedId.value = components.value.length > 0 ? components.value[0].id : null
}
}
}
// 选中设计组件
const selectComponent = (instanceId: string | null) => {
selectedId.value = instanceId
}
// 更新组件属性
const updateComponentProps = (instanceId: string, key: string, value: any) => {
const component = components.value.find(c => c.id === instanceId)
if (component) {
component.props[key] = value
}
}
// 重新排序组件(拖拽后)
const reorderComponents = (newOrder: DesignComponentInstance[]) => {
components.value = newOrder
}
// 获取组件元数据
const getComponentMeta = (componentId: string) => {
return componentMetas.value.find(m => m.id === componentId)
}
// 初始化
const init = async () => {
await loadComponentMetas()
await loadState()
}
return {
components,
selectedId,
selectedComponent,
selectedComponentMeta,
componentMetas,
isLoaded,
init,
loadComponentMetas,
loadState,
saveState,
addComponent,
removeComponent,
selectComponent,
updateComponentProps,
reorderComponents,
getComponentMeta
}
})