讲师:乌鸦哥
让我们的博客变得更完整!
把博客变成真正可用的发布平台 📝
我们的博客还不够"智能" 🤔
像微信公众号一样方便 ✨
搭建我们的"编辑部" 🏢
就像报社的编辑部一样
# 在 pages 目录下创建 admin 文件夹
mkdir pages/admin
# 创建管理员首页
touch pages/admin/index.vue
# 创建写文章页面
touch pages/admin/new-post.vue
就像在办公楼里开辟一个编辑部
门口要有保安 🔐
<script setup>
// 检查是否已登录
const { data: user } = await useFetch('/api/user/me')
// 如果没有登录,跳转到登录页
if (!user.value) {
await navigateTo('/login')
}
// 如果不是管理员,拒绝访问
if (user.value.role !== 'admin') {
throw createError({
statusCode: 403,
statusMessage: '访问被拒绝:需要管理员权限'
})
}
</script>
<template>
<div>
<h1>管理员控制台</h1>
<p>欢迎回来,{{ user.name }}!</p>
</div>
</template>
设计我们的"投稿箱" 📋
就像填写投稿表一样
<template>
<div class="max-w-4xl mx-auto p-6">
<h1 class="text-3xl font-bold mb-8">发布新文章</h1>
<form @submit.prevent="publishPost">
<!-- 文章标题 -->
<div class="mb-6">
<label class="block text-sm font-medium mb-2">文章标题</label>
<input
v-model="post.title"
type="text"
class="w-full p-3 border rounded-lg"
placeholder="请输入吸引人的标题..."
required
>
</div>
<!-- 文章内容 -->
<div class="mb-6">
<label class="block text-sm font-medium mb-2">文章内容</label>
<textarea
v-model="post.content"
rows="15"
class="w-full p-3 border rounded-lg"
placeholder="在这里写下你的精彩内容..."
required
></textarea>
</div>
<!-- 提交按钮 -->
<button
type="submit"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600"
>
发布文章
</button>
</form>
</div>
</template>
质量控制很重要 ✅
<script setup>
const post = ref({
title: '',
content: '',
tags: [],
status: 'draft'
})
// 表单验证
const validatePost = () => {
if (!post.value.title.trim()) {
alert('请输入文章标题')
return false
}
if (post.value.title.length > 100) {
alert('标题太长了,请控制在100字以内')
return false
}
if (!post.value.content.trim()) {
alert('文章内容不能为空')
return false
}
if (post.value.content.length < 50) {
alert('文章内容太短了,至少要50字哦')
return false
}
return true
}
</script>
搭建数据处理中心 🔧
像邮局的不同服务窗口
// server/api/posts/index.post.js
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
try {
// 获取请求数据
const body = await readBody(event)
// 验证用户权限
const user = await getCurrentUser(event)
if (!user || user.role !== 'admin') {
throw createError({
statusCode: 403,
statusMessage: '没有权限发布文章'
})
}
// 创建新文章
const newPost = await prisma.post.create({
data: {
title: body.title,
content: body.content,
authorId: user.id,
status: body.status || 'published',
createdAt: new Date()
}
})
return {
success: true,
message: '文章发布成功!',
post: newPost
}
} catch (error) {
throw createError({
statusCode: 500,
statusMessage: '发布文章失败:' + error.message
})
}
})
// server/api/posts/index.get.js
export default defineEventHandler(async (event) => {
try {
const query = getQuery(event)
const page = parseInt(query.page) || 1
const limit = parseInt(query.limit) || 10
// 获取文章列表(只返回已发布的)
const posts = await prisma.post.findMany({
where: {
status: 'published'
},
orderBy: {
createdAt: 'desc'
},
skip: (page - 1) * limit,
take: limit,
include: {
author: {
select: {
name: true,
avatar: true
}
}
}
})
// 获取总数
const total = await prisma.post.count({
where: { status: 'published' }
})
return {
posts,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
}
} catch (error) {
throw createError({
statusCode: 500,
statusMessage: '获取文章列表失败'
})
}
})
把所有部件组装起来 🔗
// 在 pages/admin/new-post.vue 中
const publishPost = async () => {
// 验证表单
if (!validatePost()) return
try {
// 显示加载状态
const loading = ref(true)
// 发送请求到后端
const { data } = await $fetch('/api/posts', {
method: 'POST',
body: {
title: post.value.title,
content: post.value.content,
tags: post.value.tags,
status: post.value.status
}
})
// 成功提示
alert('文章发布成功!')
// 跳转到文章详情页
await navigateTo(`/posts/${data.post.id}`)
} catch (error) {
// 错误处理
alert('发布失败:' + error.message)
} finally {
loading.value = false
}
}
<!-- pages/index.vue -->
<script setup>
// 获取最新文章列表
const { data: postsData } = await useFetch('/api/posts', {
query: {
limit: 6
}
})
const posts = computed(() => postsData.value?.posts || [])
</script>
<template>
<div>
<h1>最新文章</h1>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<article
v-for="post in posts"
:key="post.id"
class="bg-white rounded-lg shadow-md p-6"
>
<h2 class="text-xl font-bold mb-2">
<NuxtLink :to="`/posts/${post.id}`">
{{ post.title }}
</NuxtLink>
</h2>
<p class="text-gray-600 mb-4">
{{ post.content.substring(0, 150) }}...
</p>
<div class="flex justify-between text-sm text-gray-500">
<span>作者:{{ post.author.name }}</span>
<span>{{ formatDate(post.createdAt) }}</span>
</div>
</article>
</div>
</div>
</template>
从想法到发布的完整路径 🛤️
让博客更加专业 ⭐
恭喜!你的博客现在已经是一个真正可用的发布平台了! 🎉
接下来我们可以继续添加更多酷炫的功能