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

598 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fauto Design - 开发指南
本文档为开发者提供项目的开发规范、扩展指南和最佳实践。
---
## 目录
1. [开发环境配置](#1-开发环境配置)
2. [代码规范](#2-代码规范)
3. [Vue页面规范](#3-vue页面规范)
4. [设计组件开发](#4-设计组件开发)
5. [物料组件开发](#5-物料组件开发)
6. [插件开发](#6-插件开发)
7. [后端服务扩展](#7-后端服务扩展)
8. [常见问题与经验](#8-常见问题与经验)
---
## 1. 开发环境配置
### 1.1 前端环境
```bash
cd draggable-panels
npm install
npm run dev
```
开发服务器:`http://localhost:5173`
设计器入口:`http://localhost:5173/draggable`
### 1.2 后端环境
```bash
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规范
```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组件规范
```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**
```vue
<template>
<!-- 正确单一el-row根元素 -->
<el-row :gutter="20">
<el-col :span="12">...</el-col>
<el-col :span="12">...</el-col>
</el-row>
</template>
```
```vue
<template>
<!-- 错误多个根元素 -->
<el-row>...</el-row>
<el-row>...</el-row>
</template>
```
### 3.2 设计组件标识
页面中的设计组件需要 `data-component` 属性:
```html
<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)
```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)
```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 扩展元数据类型
如需添加新的元数据类型:
1.`designStore.ts``MetadataField.type` 添加类型
2.`DataTable/index.vue` 添加对应的编辑控件
```typescript
// designStore.ts
export interface MetadataField {
type: 'text' | 'number' | 'select' | 'boolean' | 'color' | 'myNewType'
// ...
}
```
```vue
<!-- 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)
```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)
```json
{
"id": "MyMaterial",
"name": "我的物料",
"icon": "🔧",
"description": "物料组件描述",
"category": "tool",
"defaultPanel": "left"
}
```
### 5.4 注册物料
```typescript
// 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 创建新插件
```typescript
// 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 统一导出
```typescript
// plugins/index.ts
export { useMyPlugin } from './myPlugin'
```
---
## 7. 后端服务扩展
### 7.1 添加新API
```javascript
// 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 添加新服务函数
```javascript
// 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 拖拽相关
#### 两阶段拖拽
拖拽分为源选择和目标选择两个阶段,避免误操作:
```typescript
// dragStore.ts
dragPhase: 'source' | 'target'
// 源选择阶段:收集层级节点
// 目标选择阶段:选择放置位置
```
#### 层级选择持久化
在目标选择阶段切换层级时,需要记住之前的选择:
```typescript
// 当移动到新元素时,尝试保持相同的层级深度
if (previousSelectedIndex !== null) {
selectedHierarchyIndex = Math.min(previousSelectedIndex, nodes.length - 1)
}
```
### 8.2 交互相关
#### 屏蔽浏览器右键菜单
实现自定义右键菜单时必须阻止默认行为:
```typescript
const handleContextMenu = (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
// 显示自定义菜单
}
```
#### 多视图联动
设计中心、结构树、元数据面板需要同步选中状态:
```typescript
// 使用自定义事件
window.dispatchEvent(new CustomEvent('design-component-selected', {
detail: { path, componentName }
}))
// 其他组件监听
window.addEventListener('design-component-selected', handler)
```
### 8.3 后端相关
#### 保持代码格式
修改Vue文件时使用字符串操作而非AST重建保持原始格式
```javascript
// ✅ 正确:字符串替换
const newContent = content.substring(0, start) + newCode + content.substring(end)
// ❌ 避免AST重建会丢失格式
const newAST = transform(ast)
const newContent = generate(newAST)
```
#### 热更新触发
修改Vue文件后触发事件让前端刷新
```typescript
window.dispatchEvent(new CustomEvent('vue-template-updated', {
detail: { pagePath }
}))
```
### 8.4 性能优化
#### 防止重复绑定
使用标记属性避免重复绑定事件:
```typescript
if (element.hasAttribute('data-fauto-bindend')) return
element.setAttribute('data-fauto-bindend', 'true')
```
#### MutationObserver
监听DOM变化动态注入事件
```typescript
const observer = new MutationObserver((mutations) => {
// 检查是否有新的el-row/el-col
if (hasNewElements) {
injectInteractionEvents()
}
})
observer.observe(container, { childList: true, subtree: true })
```
---
## 9. 调试技巧
### 9.1 前端调试
```typescript
// 在关键位置添加日志
console.log('[模块名] 操作:', { data })
// 使用Vue DevTools查看Pinia状态
// 使用浏览器DevTools的Elements面板查看data-path属性
```
### 9.2 后端调试
```javascript
// 添加详细日志
console.log(`[API] 请求: ${req.path}`, req.body)
console.log(`[Service] 处理结果:`, result)
```
### 9.3 常用调试命令
```bash
# 查看端口占用
netstat -ano | findstr :3001
# 杀死进程
taskkill /F /PID <pid>
```
---
**文档更新时间**2026-01-20