This commit is contained in:
wfz
2026-01-20 22:07:05 +08:00
parent bfa4e3107f
commit 842a132ec6
5 changed files with 1301 additions and 535 deletions

597
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,597 @@
# 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