This commit is contained in:
2025-12-20 23:16:23 +08:00
parent 2d2bd98c47
commit f858f69daa
43 changed files with 476 additions and 99 deletions

View File

@@ -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>

View 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>

View File

@@ -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) => {

View File

@@ -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 = () => {

View 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)
}

View File

@@ -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)
}

View File

@@ -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')

View File

@@ -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)
}

View 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

View 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>