Files
fauto-design/DEVELOPMENT.md
2026-01-20 22:07:05 +08:00

12 KiB
Raw Permalink Blame History

Fauto Design - 开发指南

本文档为开发者提供项目的开发规范、扩展指南和最佳实践。


目录

  1. 开发环境配置
  2. 代码规范
  3. Vue页面规范
  4. 设计组件开发
  5. 物料组件开发
  6. 插件开发
  7. 后端服务扩展
  8. 常见问题与经验

1. 开发环境配置

1.1 前端环境

cd draggable-panels
npm install
npm run dev

开发服务器:http://localhost:5173
设计器入口:http://localhost:5173/draggable

1.2 后端环境

cd vue-template-service
npm install
node src/index.js

API服务http://localhost:3001

1.3 推荐IDE配置

  • VS Code + Volar 扩展
  • 启用 TypeScript 严格模式
  • 配置 ESLint + Prettier

2. 代码规范

2.1 TypeScript规范

// ✅ 正确:明确定义类型
interface UserInfo {
  id: string
  name: string
  age: number
}

const user: UserInfo = { id: '1', name: 'Tom', age: 18 }

// ❌ 错误使用any
const user: any = { ... }

2.2 Vue组件规范

<script setup lang="ts">
// 1. 导入语句
import { ref, computed, onMounted } from 'vue'
import type { PropType } from 'vue'
import { useStore } from '../stores/xxx'

// 2. Props定义必须有类型
const props = defineProps<{
  title: string
  count?: number
}>()

// 3. Emits定义
const emit = defineEmits<{
  (e: 'update', value: string): void
  (e: 'close'): void
}>()

// 4. 响应式状态
const loading = ref(false)
const data = ref<DataItem[]>([])

// 5. 计算属性
const isEmpty = computed(() => data.value.length === 0)

// 6. 方法
const fetchData = async () => { ... }

// 7. 生命周期
onMounted(() => { ... })
</script>

<template>
  <!-- 模板内容 -->
</template>

<style scoped>
/* 使用scoped避免样式污染 */
</style>

2.3 文件命名规范

类型 规范 示例
Vue组件 PascalCase UserProfile.vue
TypeScript文件 camelCase dragStore.ts
目录 camelCase/PascalCase designComponents/
JSON配置 camelCase index.json

2.4 目录职责

draggable-panels/src/fauto/
├── components/       # 基础UI组件布局相关
├── materials/        # 物料组件(可拖拽到面板的功能组件)
├── designComponents/ # 设计组件(可拖拽到页面的业务组件)
├── plugins/          # 插件系统(交互、拖拽、路径等核心逻辑)
├── stores/           # Pinia状态管理
└── types/            # 类型定义

3. Vue页面规范

3.1 结构要求

强制规则template第一层级有且仅有一个el-row

<template>
  <!--  正确单一el-row根元素 -->
  <el-row :gutter="20">
    <el-col :span="12">...</el-col>
    <el-col :span="12">...</el-col>
  </el-row>
</template>
<template>
  <!--  错误多个根元素 -->
  <el-row>...</el-row>
  <el-row>...</el-row>
</template>

3.2 设计组件标识

页面中的设计组件需要 data-component 属性:

<el-col :span="12">
  <div class="design-component" data-component="输入框">
    <el-form-item label="用户名">
      <el-input v-model="username" />
    </el-form-item>
  </div>
</el-col>

关键点

  • data-component 的值必须与设计组件的 name 一致
  • 用于元数据面板识别组件类型并加载对应的属性schema

3.3 结构化路径

系统会自动为el-row/el-col生成路径ID

路径 含义
r1 第1个el-row
r1c2 第1个el-row的第2个el-col
r1c2r1 第1个row → 第2个col → 第1个row
r1c2r1c3 更深层嵌套...

4. 设计组件开发

4.1 创建步骤

  1. draggable-panels/src/fauto/designComponents/ 创建目录
  2. 添加 index.json 配置文件
  3. 添加 template.html 模板文件

4.2 配置文件 (index.json)

{
  "id": "MyInput",
  "name": "我的输入框",
  "icon": "✏️",
  "description": "自定义输入组件",
  "defaultSpan": 12,
  "metadata": {
    "span": {
      "label": "宽度",
      "type": "number",
      "min": 1,
      "max": 24,
      "target": "el-col",
      "attr": ":span"
    },
    "label": {
      "label": "标签",
      "type": "text",
      "target": "el-form-item",
      "attr": "label"
    },
    "placeholder": {
      "label": "占位符",
      "type": "text",
      "target": "el-input",
      "attr": "placeholder"
    },
    "disabled": {
      "label": "禁用",
      "type": "boolean",
      "target": "el-input",
      "attr": "disabled"
    },
    "size": {
      "label": "尺寸",
      "type": "select",
      "options": ["", "large", "default", "small"],
      "target": "el-input",
      "attr": "size"
    },
    "textColor": {
      "label": "文字颜色",
      "type": "color",
      "target": "el-input",
      "attr": "text-color"
    }
  }
}

4.3 元数据类型

type 说明 渲染控件
number 数字 数字输入框
text 文本 文本输入框
select 选择 下拉选择框
boolean 布尔 开关/复选框
color 颜色 颜色选择器
columns 列配置 (暂不支持)

4.4 模板文件 (template.html)

<el-col :span="12">
  <div class="design-component design-my-input" data-component="我的输入框">
    <el-form-item label="标签">
      <el-input v-model="value" placeholder="请输入"></el-input>
    </el-form-item>
  </div>
</el-col>

模板规范

  • 外层必须是 el-col
  • 内层 div 必须有 class="design-component"data-component
  • data-component 值与 index.jsonname 一致

4.5 扩展元数据类型

如需添加新的元数据类型:

  1. designStore.tsMetadataField.type 添加类型
  2. DataTable/index.vue 添加对应的编辑控件
// designStore.ts
export interface MetadataField {
  type: 'text' | 'number' | 'select' | 'boolean' | 'color' | 'myNewType'
  // ...
}
<!-- DataTable/index.vue -->
<template v-else-if="field.type === 'myNewType'">
  <MyNewTypeEditor :value="localValues[key]" @change="handleChange" />
</template>

5. 物料组件开发

5.1 创建步骤

  1. draggable-panels/src/fauto/materials/ 创建目录
  2. 添加 index.vue 组件文件
  3. 添加 index.json 配置文件
  4. materials/index.ts 注册

5.2 组件文件 (index.vue)

<script setup lang="ts">
import config from './index.json'

// 组件逻辑
</script>

<template>
  <div class="my-material">
    <div class="material-header">
      <span class="title">{{ config.name }}</span>
    </div>
    <div class="material-body">
      <!-- 物料组件内容 -->
    </div>
  </div>
</template>

<style scoped>
.my-material {
  height: 100%;
  display: flex;
  flex-direction: column;
}
</style>

5.3 配置文件 (index.json)

{
  "id": "MyMaterial",
  "name": "我的物料",
  "icon": "🔧",
  "description": "物料组件描述",
  "category": "tool",
  "defaultPanel": "left"
}

5.4 注册物料

// materials/index.ts
export { default as MyMaterial } from './MyMaterial/index.vue'

export const materialConfigs = {
  // ...
  MyMaterial: () => import('./MyMaterial/index.json')
}

6. 插件开发

6.1 插件位置

所有与交互、拖拽、路径相关的核心逻辑放在 draggable-panels/src/fauto/plugins/

6.2 创建新插件

// plugins/myPlugin.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useMyPlugin = defineStore('myPlugin', () => {
  // 状态
  const state = ref<MyState>(initialState)
  
  // 计算属性
  const derivedValue = computed(() => ...)
  
  // 方法
  const doSomething = () => { ... }
  
  return {
    state,
    derivedValue,
    doSomething
  }
})

6.3 统一导出

// plugins/index.ts
export { useMyPlugin } from './myPlugin'

7. 后端服务扩展

7.1 添加新API

// vue-template-service/src/index.js
app.post('/api/my-endpoint', async (req, res) => {
  try {
    const { param1, param2 } = req.body
    
    // 业务逻辑
    const result = await myService(param1, param2)
    
    res.json({ success: true, data: result })
  } catch (error) {
    console.error('[API] 错误:', error)
    res.status(500).json({ success: false, error: error.message })
  }
})

7.2 添加新服务函数

// vue-template-service/src/services/templateService.js
export function myNewFunction(vueContent, options) {
  try {
    // 1. 解析Vue文件
    const sfcResult = parseSFC(vueContent)
    const templateBlock = sfcResult.descriptor.template
    
    // 2. 解析template AST
    const ast = parseTemplate(templateBlock.content, {
      comments: true,
      whitespace: 'preserve'
    })
    
    // 3. 处理逻辑
    // ...
    
    return { success: true, data: ... }
  } catch (error) {
    return { success: false, error: error.message }
  }
}

8. 常见问题与经验

8.1 拖拽相关

两阶段拖拽

拖拽分为源选择和目标选择两个阶段,避免误操作:

// dragStore.ts
dragPhase: 'source' | 'target'

// 源选择阶段:收集层级节点
// 目标选择阶段:选择放置位置

层级选择持久化

在目标选择阶段切换层级时,需要记住之前的选择:

// 当移动到新元素时,尝试保持相同的层级深度
if (previousSelectedIndex !== null) {
  selectedHierarchyIndex = Math.min(previousSelectedIndex, nodes.length - 1)
}

8.2 交互相关

屏蔽浏览器右键菜单

实现自定义右键菜单时必须阻止默认行为:

const handleContextMenu = (e: MouseEvent) => {
  e.preventDefault()
  e.stopPropagation()
  // 显示自定义菜单
}

多视图联动

设计中心、结构树、元数据面板需要同步选中状态:

// 使用自定义事件
window.dispatchEvent(new CustomEvent('design-component-selected', {
  detail: { path, componentName }
}))

// 其他组件监听
window.addEventListener('design-component-selected', handler)

8.3 后端相关

保持代码格式

修改Vue文件时使用字符串操作而非AST重建保持原始格式

// ✅ 正确:字符串替换
const newContent = content.substring(0, start) + newCode + content.substring(end)

// ❌ 避免AST重建会丢失格式
const newAST = transform(ast)
const newContent = generate(newAST)

热更新触发

修改Vue文件后触发事件让前端刷新

window.dispatchEvent(new CustomEvent('vue-template-updated', {
  detail: { pagePath }
}))

8.4 性能优化

防止重复绑定

使用标记属性避免重复绑定事件:

if (element.hasAttribute('data-fauto-bindend')) return
element.setAttribute('data-fauto-bindend', 'true')

MutationObserver

监听DOM变化动态注入事件

const observer = new MutationObserver((mutations) => {
  // 检查是否有新的el-row/el-col
  if (hasNewElements) {
    injectInteractionEvents()
  }
})
observer.observe(container, { childList: true, subtree: true })

9. 调试技巧

9.1 前端调试

// 在关键位置添加日志
console.log('[模块名] 操作:', { data })

// 使用Vue DevTools查看Pinia状态
// 使用浏览器DevTools的Elements面板查看data-path属性

9.2 后端调试

// 添加详细日志
console.log(`[API] 请求: ${req.path}`, req.body)
console.log(`[Service] 处理结果:`, result)

9.3 常用调试命令

# 查看端口占用
netstat -ano | findstr :3001

# 杀死进程
taskkill /F /PID <pid>

文档更新时间2026-01-20