搜索

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

[板块5:静态页面生成器] - 13 - 实现一键发布功能

代码示例
# 实现一键发布功能

## 1。目标

在后台页面管理中添加「一键发布」按钮,点击后调用后端 API 执行静态文件生成命令,将所有已发布页面生成为 HTML 文件。

## 2。修改文件清单

| 文件 | 操作 | 说明 |
|------|------|------|
| `routes/api.php` | 修改 | 添加发布接口路由 |
| `app/Http/Controllers/Admin/PageController.php` | 修改 | 添加 publish 方法 |
| `resources/js/admin/views/Pages.vue` | 修改 | 添加发布按钮和调用逻辑 |

---

## 3。修改详情

### 3.1 添加 API 路由

**文件路径**:`routes/api.php`

**修改位置**:在 `auth:sanctum` 中间件组内,页面管理路由之后

**新增代码**:

```php
// 一键发布
Route::post('/publish', [PageController::class, 'publish']);
```

**修改原因**:提供后端发布接口,供前端调用。

### 3.2 添加 publish 方法

**文件路径**:`app/Http/Controllers/Admin/PageController.php`

**修改位置**:在类中添加新方法

**新增代码**:

```php
use Illuminate\Support\Facades\Artisan;

/**
 * 一键发布 - 生成静态页面
 */
public function publish()
{
    try {
        Artisan::call('generate:static');
        $output = Artisan::output();
        
        return response()->json([
            'success' => true,
            'message' => '静态页面生成成功',
            'output' => $output
        ]);
    } catch (\Exception $e) {
        return response()->json([
            'success' => false,
            'message' => '生成失败:' . $e->getMessage()
        ], 500);
    }
}
```

**修改原因**:
- `Artisan::call('generate:static')`:调用 5-5 创建的静态文件生成命令
- 返回 JSON 格式的成功或失败信息

### 3.3 修改前端页面

**文件路径**:`resources/js/admin/views/Pages.vue`

**修改位置1:导入图标和 axios**

```typescript
import { Plus, Upload } from '@element-plus/icons-vue';
import axios from 'axios';
```

**修改位置2:添加发布状态变量**

```typescript
const publishing = ref(false);
```

**修改位置3:添加发布方法**

```typescript
const handlePublish = async () => {
    publishing.value = true;
    try {
        const response = await axios.post('/api/admin/publish');
        if (response.data.success) {
            ElMessage.success(response.data.message);
        } else {
            ElMessage.error(response.data.message);
        }
    } catch (error) {
        ElMessage.error('发布失败,请检查服务器日志');
    } finally {
        publishing.value = false;
    }
};
```

**修改位置4:修改模板头部区域**

**修改前**:

```vue
<template #header>
    <div>
        <span>页面管理</span>
        <el-button type="primary" size="small" @click="handleCreate">
            <el-icon><Plus /></el-icon>
            新建页面
        </el-button>
    </div>
</template>
```

**修改后**:

```vue
<template #header>
    <div>
        <span>页面管理</span>
        <div>
            <el-button type="success" size="small" @click="handlePublish" :loading="publishing">
                <el-icon><Upload /></el-icon>
                一键发布
            </el-button>
            <el-button type="primary" size="small" @click="handleCreate">
                <el-icon><Plus /></el-icon>
                新建页面
            </el-button>
        </div>
    </div>
</template>
```

**修改原因**:
- `handlePublish`:调用后端发布接口
- `:loading="publishing"`:防止重复提交,显示加载动画
- `ElMessage`:给用户明确的操作反馈

### 3.4 完整代码(Pages.vue 最终版)

**文件路径**:`resources/js/admin/views/Pages.vue`

```vue
<template>
    <div>
        <el-card>
            <template #header>
                <div>
                    <span>页面管理</span>
                    <div>
                        <el-button type="success" size="small" @click="handlePublish" :loading="publishing">
                            <el-icon><Upload /></el-icon>
                            一键发布
                        </el-button>
                        <el-button type="primary" size="small" @click="handleCreate">
                            <el-icon><Plus /></el-icon>
                            新建页面
                        </el-button>
                    </div>
                </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 { ElMessage, ElMessageBox } from 'element-plus';
import { Plus, Upload } from '@element-plus/icons-vue';
import { usePageStore } from '../stores/page';
import { useRouter } from 'vue-router';
import axios from 'axios';

const pageStore = usePageStore();
const router = useRouter();
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 publishing = ref(false);

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) => {
    router.push(`/admin/pages/${row.id}/edit`);
};

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;
};

const handlePublish = async () => {
    publishing.value = true;
    try {
        const response = await axios.post('/api/admin/publish');
        if (response.data.success) {
            ElMessage.success(response.data.message);
        } else {
            ElMessage.error(response.data.message);
        }
    } catch (error) {
        ElMessage.error('发布失败,请检查服务器日志');
    } finally {
        publishing.value = false;
    }
};

onMounted(() => {
    pageStore.fetchPages();
});
</script>

<style scoped>
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
</style>
```

---

## 4。验证结果

| 验证项 | 预期结果 | 实际结果 |
|--------|---------|---------|
| 发布按钮显示 | 页面管理头部显示「一键发布」按钮 | ✅ |
| 点击发布 | 提示「静态页面生成成功」 | ✅ |
| 静态文件生成 | `public/*.html` 文件更新 | ✅ |

---

🧸 adorable code

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

hello@adorablecode.com