- 后台全面引入 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>
58 行
2.2 KiB
Vue
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;">›</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>
|