Files
fauto-design/draggable-panels/IMPLEMENTATION_PLAN.md
2025-12-21 20:58:34 +08:00

22 KiB
Raw Blame History

🎯 可视化拖拽设计器 - 整体实施计划

目标将当前的设计器升级为可直接解析和编辑真实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转换

// 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生成算法

// 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. 拖拽事件处理流程

// 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. 模板字符串修改逻辑

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

// 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. 显示修改后的布局 ✅

📊 关键类型定义

// 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<string, any>      // 属性 (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通信


🚀 启动指南(实施后)

# 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组件

让我们开始实施吧! 🚀