# 可视化拖拽编辑器
##义组件数据结构
### 1. 创建组件类型定义文件
#### 1.1 创建 types 目录
**执行命令**:
```bash
mkdir resources\js\admin\types
```
#### 1.2 文件路径
`resources\js\admin\types\components.ts`
#### 1.3 完整代码
```typescript
// 组件类型枚举
export type ComponentType =
| 'banner' // 横幅
| 'text' // 纯文本
| 'image' // 图片
| 'multi_col' // 多列布局
| 'map' // 地图
| 'contact_form'; // 联系表单
// 组件内容接口
export interface ComponentContent {
title?: string; // 标题
subtitle?: string; // 副标题
text?: string; // 文本内容
image_url?: string; // 图片地址
link_url?: string; // 链接地址
columns?: Column[]; // 多列布局的列数据
address?: string; // 地图地址
email?: string; // 联系邮箱
phone?: string; // 联系电话
}
// 多列布局的单列数据
export interface Column {
title: string;
content: string;
image_url?: string;
}
// 样式设置接口
export interface ComponentSettings {
background_color?: string; // 背景色
text_color?: string; // 文字颜色
padding?: string; // 内边距
margin?: string; // 外边距
width?: string; // 宽度
height?: string; // 高度
align?: 'left' | 'center' | 'right'; // 对齐方式
}
// 组件完整数据结构
export interface PageComponent {
id: number;
page_id: number;
component_type: ComponentType;
content: ComponentContent;
settings: ComponentSettings;
sort_order: number;
created_at: string;
updated_at: string;
}
// 创建组件的输入类型(不含 id 和时间戳)
export type CreateComponentInput = Omit<PageComponent, 'id' | 'created_at' | 'updated_at'>;
```
#### 1.4 代码说明
| 接口 | 作用 |
|------|------|
| `ComponentType` | 组件类型枚举,限制只能使用预定义的 6 种组件 |
| `ComponentContent` | 组件内容,根据组件类型存储不同字段 |
| `Column` | 多列布局的单列数据结构 |
| `ComponentSettings` | 样式设置,控制组件外观 |
| `PageComponent` | 组件完整数据结构,对应数据库表 |
| `CreateComponentInput` | 创建组件时的输入类型(省略自动生成的字段) |
#### 1.5 设计原因
- 使用 TypeScript 接口确保数据结构统一
- `content` 和 `settings` 为 JSON 字段,灵活扩展
- `sort_order` 用于拖拽排序
---
### 2. 创建组件 API 封装
#### 2.1 文件路径
`resources\js\admin\api\component.ts`
#### 2.2 完整代码
```typescript
import axios from 'axios';
import type { PageComponent, CreateComponentInput } from '../types/components';
export const componentApi = {
// 获取页面的所有组件
getByPageId: (pageId: number) => axios.get<PageComponent[]>(`/api/admin/pages/${pageId}/components`),
// 创建组件
create: (data: CreateComponentInput) => axios.post<PageComponent>('/api/admin/components', data),
// 更新组件
update: (id: number, data: Partial<CreateComponentInput>) => axios.put<PageComponent>(`/api/admin/components/${id}`, data),
// 删除组件
delete: (id: number) => axios.delete(`/api/admin/components/${id}`),
// 批量更新排序
updateSortOrder: (items: { id: number; sort_order: number }[]) => axios.post('/api/admin/components/sort', { items }),
};
```
#### 2.3 代码说明
| 方法 | 作用 |
|------|------|
| `getByPageId` | 获取指定页面的所有组件 |
| `create` | 创建新组件 |
| `update` | 更新组件 |
| `delete` | 删除组件 |
| `updateSortOrder` | 批量更新排序 |
---
### 3. 创建组件 Store
#### 3.1 文件路径
`resources\js\admin\stores\component.ts`
#### 3.2 完整代码
```typescript
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { componentApi } from '../api/component';
import type { PageComponent, CreateComponentInput } from '../types/components';
import { ElMessage } from 'element-plus';
export const useComponentStore = defineStore('component', () => {
const components = ref<PageComponent[]>([]);
const loading = ref(false);
// 加载页面的组件列表
const fetchComponents = async (pageId: number) => {
loading.value = true;
try {
const response = await componentApi.getByPageId(pageId);
components.value = response.data.sort((a, b) => a.sort_order - b.sort_order);
} catch (error) {
ElMessage.error('加载组件失败');
} finally {
loading.value = false;
}
};
// 添加组件
const addComponent = async (data: CreateComponentInput) => {
loading.value = true;
try {
const response = await componentApi.create(data);
components.value.push(response.data);
ElMessage.success('添加成功');
return response.data;
} catch (error) {
ElMessage.error('添加失败');
throw error;
} finally {
loading.value = false;
}
};
// 更新组件
const updateComponent = async (id: number, data: Partial<CreateComponentInput>) => {
loading.value = true;
try {
const response = await componentApi.update(id, data);
const index = components.value.findIndex(c => c.id === id);
if (index !== -1) {
components.value[index] = response.data;
}
ElMessage.success('更新成功');
return response.data;
} catch (error) {
ElMessage.error('更新失败');
throw error;
} finally {
loading.value = false;
}
};
// 删除组件
const deleteComponent = async (id: number) => {
loading.value = true;
try {
await componentApi.delete(id);
components.value = components.value.filter(c => c.id !== id);
ElMessage.success('删除成功');
} catch (error) {
ElMessage.error('删除失败');
throw error;
} finally {
loading.value = false;
}
};
// 更新排序
const updateSortOrder = async (items: { id: number; sort_order: number }[]) => {
try {
await componentApi.updateSortOrder(items);
items.forEach(item => {
const component = components.value.find(c => c.id === item.id);
if (component) {
component.sort_order = item.sort_order;
}
});
components.value.sort((a, b) => a.sort_order - b.sort_order);
} catch (error) {
ElMessage.error('更新排序失败');
throw error;
}
};
return {
components,
loading,
fetchComponents,
addComponent,
updateComponent,
deleteComponent,
updateSortOrder,
};
});
```
#### 3.3 代码说明
| 方法 | 作用 |
|------|------|
| `fetchComponents` | 加载页面组件列表,按 sort_order 排序 |
| `addComponent` | 添加组件到画布 |
| `updateComponent` | 更新组件属性 |
| `deleteComponent` | 删除组件 |
| `updateSortOrder` | 拖拽排序后更新顺序 |
#### 3.4 Store 命名解释
| 术语 | 含义 |
|------|------|
| **Store** | 全局状态存储,管理多个组件共享的数据 |
| **为什么用 Store** | 避免组件间层层传递数据,统一管理,自动同步 |
---
### 4. 创建后端 ComponentController
#### 4.1 执行命令
```bash
php artisan make:controller Admin/ComponentController
```
#### 4.2 文件路径
`app/Http/Controllers/Admin/ComponentController.php`
#### 4.3 完整代码
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\PageComponent;
use Illuminate\Http\Request;
class ComponentController extends Controller
{
// 获取页面的所有组件
public function getByPageId($pageId)
{
$components = PageComponent::where('page_id', $pageId)
->orderBy('sort_order')
->get();
return response()->json($components);
}
// 创建组件
public function store(Request $request)
{
$validated = $request->validate([
'page_id' => 'required|exists:pages,id',
'component_type' => 'required|string|max:50',
'content' => 'nullable|array',
'settings' => 'nullable|array',
'sort_order' => 'integer',
]);
$component = PageComponent::create($validated);
return response()->json($component, 201);
}
// 更新组件
public function update(Request $request, $id)
{
$component = PageComponent::findOrFail($id);
$validated = $request->validate([
'component_type' => 'string|max:50',
'content' => 'nullable|array',
'settings' => 'nullable|array',
'sort_order' => 'integer',
]);
$component->update($validated);
return response()->json($component);
}
// 删除组件
public function destroy($id)
{
$component = PageComponent::findOrFail($id);
$component->delete();
return response()->json(null, 204);
}
// 批量更新排序
public function updateSortOrder(Request $request)
{
$request->validate([
'items' => 'required|array',
'items.*.id' => 'required|exists:page_components,id',
'items.*.sort_order' => 'required|integer',
]);
foreach ($request->items as $item) {
PageComponent::where('id', $item['id'])->update(['sort_order' => $item['sort_order']]);
}
return response()->json(['message' => '排序更新成功']);
}
}
```
#### 4.4 代码说明
| 方法 | 路由 | 作用 |
|------|------|------|
| `getByPageId` | GET /api/admin/pages/{pageId}/components | 获取页面组件 |
| `store` | POST /api/admin/components | 创建组件 |
| `update` | PUT /api/admin/components/{id} | 更新组件 |
| `destroy` | DELETE /api/admin/components/{id} | 删除组件 |
| `updateSortOrder` | POST /api/admin/components/sort | 批量更新排序 |
---
### 5. 添加组件 API 路由
#### 5.1 文件路径
`routes/api.php`
#### 5.2 添加内容
在 `Route::middleware('auth:sanctum')->prefix('admin')->group` 中添加:
```php
// 组件管理
Route::get('/pages/{pageId}/components', [App\Http\Controllers\Admin\ComponentController::class, 'getByPageId']);
Route::post('/components', [App\Http\Controllers\Admin\ComponentController::class, 'store']);
Route::put('/components/{id}', [App\Http\Controllers\Admin\ComponentController::class, 'update']);
Route::delete('/components/{id}', [App\Http\Controllers\Admin\ComponentController::class, 'destroy']);
Route::post('/components/sort', [App\Http\Controllers\Admin\ComponentController::class, 'updateSortOrder']);
```
#### 5.3 验证路由注册
```bash
php artisan route:list | grep components
```
预期输出:
```
POST api/admin/components ...... Admin\ComponentController@store
POST api/admin/components/sort ...... Admin\ComponentController@updateSortOrder
PUT api/admin/components/{id} ...... Admin\ComponentController@update
DELETE api/admin/components/{id} ...... Admin\ComponentController@destroy
GET|HEAD api/admin/pages/{pageId}/components ...... Admin\ComponentController@getByPageId
```
---
### 6. 编译验证
#### 6.1 执行命令
```bash
npm run build
```
#### 6.2 预期结果
编译成功,无报错。
---
### 7. 验证结果
| 验证项 | 预期结果 | 实际结果 |
|--------|---------|---------|
| TypeScript 类型定义 | 无语法错误 | ✅ 通过 |
| API 路由注册 | 5 条路由 | ✅ 通过 |
| 后端控制器 | 5 个方法 | ✅ 通过 |
| 编译 | 无报错 | ✅ 通过 |