laobinghu ce208df092 feat: 实现 Nuxt4 + Nuxt UI 博客前端完整功能
核心功能:
- 项目初始化 (Nuxt 4 + Nuxt UI + Pinia + ofetch)
- TypeScript 类型定义 (User, Article, Comment, API 响应)
- 认证系统 (登录/登出、Cookie 支持、权限中间件)
- 文章列表页 (筛选、分页、响应式布局)
- 文章详情页 (Markdown 渲染、评论系统)
- 文章编辑器 (左右分栏、实时预览、Markdown 工具栏)

管理后台:
- 侧边栏布局、权限检查
- 数据分析 (数据统计卡片、热门文章、评论审核统计)
- 文章管理 (表格、筛选、删除)
- 评论管理 (审核通过/拒绝、删除)
- 用户管理 (角色管理、删除)

全局组件:
- 导航栏 (暗色模式切换、移动端菜单)
- 页脚
- 403/404 错误页

配置文件:
- .env.example 环境变量模板
- nuxt.config.ts 完整配置
- 自定义 CSS 样式
2026-03-28 15:56:50 +08:00

111 lines
2.9 KiB
Vue

<template>
<div class="flex gap-4" :class="{ 'ml-12': depth > 0 }">
<UAvatar
:src="comment.author?.avatarUrl"
:alt="comment.author?.displayName || comment.author?.username"
size="md"
class="flex-shrink-0"
/>
<div class="flex-1">
<UCard class="bg-gray-50 dark:bg-gray-800">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="font-medium text-gray-900 dark:text-white">
{{ comment.author?.displayName || comment.author?.username || comment.authorName || '匿名用户' }}
</span>
<time class="text-xs text-gray-500 dark:text-gray-400">
{{ formatTime(comment.createdAt) }}
</time>
<UBadge
v-if="comment.status !== 'approved'"
:label="statusLabels[comment.status]"
:color="statusColors[comment.status]"
size="xs"
variant="subtle"
/>
</div>
</div>
<div class="prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300">
{{ comment.content }}
</div>
<div class="mt-3 flex items-center gap-4">
<UButton
variant="ghost"
size="xs"
icon="i-heroicons-arrow-turn-left-up"
@click="showReply = !showReply"
>
回复
</UButton>
</div>
<CommentForm
v-if="showReply"
:article-id="comment.articleId"
:parent-id="comment.id"
:placeholder="`回复 ${comment.author?.displayName || comment.author?.username || '匿名用户'}`"
@submitted="onReplySubmitted"
@cancel="showReply = false"
/>
</UCard>
<div v-if="comment.replies?.length && depth < 2" class="mt-4 space-y-4">
<CommentItem
v-for="reply in comment.replies"
:key="reply.id"
:comment="reply"
:depth="depth + 1"
@reply="$emit('reply', $event)"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Comment } from '~/types/models'
defineProps<{
comment: Comment
depth: number
}>()
defineEmits<{
reply: [comment: Comment]
}>()
const showReply = ref(false)
const statusLabels: Record<string, string> = {
pending: '待审核',
approved: '已通过',
rejected: '已拒绝',
suspicious: '可疑',
}
const statusColors: Record<string, string> = {
pending: 'yellow',
approved: 'green',
rejected: 'red',
suspicious: 'orange',
}
const formatTime = (date: string) => {
return new Date(date).toLocaleString('zh-CN', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}
const onReplySubmitted = () => {
showReply.value = false
// TODO: 刷新评论列表
}
</script>