搜索

📄 文章 📚 合集
热门搜索
🐘 PHP ⚡ Laravel 🎨 Vue.js ⚛️ React 📦 Yii 📘 JavaScript 🗄️ MySQL 🐳 Docker
返回合集

[板块4:可视化拖拽编辑器] - 03- 定义组件数据结构和 API

代码示例
# 可视化拖拽编辑器

##义组件数据结构

### 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 个方法 | ✅ 通过 |
| 编译 | 无报错 | ✅ 通过 |

🧸 adorable code

专注 PHP、JavaScript、Laravel、Vue.js、React、Yii 全栈开发。记录技术探索过程中的灵感与经验,分享工程实践洞见。

hello@adorablecode.com