# 页面管理功能
## 1. 创建 PageController
### 1.1 执行命令
```bash
php artisan make:controller Admin/PageController
```
### 1.2 文件路径
`app/Http/Controllers/Admin/PageController.php`
### 1.3 完整代码
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Page;
use Illuminate\Http\Request;
class PageController extends Controller
{
public function index()
{
$pages = Page::with('site')->get();
return response()->json($pages);
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|max:255|unique:pages',
'is_home' => 'boolean',
'status' => 'boolean',
]);
$page = Page::create($validated);
return response()->json($page, 201);
}
public function show($id)
{
$page = Page::with('components')->findOrFail($id);
return response()->json($page);
}
public function update(Request $request, $id)
{
$page = Page::findOrFail($id);
$validated = $request->validate([
'title' => 'string|max:255',
'slug' => 'string|max:255|unique:pages,slug,' . $id,
'is_home' => 'boolean',
'status' => 'boolean',
]);
$page->update($validated);
return response()->json($page);
}
public function destroy($id)
{
$page = Page::findOrFail($id);
$page->delete();
return response()->json(null, 204);
}
}
```
### 1.4 代码说明
| 方法 | 路由 | 作用 |
|------|------|------|
| `index` | GET /api/admin/pages | 获取页面列表 |
| `store` | POST /api/admin/pages | 创建页面 |
| `show` | GET /api/admin/pages/{id} | 获取单个页面 |
| `update` | PUT /api/admin/pages/{id} | 更新页面 |
| `destroy` | DELETE /api/admin/pages/{id} | 删除页面 |
---
## 2. 添加 API 路由
### 2.1 文件路径
`routes/api.php`
### 2.2 完整代码
```php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\AuthController;
use App\Http\Controllers\Admin\PageController;
// 无需认证的路由
Route::post('/admin/login', [AuthController::class, 'login']);
// 需要认证的路由
Route::middleware('auth:sanctum')->prefix('admin')->group(function () {
Route::get('/user', [AuthController::class, 'user']);
Route::post('/logout', [AuthController::class, 'logout']);
// 页面管理
Route::get('/pages', [PageController::class, 'index']);
Route::post('/pages', [PageController::class, 'store']);
Route::get('/pages/{id}', [PageController::class, 'show']);
Route::put('/pages/{id}', [PageController::class, 'update']);
Route::delete('/pages/{id}', [PageController::class, 'destroy']);
});
```
---
## 3. 创建 API 请求封装
### 3.1 文件路径
`resources\js\admin\api\page.ts`
### 3.2 完整代码
```typescript
import axios from 'axios';
export interface Page {
id: number;
site_id: number;
title: string;
slug: string;
is_home: boolean;
status: boolean;
created_at: string;
updated_at: string;
}
export const pageApi = {
getList: () => axios.get<Page[]>('/api/admin/pages'),
getDetail: (id: number) => axios.get<Page>(`/api/admin/pages/${id}`),
create: (data: Partial<Page>) => axios.post<Page>('/api/admin/pages', data),
update: (id: number, data: Partial<Page>) => axios.put<Page>(`/api/admin/pages/${id}`, data),
delete: (id: number) => axios.delete(`/api/admin/pages/${id}`),
};
```
---
## 4. 创建页面管理 Store
### 4.1 文件路径
`resources\js\admin\stores\page.ts`
### 4.2 完整代码
```typescript
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { pageApi, type Page } from '../api/page';
import { ElMessage } from 'element-plus';
export const usePageStore = defineStore('page', () => {
const pages = ref<Page[]>([]);
const loading = ref(false);
const currentPage = ref<Page | null>(null);
const fetchPages = async () => {
loading.value = true;
try {
const response = await pageApi.getList();
pages.value = response.data;
} catch (error) {
ElMessage.error('获取页面列表失败');
} finally {
loading.value = false;
}
};
const fetchPage = async (id: number) => {
loading.value = true;
try {
const response = await pageApi.getDetail(id);
currentPage.value = response.data;
} catch (error) {
ElMessage.error('获取页面详情失败');
} finally {
loading.value = false;
}
};
const createPage = async (data: Partial<Page>) => {
loading.value = true;
try {
const response = await pageApi.create(data);
pages.value.unshift(response.data);
ElMessage.success('创建成功');
return response.data;
} catch (error) {
ElMessage.error('创建失败');
throw error;
} finally {
loading.value = false;
}
};
const updatePage = async (id: number, data: Partial<Page>) => {
loading.value = true;
try {
const response = await pageApi.update(id, data);
const index = pages.value.findIndex(p => p.id === id);
if (index !== -1) {
pages.value[index] = response.data;
}
ElMessage.success('更新成功');
return response.data;
} catch (error) {
ElMessage.error('更新失败');
throw error;
} finally {
loading.value = false;
}
};
const deletePage = async (id: number) => {
loading.value = true;
try {
await pageApi.delete(id);
pages.value = pages.value.filter(p => p.id !== id);
ElMessage.success('删除成功');
} catch (error) {
ElMessage.error('删除失败');
throw error;
} finally {
loading.value = false;
}
};
return {
pages,
loading,
currentPage,
fetchPages,
fetchPage,
createPage,
updatePage,
deletePage,
};
});
```
---
## 5. 修改 Pages.vue 实现页面列表
### 5.1 文件路径
`resources\js\admin\views\Pages.vue`
### 5.2 完整代码
```vue
<template>
<div>
<el-card>
<template #header>
<div>
<span>页面管理</span>
<el-button type="primary" size="small" @click="handleCreate">
<el-icon><Plus /></el-icon>
新建页面
</el-button>
</div>
</template>
<el-table :data="pageStore.pages" v-loading="pageStore.loading" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="页面标题" />
<el-table-column prop="slug" label="访问别名" />
<el-table-column prop="is_home" label="是否首页" width="100">
<template #default="{ row }">
<el-tag :type="row.is_home ? 'success' : 'info'">
{{ row.is_home ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status ? 'success' : 'danger'">
{{ row.status ? '已发布' : '草稿' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="updated_at" label="更新时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" @close="handleDialogClose">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="页面标题" prop="title">
<el-input v-model="form.title" placeholder="请输入页面标题" />
</el-form-item>
<el-form-item label="访问别名" prop="slug">
<el-input v-model="form.slug" placeholder="例如: about" />
</el-form-item>
<el-form-item label="是否首页" prop="is_home">
<el-switch v-model="form.is_home" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="form.status"
active-value="1"
inactive-value="0"
active-text="发布"
inactive-text="草稿"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ElMessageBox } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import { usePageStore } from '../stores/page';
const pageStore = usePageStore();
const dialogVisible = ref(false);
const dialogTitle = ref('');
const submitting = ref(false);
const formRef = ref();
const isEdit = ref(false);
const editId = ref<number | null>(null);
const form = reactive({
title: '',
slug: '',
is_home: false,
status: false,
});
const rules = {
title: [{ required: true, message: '请输入页面标题', trigger: 'blur' }],
slug: [{ required: true, message: '请输入访问别名', trigger: 'blur' }],
};
const handleCreate = () => {
isEdit.value = false;
editId.value = null;
dialogTitle.value = '新建页面';
resetForm();
dialogVisible.value = true;
};
const handleEdit = (row: any) => {
isEdit.value = true;
editId.value = row.id;
dialogTitle.value = '编辑页面';
form.title = row.title;
form.slug = row.slug;
form.is_home = Boolean(row.is_home);
form.status = Boolean(row.status);
dialogVisible.value = true;
};
const handleDelete = (row: any) => {
ElMessageBox.confirm(`确定要删除页面「${row.title}」吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
await pageStore.deletePage(row.id);
await pageStore.fetchPages();
}).catch(() => {});
};
const handleSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return;
submitting.value = true;
try {
if (isEdit.value && editId.value) {
await pageStore.updatePage(editId.value, form);
} else {
await pageStore.createPage(form);
}
dialogVisible.value = false;
await pageStore.fetchPages();
} finally {
submitting.value = false;
}
});
};
const handleDialogClose = () => {
resetForm();
formRef.value?.clearValidate();
};
const resetForm = () => {
form.title = '';
form.slug = '';
form.is_home = false;
form.status = false;
};
onMounted(() => {
pageStore.fetchPages();
});
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
```
---
## 6. 创建缺失的模型
### 6.1 问题发现
访问页面时报错:`Class "App\Models\Page" not found`
**原因**:`Page` 模型不存在
### 6.2 创建 Page 模型
```bash
php artisan make:model Page
```
**文件路径**:`app/Models/Page.php`
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
use HasFactory;
protected $fillable = [
'site_id',
'title',
'slug',
'is_home',
'status',
];
protected $casts = [
'is_home' => 'boolean',
'status' => 'boolean',
];
public function site()
{
return $this->belongsTo(Site::class);
}
public function components()
{
return $this->hasMany(PageComponent::class);
}
}
```
### 6.3 创建 Site 模型
```bash
php artisan make:model Site
```
**文件路径**:`app/Models/Site.php`
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Site extends Model
{
use HasFactory;
protected $fillable = [
'site_name',
'site_logo',
'site_keywords',
'site_description',
];
}
```
### 6.4 创建 PageComponent 模型
```bash
php artisan make:model PageComponent
```
**文件路径**:`app/Models/PageComponent.php`
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PageComponent extends Model
{
use HasFactory;
protected $fillable = [
'page_id',
'component_type',
'content',
'settings',
'sort_order',
];
protected $casts = [
'content' => 'array',
'settings' => 'array',
];
public function page()
{
return $this->belongsTo(Page::class);
}
}
```
---
## 7. 编译验证
### 7.1 执行命令
```bash
npm run build
```
### 7.2 预期结果
编译成功,无报错。
---
## 8. 访问测试
浏览器打开:`http://engine-api.test/admin/pages`
预期结果:显示页面列表,包含 ID、页面标题、访问别名、是否首页、状态、更新时间、操作按钮。
---
## 9. 遇到的问题及解决
### 9.1 问题:PageController 中 Page 模型找不到
| 项目 | 内容 |
|------|------|
| **现象** | 访问页面时报错 `Class "App\Models\Page" not found` |
| **原因** | `Page` 模型不存在 |
| **解决方案** | 执行 `php artisan make:model Page` 创建模型 |
### 9.2 问题:Site 模型和 PageComponent 模型也不存在
| 项目 | 内容 |
|------|------|
| **现象** | Page 模型中关联了 Site 和 PageComponent,但模型不存在 |
| **原因** | 阶段1只创建了迁移文件,没有创建模型 |
| **解决方案** | 创建 Site 和 PageComponent 模型 |
### 9.3 问题:api 目录已存在
| 项目 | 内容 |
|------|------|
| **说明** | `resources\js\admin\api` 目录在阶段2已创建,不需要重复创建 |
---
## 10. 验证结果
| 验证项 | 预期结果 | 实际结果 |
|--------|---------|---------|
| 编译 | 无报错 | ✅ 通过 |
| 页面列表 | 显示数据表格 | ✅ 通过 |
| 新建页面 | 弹出表单对话框 | ✅ 通过 |
| 编辑页面 | 弹出表单并回填数据 | ✅ 通过 |
| 删除页面 | 弹出确认框,删除后列表刷新 | ✅ 通过 |
| 数据显示 | 显示已有页面数据 | ✅ 通过 |
```
---
**以上是阶段8的完整记录,供您整理到博客。** 🚀