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

58 行
2.2 KiB
Vue

<template>
<div style="max-width: 900px;">
<n-space justify="space-between" align="center" style="margin-bottom: 20px;">
<div>
<div style="font-size: 20px; font-weight: 600; color: #111827; margin-bottom: 4px;">景点详情</div>
<div style="font-size: 13px; color: #9ca3af;">管理各景点的详细介绍 · 对应官网目的地详情页</div>
</div>
<a href="/destinations" target="_blank">
<n-button>查看前台</n-button>
</a>
</n-space>
<n-spin v-if="!loaded" style="display:block; padding: 60px; text-align: center;" />
<template v-else>
<n-space vertical :size="8">
<NuxtLink
v-for="item in items"
:key="item._index"
:to="`/admin/content/destinations-detail/${item._index}`"
style="text-decoration: none;"
>
<n-card hoverable>
<n-space justify="space-between" align="center">
<div>
<div style="font-size: 15px; font-weight: 600; color: #111827; margin-bottom: 4px;">{{ item.name || '未命名' }}</div>
<div v-if="item.subtitle" style="font-size: 13px; color: #6b7280;">{{ item.subtitle }}</div>
</div>
<n-space align="center">
<n-tag v-if="item.tag" type="success" size="small">{{ item.tag }}</n-tag>
<span style="font-size: 20px; color: #d1d5db;">&rsaquo;</span>
</n-space>
</n-space>
</n-card>
</NuxtLink>
</n-space>
</template>
</div>
</template>
<script setup>
import { NButton, NCard, NSpace, NTag, NSpin } from 'naive-ui'
definePageMeta({ layout: 'admin', middleware: 'admin' })
const { adminFetch } = useAdmin()
const items = ref([]); const loaded = ref(false)
onMounted(async () => {
try {
const res = await adminFetch('/api/admin/site-content?key=destinations-detail')
const data = res.data
const arr = Array.isArray(data) ? data : (data.destinations || [])
items.value = arr.map((d, i) => ({
_index: i, name: d.name || '', subtitle: d.subtitle || '', tag: d.tag || '', id: d.id || '',
}))
} catch {}
loaded.value = true
})
</script>