feat(pages): 添加页面详情页和产品详情页路由
- 实现页面详情页功能,支持通过ID动态加载CMS导航内容 - 添加页面标题和内容渲染逻辑,支持多种内容字段候选 - 集成SEO优化,自动设置页面标题、描述和路径信息 - 实现错误处理机制,包括加载失败和页面不存在的情况 - 添加重试和返回首页的功能按钮 - 创建产品详情页基础路由结构(占位文件)
This commit is contained in:
124
app/pages/page/[id].vue
Normal file
124
app/pages/page/[id].vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user