3
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user