核心功能: - 项目初始化 (Nuxt 4 + Nuxt UI + Pinia + ofetch) - TypeScript 类型定义 (User, Article, Comment, API 响应) - 认证系统 (登录/登出、Cookie 支持、权限中间件) - 文章列表页 (筛选、分页、响应式布局) - 文章详情页 (Markdown 渲染、评论系统) - 文章编辑器 (左右分栏、实时预览、Markdown 工具栏) 管理后台: - 侧边栏布局、权限检查 - 数据分析 (数据统计卡片、热门文章、评论审核统计) - 文章管理 (表格、筛选、删除) - 评论管理 (审核通过/拒绝、删除) - 用户管理 (角色管理、删除) 全局组件: - 导航栏 (暗色模式切换、移动端菜单) - 页脚 - 403/404 错误页 配置文件: - .env.example 环境变量模板 - nuxt.config.ts 完整配置 - 自定义 CSS 样式
111 lines
2.9 KiB
Vue
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>
|