- 实现页面详情页功能,支持通过ID动态加载CMS导航内容 - 添加页面标题和内容渲染逻辑,支持多种内容字段候选 - 集成SEO优化,自动设置页面标题、描述和路径信息 - 实现错误处理机制,包括加载失败和页面不存在的情况 - 添加重试和返回首页的功能按钮 - 创建产品详情页基础路由结构(占位文件)
125 lines
3.1 KiB
Vue
125 lines
3.1 KiB
Vue
<template>
|
|
<div class="mx-auto max-w-screen-md px-4 py-12">
|
|
<a-spin v-if="pending" />
|
|
|
|
<a-result
|
|
v-else-if="loadError"
|
|
status="error"
|
|
title="页面加载失败"
|
|
:sub-title="loadError.message"
|
|
>
|
|
<template #extra>
|
|
<a-space>
|
|
<a-button type="primary" @click="refresh()">重试</a-button>
|
|
<a-button @click="navigateTo('/')">返回首页</a-button>
|
|
</a-space>
|
|
</template>
|
|
</a-result>
|
|
|
|
<a-result
|
|
v-else-if="!navigation"
|
|
status="404"
|
|
title="页面不存在"
|
|
sub-title="未找到对应的页面内容。"
|
|
>
|
|
<template #extra>
|
|
<a-space>
|
|
<a-button type="primary" @click="navigateTo('/')">返回首页</a-button>
|
|
</a-space>
|
|
</template>
|
|
</a-result>
|
|
|
|
<template v-else>
|
|
<a-typography-title :level="1" class="!mb-6">{{ pageTitle }}</a-typography-title>
|
|
<a-alert v-if="!pageContent" class="mb-6" type="info" show-icon message="暂无内容" />
|
|
<RichText v-else :content="pageContent" />
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { getCmsNavigation } from '@/api/cms/cmsNavigation'
|
|
import type { CmsNavigation } from '@/api/cms/cmsNavigation/model'
|
|
import { usePageSeo } from '@/composables/usePageSeo'
|
|
|
|
const route = useRoute()
|
|
|
|
const id = computed(() => {
|
|
const raw = route.params.id
|
|
const text = Array.isArray(raw) ? raw[0] : raw
|
|
const n = Number(text)
|
|
return Number.isFinite(n) ? n : NaN
|
|
})
|
|
|
|
const {
|
|
data: navigation,
|
|
pending,
|
|
error: loadError,
|
|
refresh
|
|
} = await useAsyncData<CmsNavigation | null>(
|
|
() => `cms-navigation-${String(route.params.id)}`,
|
|
async () => {
|
|
if (!Number.isFinite(id.value)) return null
|
|
return await getCmsNavigation(id.value)
|
|
},
|
|
{ watch: [id] }
|
|
)
|
|
|
|
function pickString(obj: Record<string, unknown>, key: string) {
|
|
const v = obj[key]
|
|
return typeof v === 'string' ? v.trim() : ''
|
|
}
|
|
|
|
function coerceContent(value: unknown): string {
|
|
if (typeof value === 'string') return value
|
|
if (value && typeof value === 'object') {
|
|
try {
|
|
return JSON.stringify(value)
|
|
} catch {
|
|
return String(value)
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
|
|
const pageTitle = computed(() => {
|
|
const nav = navigation.value as unknown as Record<string, unknown> | null
|
|
if (!nav) return '页面'
|
|
return pickString(nav, 'title') || pickString(nav, 'label') || `页面 ${String(route.params.id)}`
|
|
})
|
|
|
|
const pageContent = computed(() => {
|
|
const nav = navigation.value as unknown as Record<string, unknown> | null
|
|
if (!nav) return ''
|
|
|
|
// Different CMS deployments may store content under different keys.
|
|
const candidates = [
|
|
'content',
|
|
'html',
|
|
'body',
|
|
'text',
|
|
'pageContent',
|
|
'articleContent',
|
|
'suffix',
|
|
'comments',
|
|
'meta',
|
|
'style'
|
|
]
|
|
|
|
for (const k of candidates) {
|
|
const val = nav[k]
|
|
const text = coerceContent(val).trim()
|
|
if (text) return text
|
|
}
|
|
|
|
return ''
|
|
})
|
|
|
|
usePageSeo({
|
|
title: pageTitle.value,
|
|
description: pageContent.value ? pageContent.value.slice(0, 120) : `${pageTitle.value} - 页面内容`,
|
|
path: route.path
|
|
})
|
|
</script>
|
|
|