# 🎯 可视化拖拽设计器 - 整体实施计划 > **目标**:将当前的设计器升级为可直接解析和编辑真实Vue文件的可视化拖拽编辑器 > **核心技术**:Vue编译时AST转换 + Element Plus布局 + 实时文件修改 > **参考实现**:plugins示例项目的拖拽机制 --- ## 📋 项目总体架构设计 ### **整体技术栈升级** ``` 现有架构 → 目标架构 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 设计组件 (虚拟对象) → 真实Vue文件解析 DesignCenter (模拟预览) → 实时渲染的Vue页面 属性编辑 (内存状态) → 直接修改源文件 拖拽排序 (组件实例) → 拖拽修改el-row/el-col布局 ``` ### **核心功能模块划分** ``` ┌─────────────────────────────────────────────────────────┐ │ 可视化拖拽设计器 │ └─────────────────────────────────────────────────────────┘ │ ├─ 模块1: Vue文件解析引擎 │ ├─ AST解析与遍历 │ ├─ 组件树结构提取 │ └─ 元数据生成 (_material路径ID) │ ├─ 模块2: 编译时增强插件系统 │ ├─ Vite插件配置 │ ├─ Vue模板AST转换 │ ├─ 拖拽指令自动注入 │ └─ 结构化ID生成 │ ├─ 模块3: 运行时拖拽交互系统 │ ├─ 自定义拖拽指令 (v-auto-drag) │ ├─ 拖拽事件处理 (dragstart/drop/over) │ ├─ 元素选中与高亮 │ └─ 滚轮切换父子元素 │ ├─ 模块4: 可视化画布系统 │ ├─ 实时渲染Vue页面 │ ├─ el-row/el-col布局展示 │ ├─ 拖拽预览与占位符 │ └─ 组件边界高亮 │ ├─ 模块5: 源文件修改引擎 │ ├─ 模板字符串操作 │ ├─ 元素位置计算与插入 │ ├─ 代码格式化与清理 │ └─ 文件写入API (后端支持) │ └─ 模块6: 设计器UI增强 ├─ 左侧: 组件树查看器 ├─ 中间: 可视化画布 ├─ 右侧: 属性编辑器 └─ 顶部: 文件选择与工具栏 ``` --- ## 🗂️ 项目文件结构设计 ``` draggable-panels/ ├── src/ │ ├── fauto/ │ │ ├── Designer.vue # 设计器主入口 │ │ │ │ │ ├── components/ │ │ │ ├── Header.vue # ✨新增: 文件选择工具栏 │ │ │ ├── MainLayout.vue # (保持原有) │ │ │ └── Panel.vue # (保持原有) │ │ │ │ │ ├── materials/ │ │ │ ├── VueFileExplorer/ # ✨新增: Vue文件浏览器 │ │ │ │ ├── index.vue │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── ComponentTreeViewer/ # ✨新增: 组件结构树 │ │ │ │ ├── index.vue │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── VisualCanvas/ # ✨新增: 可视化画布 │ │ │ │ ├── index.vue # (实时渲染目标Vue文件) │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── PropertyEditor/ # ✨新增: 属性编辑器 │ │ │ │ ├── index.vue │ │ │ │ └── index.json │ │ │ │ │ │ │ └── (保留原有物料组件...) │ │ │ │ │ ├── designComponents/ # (保留,但不再是主要编辑对象) │ │ │ │ │ ├── core/ # ✨新增: 核心功能模块 │ │ │ ├── parser/ # Vue文件解析器 │ │ │ │ ├── vueParser.ts # SFC解析入口 │ │ │ │ ├── templateParser.ts # template AST解析 │ │ │ │ ├── componentTreeBuilder.ts # 组件树构建 │ │ │ │ └── pathGenerator.ts # 结构化路径生成 │ │ │ │ │ │ │ ├── modifier/ # 源文件修改器 │ │ │ │ ├── templateModifier.ts # 模板修改逻辑 │ │ │ │ ├── elementExtractor.ts # 元素提取与定位 │ │ │ │ ├── codeFormatter.ts # 代码格式化 │ │ │ │ └── fileWriter.ts # 文件写入API封装 │ │ │ │ │ │ │ └── directives/ # 自定义指令 │ │ │ ├── autoDrag.ts # 拖拽指令实现 │ │ │ └── elementSelect.ts # 元素选中指令 │ │ │ │ │ ├── stores/ │ │ │ ├── panelStore.ts # (保留) │ │ │ ├── designStore.ts # (保留,但角色调整) │ │ │ │ │ │ │ ├── vueFileStore.ts # ✨新增: Vue文件状态管理 │ │ │ │ # - 当前打开的Vue文件路径 │ │ │ │ # - 解析后的组件树结构 │ │ │ │ # - 当前选中的元素 │ │ │ │ # - 文件缓存与同步 │ │ │ │ │ │ │ └── canvasStore.ts # ✨新增: 画布交互状态 │ │ │ # - 拖拽状态管理 │ │ │ # - 选中元素路径 │ │ │ # - 悬停目标信息 │ │ │ │ │ └── types/ │ │ ├── index.ts # (保留原有) │ │ ├── vueFile.ts # ✨新增: Vue文件相关类型 │ │ └── canvas.ts # ✨新增: 画布相关类型 │ │ │ └── plugins/ # ✨新增: Vite/Vue插件 │ ├── index.ts # 插件总入口 │ ├── vite/ │ │ └── vueFilePlugin.ts # Vite插件 (文件写入API) │ └── vue/ │ ├── dragPlugin.ts # Vue编译时插件 │ ├── autoDrag.ts # 拖拽指令实现 │ └── dragStyles.css # 拖拽样式 │ ├── vite.config.ts # ✨修改: 集成新插件 ├── package.json # ✨修改: 新增依赖 └── IMPLEMENTATION_PLAN.md # 本文档 ``` --- ## 📦 阶段性实施计划 ### **阶段1: 基础设施搭建** (第1-2天) #### **任务1.1: 安装依赖与环境准备** - [ ] 安装Element Plus (`npm install element-plus`) - [ ] 安装@vue/compiler-sfc (用于AST解析) - [ ] 配置vite.config.ts引入插件系统 - [ ] 创建测试用的TestPage.vue文件 #### **任务1.2: 创建插件系统框架** - [ ] 创建 `src/plugins/` 目录结构 - [ ] 实现 `vite/vueFilePlugin.ts` - 文件写入API中间件 - [ ] 实现 `vue/dragPlugin.ts` - Vue编译时节点转换器 - [ ] 配置vite.config.ts加载插件 **验收标准**: - ✅ Vite能正常启动,插件加载无报错 - ✅ 访问 `/api/write-file` 能收到响应 - ✅ TestPage.vue编译时能看到插件日志 --- ### **阶段2: Vue文件解析引擎** (第3-4天) #### **任务2.1: 实现SFC解析器** - [ ] 创建 `core/parser/vueParser.ts` - 解析Vue单文件组件 - 提取template/script/style - 缓存原始代码 #### **任务2.2: 实现template AST解析** - [ ] 创建 `core/parser/templateParser.ts` - 使用@vue/compiler-dom解析template - 遍历AST节点 - 识别el-row和el-col元素 #### **任务2.3: 实现组件树构建器** - [ ] 创建 `core/parser/componentTreeBuilder.ts` - 递归构建树形结构 - 生成结构化路径ID (r1c2r1c1...) - 提取组件属性信息 #### **任务2.4: 创建vueFileStore** - [ ] 创建 `stores/vueFileStore.ts` - 管理当前打开的Vue文件 - 存储解析后的组件树 - 提供文件加载/保存接口 **验收标准**: - ✅ 能解析TestPage.vue并生成组件树 - ✅ 每个el-row/el-col都有唯一的路径ID - ✅ vueFileStore能正确缓存解析结果 --- ### **阶段3: 编译时增强系统** (第5-6天) #### **任务3.1: 实现路径ID生成算法** - [ ] 创建 `core/parser/pathGenerator.ts` - 实现plugins示例中的路径生成逻辑 - 递归计算父子关系 - 生成r1c2格式的ID #### **任务3.2: 实现Vue编译时转换器** - [ ] 完善 `plugins/vue/dragPlugin.ts` - 在编译时遍历AST - 自动为el-row/el-col注入v-auto-drag指令 - 注入_material属性 #### **任务3.3: 实现拖拽指令** - [ ] 创建 `plugins/vue/autoDrag.ts` - 实现dragstart/dragover/drop事件处理 - 实现元素选中逻辑 - 实现滚轮切换父子元素 - 添加拖拽样式 **验收标准**: - ✅ 编译后的Vue组件自动包含拖拽功能 - ✅ 每个el-col都能被拖拽 - ✅ 点击元素能高亮选中 - ✅ 滚轮能切换选中父子元素 --- ### **阶段4: 可视化画布系统** (第7-8天) #### **任务4.1: 创建VisualCanvas物料组件** - [ ] 创建 `materials/VisualCanvas/index.vue` - 动态加载目标Vue文件 - 使用iframe或动态组件渲染 - 应用拖拽样式 #### **任务4.2: 实现画布交互状态管理** - [ ] 创建 `stores/canvasStore.ts` - 管理拖拽状态(拖拽元素ID、目标位置) - 管理选中状态(当前选中元素路径) - 提供拖拽预览数据 #### **任务4.3: 实现拖拽视觉反馈** - [ ] 创建 `plugins/vue/dragStyles.css` - 拖拽中的半透明效果 - 拖拽进入的高亮边框 - 元素选中的蓝色描边 - 拖拽占位符样式 **验收标准**: - ✅ 画布能正确渲染TestPage.vue - ✅ 拖拽时有清晰的视觉反馈 - ✅ 选中元素有蓝色描边 - ✅ 悬停目标有红色高亮 --- ### **阶段5: 源文件修改引擎** (第9-10天) #### **任务5.1: 实现元素提取与定位** - [ ] 创建 `core/modifier/elementExtractor.ts` - 根据_material ID定位元素 - 提取完整的元素字符串(含子元素) - 计算元素在模板中的位置 #### **任务5.2: 实现模板修改逻辑** - [ ] 创建 `core/modifier/templateModifier.ts` - 移除源元素 - 根据方向插入到目标位置 - 处理嵌套结构的移动 #### **任务5.3: 实现代码格式化与清理** - [ ] 创建 `core/modifier/codeFormatter.ts` - 清除运行时注入的_material属性 - 保持原有代码缩进 - 移除多余的空行 #### **任务5.4: 实现文件写入封装** - [ ] 创建 `core/modifier/fileWriter.ts` - 调用/api/write-file接口 - 错误处理与重试 - 写入成功后刷新页面 **验收标准**: - ✅ 拖拽后能正确修改Vue源文件 - ✅ 修改后的代码格式正确 - ✅ 不会破坏原有代码结构 - ✅ 页面自动刷新显示修改结果 --- ### **阶段6: 设计器UI增强** (第11-12天) #### **任务6.1: 创建VueFileExplorer** - [ ] 创建 `materials/VueFileExplorer/index.vue` - 显示项目中的所有.vue文件 - 点击文件加载到画布 - 显示当前打开的文件路径 #### **任务6.2: 创建ComponentTreeViewer** - [ ] 创建 `materials/ComponentTreeViewer/index.vue` - 展示当前Vue文件的组件树 - 显示el-row/el-col的层级结构 - 点击树节点选中画布中的元素 - 支持拖拽调整顺序(可选) #### **任务6.3: 创建PropertyEditor** - [ ] 创建 `materials/PropertyEditor/index.vue` - 显示选中元素的属性(span、gutter等) - 支持修改属性值 - 实时更新到源文件 #### **任务6.4: 升级Header工具栏** - [ ] 修改 `components/Header.vue` - 添加"打开文件"按钮 - 显示当前文件名 - 添加"保存"、"撤销"按钮(可选) **验收标准**: - ✅ 能浏览并选择Vue文件 - ✅ 组件树能实时同步画布状态 - ✅ 属性编辑器能修改元素属性 - ✅ 工具栏显示当前操作状态 --- ### **阶段7: 功能完善与优化** (第13-14天) #### **任务7.1: 实现撤销/重做功能** - [ ] 创建历史记录栈 - [ ] 实现Ctrl+Z撤销 - [ ] 实现Ctrl+Shift+Z重做 #### **任务7.2: 实现拖拽位置预览** - [ ] 拖拽时显示插入位置占位符 - [ ] 左右方向箭头指示 - [ ] 不合法拖拽的禁止光标 #### **任务7.3: 实现快捷键支持** - [ ] Delete键删除选中元素 - [ ] Ctrl+C/V 复制粘贴(可选) - [ ] 方向键微调位置(可选) #### **任务7.4: 性能优化** - [ ] 大文件解析性能优化 - [ ] 拖拽事件节流 - [ ] 组件树虚拟滚动(如果元素很多) **验收标准**: - ✅ 撤销/重做功能正常 - ✅ 拖拽预览清晰直观 - ✅ 快捷键响应流畅 - ✅ 大文件(100+元素)操作流畅 --- ### **阶段8: 测试与文档** (第15天) #### **任务8.1: 编写单元测试** - [ ] 测试Vue文件解析器 - [ ] 测试路径ID生成算法 - [ ] 测试模板修改逻辑 #### **任务8.2: 编写集成测试** - [ ] 端到端拖拽测试 - [ ] 文件加载与保存测试 - [ ] 多文件切换测试 #### **任务8.3: 更新项目文档** - [ ] 更新PROJECT_CONTEXT.md - [ ] 编写用户使用指南 - [ ] 编写开发者文档 --- ## 🔧 技术实现关键点 ### **1. Vue编译时AST转换** ```typescript // plugins/vue/dragPlugin.ts export function dragNodeTransform(node, context) { // 1. 只处理el-row和el-col元素 if (node.type === 1 && (node.tag === 'el-row' || node.tag === 'el-col')) { // 2. 生成结构化路径ID const materialId = generateStructuredPath(node, context) // 3. 注入v-auto-drag指令 node.props.push({ type: 7, // 指令类型 name: 'auto-drag', exp: undefined, modifiers: [] }) // 4. 注入_material属性 node.props.push({ type: 6, // 属性类型 name: '_material', value: { type: 2, content: materialId } }) } } ``` ### **2. 结构化路径ID生成算法** ```typescript // core/parser/pathGenerator.ts /** * 生成结构化路径ID * 例如: r1c2r1 表示 第1个row -> 第2个col -> 第1个row */ function generateStructuredPath(node, context) { // 1. 递归查找父节点路径 const parent = findParentNode(node, context.root) let parentPath = parent ? generateStructuredPath(parent) : '' // 2. 计算当前节点在同级中的索引 if (node.tag === 'el-row') { const rowIndex = calculateSiblingIndex(node, parent, 'el-row') return `${parentPath}r${rowIndex}` } else if (node.tag === 'el-col') { const colIndex = calculateSiblingIndex(node, parent, 'el-col') return `${parentPath}c${colIndex}` } return parentPath } ``` ### **3. 拖拽事件处理流程** ```typescript // plugins/vue/autoDrag.ts // 拖拽开始 function onDragStart(e, elementId) { __dragElement.value = elementId e.target.classList.add('drag-overlay') } // 拖拽悬停 function onDragOver(e, targetId) { const rect = e.target.getBoundingClientRect() const direction = e.clientX - rect.left < rect.width / 2 ? 'left' : 'right' __dropInfo.value = { targetId, direction } e.target.classList.add('drag-enter') } // 拖拽放置 function onDrop(e, targetId) { // 调用模板修改器 modifyVueTemplate(__dragElement.value, targetId, __dropInfo.value.direction) // 清理状态 cleanupDragState() } ``` ### **4. 模板字符串修改逻辑** ```typescript // core/modifier/templateModifier.ts function modifyTemplate(sourceId, targetId, direction) { // 1. 提取源元素 const sourceElement = extractElementById(template, sourceId) // 2. 从模板中移除源元素 template = template.replace(sourceElement, '') // 3. 查找目标元素位置 const targetPosition = findElementPosition(template, targetId) // 4. 根据方向插入源元素 if (direction === 'left') { template = insertBefore(template, targetPosition, sourceElement) } else { template = insertAfter(template, targetPosition, sourceElement) } // 5. 清理_material属性 template = template.replace(/\s+_material="[^"]*"/g, '') return template } ``` ### **5. 文件写入API** ```typescript // plugins/vite/vueFilePlugin.ts configureServer(server) { server.middlewares.use('/api/write-file', async (req, res) => { const { filePath, content } = await parseBody(req) const fullPath = path.resolve(process.cwd(), filePath) fs.writeFileSync(fullPath, content, 'utf8') res.end(JSON.stringify({ success: true })) }) } ``` --- ## 🎯 核心数据流 ``` 1. 用户打开Vue文件 ↓ 2. vueFileStore.loadFile(path) ↓ 3. vueParser解析SFC → 提取template ↓ 4. templateParser解析AST → 构建组件树 ↓ 5. 组件树显示在ComponentTreeViewer ↓ 6. VisualCanvas渲染Vue文件(编译时已注入拖拽指令) ↓ 7. 用户在画布中拖拽el-col ↓ 8. dragStart → 记录源ID ↓ 9. dragOver → 计算目标ID和方向 ↓ 10. drop → 调用templateModifier ↓ 11. 修改template字符串 ↓ 12. 调用/api/write-file写入文件 ↓ 13. Vite热更新 → 页面自动刷新 ↓ 14. 显示修改后的布局 ✅ ``` --- ## 📊 关键类型定义 ```typescript // types/vueFile.ts export interface VueFileInfo { path: string // 文件路径 name: string // 文件名 content: string // 原始内容 template: string // 模板部分 componentTree: ComponentNode[] // 组件树 lastModified: number // 最后修改时间 } export interface ComponentNode { id: string // 结构化路径ID (r1c2) tag: 'el-row' | 'el-col' // 标签类型 attrs: Record // 属性 (span, gutter等) children: ComponentNode[] // 子节点 parent: ComponentNode | null // 父节点引用 depth: number // 深度(用于树形展示) } // types/canvas.ts export interface DragState { dragElementId: string | null // 正在拖拽的元素ID targetElementId: string | null // 目标元素ID direction: 'left' | 'right' | null // 插入方向 isDragging: boolean // 是否正在拖拽 } export interface SelectState { selectedElementId: string | null // 选中的元素ID selectedElementPath: ComponentNode[] // 选中元素的父级路径 hoverElementId: string | null // 鼠标悬停的元素ID } ``` --- ## ⚠️ 技术难点与解决方案 ### **难点1: AST节点父级查找** **问题**:Vue编译时AST节点没有parent引用 **解决**:递归遍历根节点,构建节点关系映射表 ### **难点2: 字符串操作的准确性** **问题**:正则匹配可能误删嵌套元素 **解决**:使用深度计数器,精确匹配开始/结束标签 ### **难点3: 热更新与拖拽状态冲突** **问题**:文件修改后页面刷新,拖拽状态丢失 **解决**:将拖拽状态保存到sessionStorage,刷新后恢复 ### **难点4: 大文件性能问题** **问题**:100+元素的Vue文件解析缓慢 **解决**:使用Web Worker后台解析,分片渲染组件树 ### **难点5: 跨iframe通信** **问题**:如果画布使用iframe,事件监听复杂 **解决**:使用动态组件代替iframe,或通过postMessage通信 --- ## 🚀 启动指南(实施后) ```bash # 1. 安装依赖 npm install # 2. 启动开发服务器 npm run dev # 3. 访问设计器 http://localhost:5173/draggable # 4. 操作流程 - 点击"窗口" → 打开"Vue文件浏览器"(左侧) - 点击"窗口" → 打开"可视化画布"(中间) - 点击"窗口" → 打开"组件树查看器"(左侧) - 在文件浏览器中选择TestPage.vue - 在画布中拖拽el-col调整布局 - 查看源文件自动更新 ``` --- ## 📝 后续可扩展功能 ### **功能增强** 1. **组件库集成** - 支持从Element Plus组件库拖拽添加组件 - 不仅限于el-row/el-col,支持所有组件 2. **多文件项目管理** - 项目文件树浏览 - 多文件同时编辑(Tab切换) - 文件依赖关系分析 3. **智能布局** - 自动计算span总和 - 响应式布局预览(xs/sm/md/lg) - 对齐辅助线 4. **团队协作** - Git集成(提交/拉取) - 实时协作编辑(WebSocket) - 版本历史对比 ### **开发体验** 1. **代码生成** - 自动生成配套的script代码 - 自动导入组件 - 自动生成数据绑定 2. **预设模板** - 常用布局模板库 - 一键应用模板 - 自定义模板保存 --- ## ✅ 验收清单(最终) - [ ] 能浏览并选择项目中的.vue文件 - [ ] 能在画布中实时渲染Vue文件 - [ ] 能通过拖拽调整el-row/el-col布局 - [ ] 拖拽操作能正确修改源文件 - [ ] 文件修改后页面自动刷新 - [ ] 点击元素能高亮选中 - [ ] 滚轮能切换父子元素选中 - [ ] 组件树能实时同步画布状态 - [ ] 属性编辑器能修改元素属性 - [ ] 支持撤销/重做操作 - [ ] 所有操作流畅无明显卡顿 - [ ] 代码格式保持规范 --- ## 🎉 总结 本计划将分**8个阶段、15天**完成,核心思路是: 1. **编译时增强**:通过Vue编译器插件自动注入拖拽能力 2. **运行时交互**:通过自定义指令实现拖拽逻辑 3. **字符串操作**:精确修改模板字符串 4. **文件同步**:通过Vite中间件实现文件写入 相比于传统的虚拟组件设计器,这种方案的优势是: - ✅ **所见即所得**:直接编辑真实的Vue文件 - ✅ **零学习成本**:开发者无需学习DSL或JSON配置 - ✅ **完全可控**:生成的代码就是手写的代码 - ✅ **灵活扩展**:可以支持任意Vue组件 **让我们开始实施吧!** 🚀