diff --git a/draggable-panels/PROJECT_CONTEXT.md b/draggable-panels/PROJECT_CONTEXT.md index 081736e..949179c 100644 --- a/draggable-panels/PROJECT_CONTEXT.md +++ b/draggable-panels/PROJECT_CONTEXT.md @@ -1,20 +1,22 @@ -# 拖拽面板设计器 - 项目上下文总结 +# Vue页面可视化设计器 - 项目上下文 -> **用途**:用于在新电脑快速恢复 AI 协作上下文 -> **生成时间**:2025-12-20 -> **项目路径**:`/Volumes/mypan/ui/draggable-panels` +> **用途**:用于在新环境快速恢复 AI 协作上下文 +> **更新时间**:2025-12-22 +> **项目路径**:`d:/workspace/fauto-design/draggable-panels` --- ## 📋 项目概述 -这是一个基于 **Vue3 + TypeScript + Vite** 的**可拖拽面板设计器**项目,类似 IDE 界面(IDEA/VS Code)的多面板布局,支持: +这是一个基于 **Vue3 + TypeScript + Vite + Element Plus** 的**可视化页面设计器**,通过拖拽操作直接编辑真实的Vue页面文件。 -- 🎯 三栏面板布局(左/中/右可调整宽度) -- 🎨 Tab 可拖拽、跨面板移动 -- 🧩 物料组件系统(自动扫描注册) -- 🎭 设计组件系统(可视化编辑、属性绑定) -- 💾 状态持久化(通过模拟 API) +### 核心特性 + +1. **直接解析Vue文件** - 动态扫描并渲染`src/views`下的页面 +2. **拖拽式设计** - 将设计组件拖拽到页面的el-row/el-col上 +3. **非侵入式交互** - 通过注入方式绑定事件,不修改页面代码 +4. **智能层级选择** - 键盘方向键切换嵌套元素层级 +5. **实时视觉反馈** - 拖拽预览 + 拖放区域显示(上/下/左/右) --- @@ -23,304 +25,281 @@ ``` draggable-panels/ ├── src/ -│ ├── fauto/ # 🔥 设计器核心代码(所有功能都在这里) -│ │ ├── Designer.vue # 设计器主入口页面 -│ │ ├── components/ # 设计器基础组件 -│ │ │ ├── Header.vue # 顶部菜单栏(打开项目/窗口菜单) -│ │ │ ├── Footer.vue # 底部状态栏 -│ │ │ ├── MainLayout.vue # 三栏布局容器 -│ │ │ ├── Panel.vue # 单个面板(含 Tab 列表 + 内容区) -│ │ │ └── Resizer.vue # 面板间拖拽分割线 +│ ├── fauto/ # 🔥 设计器核心代码 +│ │ ├── Designer.vue # 设计器主入口 │ │ │ -│ │ ├── materials/ # 🎁 物料组件(8个) -│ │ │ ├── index.ts # ✅ 自动扫描所有 */index.vue 和 */index.json -│ │ │ ├── DesignComponentList/ # 可选设计组件列表 -│ │ │ ├── TreeViewer/ # 树形结构查看器(已添加实例列表) -│ │ │ ├── DesignCenter/ # 设计中心(组件预览面板) -│ │ │ ├── DataTable/ # 属性表格编辑器 -│ │ │ ├── TextEditor/ # 文本编辑器(支持状态持久化) -│ │ │ ├── TestWidget1/2/3/ # 测试组件 -│ │ │ └── [每个物料包含] -│ │ │ ├── index.vue # 组件实现 -│ │ │ └── index.json # 组件元数据(name/description) +│ │ ├── components/ # 基础UI组件 +│ │ │ ├── Header.vue # 顶部菜单栏 +│ │ │ ├── Footer.vue # 底部状态栏(显示拖拽状态) +│ │ │ ├── MainLayout.vue # 三栏布局容器 +│ │ │ ├── Panel.vue # 面板容器 +│ │ │ └── Resizer.vue # 面板分隔器 │ │ │ -│ │ ├── designComponents/ # 🎨 设计组件(3个) -│ │ │ ├── TextInput/ # 文本输入框 -│ │ │ ├── RadioSelect/ # 单选器 -│ │ │ ├── GridTable/ # 表格 -│ │ │ └── [每个设计组件包含] -│ │ │ ├── index.vue # Vue 组件定义 -│ │ │ └── index.json # 元数据(name/description/props) +│ │ ├── materials/ # 🎁 物料组件系统 +│ │ │ ├── PageManagement/ # 页面管理(树形选择Vue文件) +│ │ │ ├── DesignComponentList/ # 设计组件列表 +│ │ │ └── DesignCenter/ # 设计中心(动态渲染页面) +│ │ │ ├── index.vue # 主组件 +│ │ │ ├── InteractiveWrapper.vue # 交互包装器(注入事件) +│ │ │ ├── DropZone.vue # 拖放区域指示器 +│ │ │ └── DragPreview.vue # 拖拽预览 │ │ │ -│ │ ├── stores/ # 🗄️ Pinia 状态管理 -│ │ │ ├── panelStore.ts # 面板布局 + 物料状态管理 -│ │ │ └── designStore.ts # 设计组件实例管理(✅ 本地自动扫描) +│ │ ├── designComponents/ # 🎨 设计组件库 +│ │ │ ├── TextInput/ # 文本输入框 +│ │ │ ├── RadioSelect/ # 单选器 +│ │ │ └── GridTable/ # 表格 │ │ │ -│ │ └── types/ # 📝 TypeScript 类型定义 -│ │ └── index.ts # Panel/TabItem/MaterialInfo 等 +│ │ ├── plugins/ # 🔌 插件系统(核心功能) +│ │ │ ├── index.ts # 统一导出 +│ │ │ ├── interactionStore.ts # 全局交互事件钩子 +│ │ │ ├── dragStore.ts # 拖拽状态管理(层级选择) +│ │ │ └── pathUtils.ts # 结构化路径工具 +│ │ │ +│ │ ├── stores/ # 🗄️ Pinia状态管理 +│ │ │ ├── panelStore.ts # 面板布局状态 +│ │ │ ├── designStore.ts # 设计组件元数据 +│ │ │ └── vueFileStore.ts # Vue文件选择状态 +│ │ │ +│ │ └── types/ # 📝 类型定义 │ │ -│ ├── views/ # 普通视图 -│ │ └── HelloWorld.vue # 首页欢迎页 +│ ├── views/ # 📄 示例页面 +│ │ ├── TestPage1.vue # 测试页面1 +│ │ ├── TestPage2.vue # 测试页面2 +│ │ ├── user/Profile.vue # 用户资料页 +│ │ └── dashboard/Overview.vue # 仪表板 │ │ -│ ├── router.ts # 🛤️ 路由配置(/ 和 /draggable) -│ ├── main.ts # 应用入口 -│ ├── App.vue # 根组件(仅路由容器) -│ └── style.css # 全局样式 +│ ├── router.ts # 路由配置 +│ ├── main.ts # 应用入口 +│ ├── App.vue # 根组件 +│ └── style.css # 全局样式 │ -├── package.json # 依赖配置 -└── vite.config.ts # Vite 配置 +└── vite.config.ts # Vite配置 ``` --- ## 🔑 核心技术要点 -### 1. **路由设计** +### 1. **Vue页面规范** ⭐ -```typescript -// src/router.ts -'/' → HelloWorld 欢迎页 -'/draggable' → Designer 设计器主页面(fauto/Designer.vue) +**强制规则**:template的第一层级**有且仅有一个el-row** + +**示例**: +```vue + ``` -### 2. **物料组件自动注册** ⭐ +**目的**:保证布局可解析性,方便自动生成结构化路径ID -**文件**:`src/fauto/materials/index.ts` +### 2. **结构化路径ID** ⭐ + +**格式**:`r{n}c{m}r{x}c{y}...` +- `r`: el-row +- `c`: el-col +- 数字:同级索引(从1开始) + +**示例**:`r1c2r1c3` 表示: +- 第1个el-row → 第2个el-col → 第1个el-row → 第3个el-col + +**实现**:`fauto/plugins/pathUtils.ts` + +### 3. **交互事件注入** ⭐ + +**InteractiveWrapper.vue** 核心流程: ```typescript -// ✅ 使用 import.meta.glob 自动扫描 -const vueModules = import.meta.glob('./*/index.vue') -const jsonModules = import.meta.glob('./*/index.json', { eager: true }) +// 1. 动态渲染组件 + -// 自动构建组件映射表和信息列表 -export const materialComponents: Record = {} -export const materialList: MaterialInfo[] = [] +// 2. 挂载后扫描DOM +const injectInteractionEvents = () => { + const rows = containerRef.value.querySelectorAll('.el-row') + const cols = containerRef.value.querySelectorAll('.el-col') + + // 3. 为每个元素注入事件 + rows.forEach((row) => { + const path = generateElementPath(row) // 生成路径ID + bindElementEvents(row, 'er', path) // 绑定事件 + }) +} + +// 4. 使用MutationObserver监听DOM变化 +observer.observe(containerRef.value, { childList: true, subtree: true }) ``` -**新增物料步骤**: -1. 在 `materials/` 下创建新目录(如 `MyWidget/`) -2. 添加 `index.vue`(组件实现) -3. 添加 `index.json`(元数据) -4. **无需修改任何注册代码**,自动生效! +### 4. **层级选择机制** ⭐ -### 3. **设计组件自动加载** ⭐ +**核心思想**:当鼠标悬停在嵌套元素上时(如`r1c1r1`),会同时触发`r1c1r1`、`r1c1`、`r1`三个层级。 -**最近修复**(2025-12-20): +**解决方案**(`dragStore.ts`): -**designStore.ts** - 从远程 API 改为本地扫描: ```typescript -// ✅ 使用 eager: true 同步加载 -const designComponentMetaModules = import.meta.glob('../designComponents/*/index.json', { eager: true }) - -// 自动构建元数据列表 -const loadComponentMetas = async () => { - const metas: DesignComponentMeta[] = [] - for (const path in designComponentMetaModules) { - const id = match[1] // 如 'TextInput' - metas.push({ id, name, description, props }) +// 1. 收集所有层级节点 +const updateHierarchy = (element: HTMLElement) => { + const nodes = [] + let current = element + + while (current) { + if (current.classList.contains('el-row') || current.classList.contains('el-col')) { + nodes.push({ path, type, element, depth }) + } + current = current.parentElement } - componentMetas.value = metas + + // 按深度排序,最深的在前 + nodes.sort((a, b) => b.depth - a.depth) +} + +// 2. 键盘切换层级 +↑ 键 → selectParentLevel() // 切换到父级 +↓ 键 → selectChildLevel() // 切换到子级 +Esc → cancelDrag() // 取消拖拽 +``` + +### 5. **拖拽交互优化** ⭐ + +#### 禁用文本选择 +```typescript +// DragPreview.vue +watch(() => dragStore.isDragging, (isDragging) => { + if (isDragging) { + document.body.classList.add('is-dragging') + } else { + document.body.classList.remove('is-dragging') + } +}) +``` + +```css +/* style.css */ +body.is-dragging { + user-select: none !important; + cursor: grabbing !important; } ``` -**DesignCenter.vue** - 组件预览渲染: -```typescript -// ✅ 使用 eager: true 确保同步加载 -const designComponentModules = import.meta.glob('../../designComponents/*/index.vue', { eager: true }) - -// 构建组件映射(用于 ) -const designComponentMap: Record = {} -for (const path in designComponentModules) { - const mod = designComponentModules[path] as any - designComponentMap[id] = markRaw(mod.default || mod) -} -``` - -### 4. **状态同步机制** ⭐ - -**TreeViewer.vue** - 实例列表实时同步(最近修复): -```typescript -// ✅ 改用 watch 替代 $subscribe(更稳定) -watch( - () => designStore.components, - (newVal) => { - localComponents.value = [...newVal] - }, - { deep: true, immediate: true } -) -``` - -### 5. **状态持久化** - -- **panelStore**:管理面板布局 + 物料组件状态 - - `loadConfig()` / `saveConfig()` → `/api/config` - - `getMaterialState()` / `updateMaterialState()` → `/api/material-states` - -- **designStore**:管理设计组件实例 - - `loadState()` / `saveState()` → `/api/design-state` - -**注意**:目前使用 `fetch('/api/...')` 模拟持久化,实际会失败但不影响运行。如需真实持久化,可改用 localStorage 或搭建后端。 +#### 拖拽预览效果 +- 跟随鼠标的半透明卡片 +- 显示组件图标 + 名称 +- Teleport到body避免z-index问题 --- ## 🎯 数据流图 ``` -用户操作 - ↓ -┌─────────────────────────────────────────────────┐ -│ Header.vue - 点击"窗口"菜单 │ -│ → panelStore.openMaterial('TreeViewer') │ -└─────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────┐ -│ Panel.vue - 渲染 Tab + 加载物料组件 │ -│ → │ -└─────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────┐ -│ DesignComponentList - 点击"文本输入框" │ -│ → designStore.addComponent('TextInput') │ -└─────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────┐ -│ TreeViewer - watch 监听 components 变化 │ -│ → 自动刷新实例列表 │ -└─────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────┐ -│ DesignCenter - 渲染组件预览 │ -│ → │ -└─────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────┐ -│ DataTable - 编辑属性 │ -│ → designStore.updateComponentProps(id, key, val)│ -└─────────────────────────────────────────────────┘ +1. 页面管理 + 用户点击Vue文件 + ↓ + vueFileStore.selectFile(path) + ↓ + DesignCenter动态渲染该页面 + ↓ + InteractiveWrapper注入交互事件 + +2. 拖拽交互 + 用户拖拽设计组件 + ↓ + dragStore.startDragFromComponentList(id, name) + ↓ + 鼠标移动到页面元素上 + ↓ + dragStore.updateHierarchy(element) // 识别层级 + ↓ + 用户按↑↓键切换层级 + ↓ + DropZone显示可放置区域(上/下/左/右) + ↓ + 用户点击区域确认 + ↓ + dragStore.confirmDrop() + ↓ + Footer显示拖放记录:TextInput → r1c1 左侧 ``` --- -## 🐛 已解决的关键问题 +## 🛠️ 快速开始 -### 问题 1:设计组件列表不显示 -**时间**:2025-12-20 -**原因**:`designStore.loadComponentMetas()` 使用 `fetch('/api/design-components')`,无后端导致 `componentMetas` 为空 -**解决**:改用 `import.meta.glob` 自动扫描本地 `designComponents/*/index.json` - -### 问题 2:TreeViewer 列表不更新 -**原因**:使用 Pinia 的 `$subscribe` 监听 mutation,逻辑复杂且不稳定 -**解决**:改用 Vue 的 `watch(() => designStore.components)` 直接监听 - -### 问题 3:DesignCenter 组件不渲染 -**原因**:`import.meta.glob` 未设置 `eager: true`,导致组件映射表构建失败 -**解决**:添加 `{ eager: true }` 并使用 `mod.default || mod` 获取组件定义 - ---- - -## 📦 依赖清单 - -```json -{ - "dependencies": { - "vue": "^3.5.24", // Vue 3 框架 - "vue-router": "^4.6.4", // 路由管理 - "pinia": "^3.0.4", // 状态管理 - "vuedraggable": "^4.1.0" // 拖拽功能 - }, - "devDependencies": { - "vite": "^7.2.4", // 构建工具 - "typescript": "~5.9.3", // TypeScript - "@vitejs/plugin-vue": "^6.0.1" - } -} +### 访问设计器 +``` +http://localhost:5173/draggable ``` +### 使用流程 +1. 点击左侧"页面管理"物料 +2. 在树形列表中选择一个Vue页面 +3. 页面在"设计中心"中渲染 +4. 从"设计组件列表"拖拽组件 +5. 移动到页面的el-row或el-col上 +6. 使用↑↓键切换层级 +7. 点击放置方向(上/下/左/右) +8. 查看Footer中的拖放记录 + --- -## 🚀 启动命令 +## 📝 开发规范 -```bash -# 1. 安装依赖 -npm install - -# 2. 启动开发服务器 -npm run dev -# 访问 http://localhost:5173/draggable - -# 3. 构建生产版本 -npm run build +### 创建符合规范的Vue页面 +```vue + ``` ---- +### 添加设计组件 +1. 在`src/fauto/designComponents/`下创建目录 +2. 添加`index.vue`和`index.json` +3. 自动在列表中显示 -## 📝 后续可优化方向 - -### 🔧 功能增强 -1. **真实持久化** - - 选项 A:改用 `localStorage` 替代模拟 API - - 选项 B:搭建 Node.js 后端(Express + fs.writeFile) - -2. **拖拽增强** - - 支持面板间 Tab 跨级拖动 - - 树形组件支持父子节点拖拽 - -3. **设计器功能** - - 实现拖拽生成 Vue 文件(模板引擎) - - 接入 Element Plus 组件库作为设计组件源 - -4. **开发体验** - - 添加 ESLint + Prettier 代码规范 - - 添加单元测试(Vitest) - -### 🎨 界面优化 -- 添加主题切换(暗色/亮色) -- 面板大小记忆功能 -- 快捷键支持(Ctrl+S 保存等) +### 插件开发 +所有与页面解析、交互相关的代码放在`src/fauto/plugins/` --- -## 💡 给 AI 的关键提示 +## 🚀 下一步计划 -### 如果遇到"自动扫描不生效" -1. 检查是否添加了 `{ eager: true }` 选项 -2. 确认路径匹配正则是否正确(`/designComponents\/(.+)\/index\.vue$/`) -3. 验证文件夹下是否有 `index.vue` 和 `index.json` - -### 如果组件不渲染 -1. 检查 `import.meta.glob` 是否使用 `eager: true` -2. 确认使用 `mod.default || mod` 获取组件 -3. 对于动态组件,确保使用 `markRaw()` 包装 - -### 如果状态不同步 -1. 优先使用 Vue 的 `watch` 而非 Pinia 的 `$subscribe` -2. 设置 `{ deep: true, immediate: true }` 确保深度监听 -3. 使用 `[...array]` 创建新数组触发响应式更新 +1. **拖放后修改源文件** - 通过API将操作写入.vue文件 +2. **属性编辑器** - 展示和编辑组件属性 +3. **热更新同步** - 修改后自动刷新页面 +4. **撤销/重做** - 操作历史记录 --- -## 📞 联系方式 +## 📚 相关文档 -**项目负责人**:[你的信息] -**Git 仓库**:[如果有的话] -**文档更新**:2025-12-20 +- **详细设计文档**:`项目设计文档.md` +- **技术选型**:Vue3 + TypeScript + Element Plus + Pinia +- **构建工具**:Vite 7.3.0 --- -## ✅ 快速检查清单(给新 AI) +## 💡 核心创新点 -当你接手这个项目时,请确认以下内容: - -- [ ] 项目已正常启动(`npm run dev`) -- [ ] 访问 `/draggable` 能看到设计器界面 -- [ ] 点击"窗口"菜单能打开物料组件 -- [ ] "设计组件列表"显示 3 个组件(TextInput/RadioSelect/GridTable) -- [ ] 点击设计组件后,TreeViewer 能显示新实例 -- [ ] DesignCenter 能正确渲染组件预览 -- [ ] DataTable 能编辑组件属性 - -如果以上任一项失败,请参考"已解决的关键问题"章节。 +1. **非侵入式设计** - 无需修改Vue页面代码即可实现拖拽设计 +2. **智能层级选择** - 自动识别嵌套结构,精准定位目标位置 +3. **实时视觉反馈** - 完善的拖拽交互体验 +4. **结构化路径** - 人类可读的元素定位方式 --- -**🎉 恭喜!你已经完全掌握了这个项目的上下文,可以继续开发了!** +**最后更新**:2025-12-22 +**AI协作建议**:优先阅读本文档的"核心技术要点"部分,理解项目的设计理念和实现方式 diff --git a/draggable-panels/config.json b/draggable-panels/config.json index e646ee0..baff7fa 100644 --- a/draggable-panels/config.json +++ b/draggable-panels/config.json @@ -16,7 +16,7 @@ "materialId": "DesignComponentList" } ], - "activeTabId": "up60643" + "activeTabId": "6hfm9ux" }, "centerPanel": { "id": "center", @@ -141,5 +141,5 @@ "activeTabId": "mxfx11j" } }, - "lastUpdated": "2025-12-22T14:24:28.202Z" + "lastUpdated": "2025-12-22T15:06:06.209Z" } \ No newline at end of file diff --git a/draggable-panels/design-state.json b/draggable-panels/design-state.json index 75eb80c..37d0db3 100644 --- a/draggable-panels/design-state.json +++ b/draggable-panels/design-state.json @@ -67,5 +67,5 @@ } ], "selectedId": "jy87mdv", - "lastUpdated": "2025-12-22T14:24:28.783Z" + "lastUpdated": "2025-12-22T14:42:41.050Z" } \ No newline at end of file diff --git a/draggable-panels/src/fauto/components/Footer.vue b/draggable-panels/src/fauto/components/Footer.vue index 3f9db80..03da9eb 100644 --- a/draggable-panels/src/fauto/components/Footer.vue +++ b/draggable-panels/src/fauto/components/Footer.vue @@ -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(() => { 🎯 {{ dragInfo }} - - ➡️ {{ dropTargetInfo }} - - - 📍 {{ dragStore.hoverDirection === 'top' ? '上方' : dragStore.hoverDirection === 'bottom' ? '下方' : dragStore.hoverDirection === 'left' ? '左侧' : '右侧' }} + + 👁️ {{ hoverInfo }} diff --git a/draggable-panels/src/fauto/materials/DesignCenter/InteractiveWrapper.vue b/draggable-panels/src/fauto/materials/DesignCenter/InteractiveWrapper.vue index e0d357e..92b6335 100644 --- a/draggable-panels/src/fauto/materials/DesignCenter/InteractiveWrapper.vue +++ b/draggable-panels/src/fauto/materials/DesignCenter/InteractiveWrapper.vue @@ -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) diff --git a/draggable-panels/src/fauto/materials/DesignCenter/index.vue b/draggable-panels/src/fauto/materials/DesignCenter/index.vue index 540a7da..ea23e94 100644 --- a/draggable-panels/src/fauto/materials/DesignCenter/index.vue +++ b/draggable-panels/src/fauto/materials/DesignCenter/index.vue @@ -1,42 +1,11 @@