- 后台全面引入 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>
69 行
3.1 KiB
Vue
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>
|