12 KiB
12 KiB
Fauto Design - 开发指南
本文档为开发者提供项目的开发规范、扩展指南和最佳实践。
目录
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 创建步骤
- 在
draggable-panels/src/fauto/designComponents/创建目录 - 添加
index.json配置文件 - 添加
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.json的name一致
4.5 扩展元数据类型
如需添加新的元数据类型:
- 在
designStore.ts的MetadataField.type添加类型 - 在
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 创建步骤
- 在
draggable-panels/src/fauto/materials/创建目录 - 添加
index.vue组件文件 - 添加
index.json配置文件 - 在
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