ai牛逼
This commit is contained in:
597
DEVELOPMENT.md
Normal file
597
DEVELOPMENT.md
Normal 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
|
||||
Reference in New Issue
Block a user