Mimingguang ee078b61bc feat: 后台管理系统完整重构 + 前后台数据联通
- 后台全面引入 Naive UI 组件库,统一 UI 规范
- 前台 usePublicApi 切换到 API 调用(GET /api/content/[key])
- 前后台数据源统一(site_content 表)
- 产品线统一管理(tour/camp/course 三套编辑器)
- 产品数据归一化(itinerary/faq/pricing 格式统一)
- 表单提交 API 改写入 submissions 表
- 新增 submissions 标记已读 API
- site-content PUT 支持 upsert
- 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb)
- 消除 PageHero 类型警告(useSEO reactive 修复)
- 25 个前台页面全部 HTTP 200,展示效果不变

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:48:02 +08:00

69 行
3.1 KiB
Vue

<template>
<div>
<h1 style="margin: 0 0 24px; font-size: 24px; color: #111827; font-weight: 600">数据概览</h1>
<n-spin :show="!stats">
<n-grid v-if="stats" :cols="4" :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<n-gi v-for="item in statCards" :key="item.key" span="4 s:2 m:1">
<n-card hoverable style="cursor: pointer" @click="item.to && navigateTo(item.to)">
<n-statistic :label="item.label" :value="item.value">
<template #suffix>
<span style="font-size: 14px; color: #9ca3af">{{ item.sub }}</span>
</template>
</n-statistic>
</n-card>
</n-gi>
</n-grid>
<template v-if="stats">
<h2 style="font-size: 18px; color: #333; margin: 32px 0 16px; font-weight: 600">快捷操作</h2>
<n-grid :cols="4" :x-gap="12" :y-gap="12" responsive="screen" item-responsive>
<n-gi v-for="q in quickLinks" :key="q.to" span="4 s:2 m:1">
<n-card hoverable size="small" style="cursor: pointer" @click="navigateTo(q.to)">
<n-space align="center">
<div>
<div style="font-size: 14px; font-weight: 500; color: #333">{{ q.title }}</div>
<div style="font-size: 12px; color: #999">{{ q.desc }}</div>
</div>
</n-space>
</n-card>
</n-gi>
</n-grid>
</template>
</n-spin>
</div>
</template>
<script setup>
import { NCard, NGrid, NGi, NStatistic, NSpace, NSpin } from 'naive-ui'
definePageMeta({ layout: 'admin', middleware: 'admin' })
const { adminFetch } = useAdmin()
const stats = ref(null)
const statCards = computed(() => {
if (!stats.value) return []
return [
{ key: 'articles', label: '文章总数', value: stats.value.articles, sub: '新闻 + 博客', to: '/admin/articles' },
{ key: 'reviews', label: '客户评价', value: stats.value.reviews, sub: '来自真实客户', to: '/admin/reviews' },
{ key: 'faqs', label: '常见问答', value: stats.value.faqs, sub: 'FAQ 条目', to: '/admin/faqs' },
{ key: 'gallery', label: '旅拍照片', value: stats.value.gallery, sub: '呼籁旅拍', to: '/admin/gallery' },
{ key: 'stories', label: '客户故事', value: stats.value.stories, sub: '深度体验', to: '/admin/stories' },
{ key: 'submissions', label: '表单提交', value: stats.value.submissions, sub: '联系 + 定制咨询', to: '/admin/submissions' },
{ key: 'siteContent', label: '站点配置', value: stats.value.siteContent, sub: '可编辑配置项' },
]
})
const quickLinks = [
{ title: '产品线管理', desc: '管理所有产品线', to: '/admin/products' },
{ title: '导航菜单', desc: '编辑顶部和底部导航', to: '/admin/navigation' },
{ title: '查看表单', desc: '客户咨询和留言', to: '/admin/submissions' },
{ title: 'SEO 配置', desc: '页面标题和描述', to: '/admin/content/seo' },
]
onMounted(async () => {
try { stats.value = await adminFetch('/api/admin/stats') } catch (e) { console.error('加载统计失败', e) }
})
</script>