23
This commit is contained in:
@@ -1,34 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { usePanelStore } from './stores/panelStore'
|
||||
import { useDesignStore } from './stores/designStore'
|
||||
import Header from './components/Header.vue'
|
||||
import Footer from './components/Footer.vue'
|
||||
import MainLayout from './components/MainLayout.vue'
|
||||
|
||||
const panelStore = usePanelStore()
|
||||
const designStore = useDesignStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await panelStore.loadConfig()
|
||||
await designStore.init()
|
||||
})
|
||||
// App.vue 现在只作为路由容器
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<Header />
|
||||
<MainLayout />
|
||||
<Footer />
|
||||
</div>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 全局样式已在 style.css 中定义 */
|
||||
</style>
|
||||
|
||||
34
draggable-panels/src/fauto/Designer.vue
Normal file
34
draggable-panels/src/fauto/Designer.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { usePanelStore } from './stores/panelStore'
|
||||
import { useDesignStore } from './stores/designStore'
|
||||
import Header from './components/Header.vue'
|
||||
import Footer from './components/Footer.vue'
|
||||
import MainLayout from './components/MainLayout.vue'
|
||||
|
||||
const panelStore = usePanelStore()
|
||||
const designStore = useDesignStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await panelStore.loadConfig()
|
||||
await designStore.init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="designer-container">
|
||||
<Header />
|
||||
<MainLayout />
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.designer-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineAsyncComponent, markRaw } from 'vue'
|
||||
import { defineAsyncComponent, markRaw } from 'vue'
|
||||
import { useDesignStore } from '../../stores/designStore'
|
||||
import config from './index.json'
|
||||
|
||||
const designStore = useDesignStore()
|
||||
|
||||
// 动态加载设计组件
|
||||
const designComponentMap: Record<string, any> = {
|
||||
TextInput: markRaw(defineAsyncComponent(() => import('../../designComponents/TextInput/index.vue'))),
|
||||
RadioSelect: markRaw(defineAsyncComponent(() => import('../../designComponents/RadioSelect/index.vue'))),
|
||||
GridTable: markRaw(defineAsyncComponent(() => import('../../designComponents/GridTable/index.vue')))
|
||||
// 自动扫描所有设计组件(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) => {
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import { useDesignStore } from '../../stores/designStore'
|
||||
import config from './index.json'
|
||||
@@ -10,13 +10,13 @@ const designStore = useDesignStore()
|
||||
const localComponents = ref([...designStore.components])
|
||||
|
||||
// 监听 designStore 组件变化,同步到本地副本
|
||||
const unsubscribe = designStore.$subscribe((mutation, state) => {
|
||||
// 检查是否是组件列表变化
|
||||
if (mutation.type === 'patchObject' &&
|
||||
(mutation.payload.components || mutation.events.some(e => e.path.includes('components')))) {
|
||||
localComponents.value = [...designStore.components]
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => designStore.components,
|
||||
(newVal) => {
|
||||
localComponents.value = [...newVal]
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
// 拖拽结束处理 - 同步顺序到 designStore
|
||||
const onDragEnd = () => {
|
||||
43
draggable-panels/src/fauto/materials/index.ts
Normal file
43
draggable-panels/src/fauto/materials/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { defineAsyncComponent, type Component } from 'vue'
|
||||
import type { MaterialInfo } from '../types'
|
||||
|
||||
// 自动扫描所有物料组件的 .vue 文件
|
||||
const vueModules = import.meta.glob('./*/index.vue')
|
||||
|
||||
// 自动扫描所有物料组件的 .json 配置文件
|
||||
const jsonModules = import.meta.glob('./*/index.json', { eager: true })
|
||||
|
||||
// 自动构建物料组件映射表
|
||||
export const materialComponents: Record<string, Component> = {}
|
||||
|
||||
for (const path in vueModules) {
|
||||
// 从路径中提取组件 ID,例如 './TextEditor/index.vue' => 'TextEditor'
|
||||
const match = path.match(/\.\/(.+)\/index\.vue$/)
|
||||
if (match) {
|
||||
const id = match[1]
|
||||
materialComponents[id] = defineAsyncComponent(vueModules[path] as any)
|
||||
}
|
||||
}
|
||||
|
||||
// 自动构建物料信息列表
|
||||
export const materialList: MaterialInfo[] = []
|
||||
|
||||
for (const path in jsonModules) {
|
||||
// 从路径中提取组件 ID
|
||||
const match = path.match(/\.\/(.+)\/index\.json$/)
|
||||
if (match) {
|
||||
const id = match[1]
|
||||
const config = (jsonModules[path] as any).default || jsonModules[path]
|
||||
materialList.push({ id, ...config })
|
||||
}
|
||||
}
|
||||
|
||||
// 获取物料组件
|
||||
export const getMaterialComponent = (id: string): Component | undefined => {
|
||||
return materialComponents[id]
|
||||
}
|
||||
|
||||
// 获取物料信息
|
||||
export const getMaterialInfo = (id: string): MaterialInfo | undefined => {
|
||||
return materialList.find(m => m.id === id)
|
||||
}
|
||||
@@ -30,6 +30,9 @@ export const useDesignStore = defineStore('design', () => {
|
||||
// 设计组件元数据缓存
|
||||
const componentMetas = ref<DesignComponentMeta[]>([])
|
||||
|
||||
// 自动扫描所有设计组件的 .json 配置文件
|
||||
const designComponentMetaModules = import.meta.glob('../designComponents/*/index.json', { eager: true })
|
||||
|
||||
// 是否已加载
|
||||
const isLoaded = ref(false)
|
||||
|
||||
@@ -45,13 +48,29 @@ export const useDesignStore = defineStore('design', () => {
|
||||
return componentMetas.value.find(m => m.id === selectedComponent.value!.componentId) || null
|
||||
})
|
||||
|
||||
// 加载设计组件元数据
|
||||
// 加载设计组件元数据(从本地文件自动扫描)
|
||||
const loadComponentMetas = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/design-components')
|
||||
if (response.ok) {
|
||||
componentMetas.value = await response.json()
|
||||
const metas: DesignComponentMeta[] = []
|
||||
|
||||
for (const path in designComponentMetaModules) {
|
||||
// 从路径中提取组件 ID,例如 '../designComponents/TextInput/index.json' => 'TextInput'
|
||||
const match = path.match(/\/designComponents\/(.+)\/index\.json$/)
|
||||
if (!match) continue
|
||||
|
||||
const id = match[1]
|
||||
const mod = designComponentMetaModules[path] as any
|
||||
const config = mod.default || mod
|
||||
|
||||
metas.push({
|
||||
id,
|
||||
name: config.name,
|
||||
description: config.description,
|
||||
props: config.props || {}
|
||||
})
|
||||
}
|
||||
|
||||
componentMetas.value = metas
|
||||
} catch (error) {
|
||||
console.error('加载设计组件元数据失败:', error)
|
||||
}
|
||||
@@ -2,9 +2,11 @@ import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { defineAsyncComponent, type Component } from 'vue'
|
||||
import type { MaterialInfo } from '../types'
|
||||
|
||||
// 导入所有物料配置
|
||||
import TextEditorConfig from './TextEditor/index.json'
|
||||
import TreeViewerConfig from './TreeViewer/index.json'
|
||||
import DataTableConfig from './DataTable/index.json'
|
||||
import TestWidget1Config from './TestWidget1/index.json'
|
||||
import TestWidget2Config from './TestWidget2/index.json'
|
||||
import TestWidget3Config from './TestWidget3/index.json'
|
||||
import DesignComponentListConfig from './DesignComponentList/index.json'
|
||||
import DesignCenterConfig from './DesignCenter/index.json'
|
||||
|
||||
// 物料组件映射表
|
||||
export const materialComponents: Record<string, Component> = {
|
||||
TextEditor: defineAsyncComponent(() => import('./TextEditor/index.vue')),
|
||||
TreeViewer: defineAsyncComponent(() => import('./TreeViewer/index.vue')),
|
||||
DataTable: defineAsyncComponent(() => import('./DataTable/index.vue')),
|
||||
TestWidget1: defineAsyncComponent(() => import('./TestWidget1/index.vue')),
|
||||
TestWidget2: defineAsyncComponent(() => import('./TestWidget2/index.vue')),
|
||||
TestWidget3: defineAsyncComponent(() => import('./TestWidget3/index.vue')),
|
||||
DesignComponentList: defineAsyncComponent(() => import('./DesignComponentList/index.vue')),
|
||||
DesignCenter: defineAsyncComponent(() => import('./DesignCenter/index.vue')),
|
||||
}
|
||||
|
||||
// 物料信息列表
|
||||
export const materialList: MaterialInfo[] = [
|
||||
{ id: 'TextEditor', ...TextEditorConfig },
|
||||
{ id: 'TreeViewer', ...TreeViewerConfig },
|
||||
{ id: 'DataTable', ...DataTableConfig },
|
||||
{ id: 'TestWidget1', ...TestWidget1Config },
|
||||
{ id: 'TestWidget2', ...TestWidget2Config },
|
||||
{ id: 'TestWidget3', ...TestWidget3Config },
|
||||
{ id: 'DesignComponentList', ...DesignComponentListConfig },
|
||||
{ id: 'DesignCenter', ...DesignCenterConfig },
|
||||
]
|
||||
|
||||
// 获取物料组件
|
||||
export const getMaterialComponent = (id: string): Component | undefined => {
|
||||
return materialComponents[id]
|
||||
}
|
||||
|
||||
// 获取物料信息
|
||||
export const getMaterialInfo = (id: string): MaterialInfo | undefined => {
|
||||
return materialList.find(m => m.id === id)
|
||||
}
|
||||
23
draggable-panels/src/router.ts
Normal file
23
draggable-panels/src/router.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HelloWorld from './views/HelloWorld.vue'
|
||||
import Designer from './fauto/Designer.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: HelloWorld
|
||||
},
|
||||
{
|
||||
path: '/draggable',
|
||||
name: 'Designer',
|
||||
component: Designer
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
68
draggable-panels/src/views/HelloWorld.vue
Normal file
68
draggable-panels/src/views/HelloWorld.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const msg = ref('欢迎使用拖拽设计器')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hello-world">
|
||||
<h1>{{ msg }}</h1>
|
||||
<div class="card">
|
||||
<p>这是一个基于 Vue3 + TypeScript 的可拖拽子窗口设计器项目</p>
|
||||
<p class="hint">访问 <router-link to="/draggable">/draggable</router-link> 进入设计器</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hello-world {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 2rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 16px;
|
||||
padding: 2rem 3rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.card p {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.8;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 2rem;
|
||||
font-size: 1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.hint a {
|
||||
color: #ffd700;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #ffd700;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.hint a:hover {
|
||||
color: #ffed4e;
|
||||
border-color: #ffed4e;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user