Files
hjc-web/server/api/page/detail.get.ts
T
gxwebsoft 2b69686795 feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
2026-09-08 12:13:44 +08:00

201 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { $fetch } from 'ofetch'
import { createError, defineEventHandler, getQuery } from 'h3'
import { useRuntimeConfig } from '#imports'
import { getTenantFromContext } from '../../utils/tenant'
/**
* 页面详情
*
* 支持两种入口:
* 1. GET /api/page/detail?navigationId=4296 → CMS 导航节点(model=page+ cms_design 内容(旧机制)
* 2. GET /api/page/detail?path=about → 新版「单页管理」cms_page(按 slug 取已发布单页)
*
* 与 website-admin 的「单页管理」模块对应:后端 mp-java CmsPageController.getByPath。
* cms-api 侧已按 status=1(已发布)过滤,未发布/不存在统一返回 data=null。
*
* 返回结构(始终为对象,前端用 status 区分):
* {
* status: 'ok' | 'empty' | 'error',
* pageId, title, path, model,
* content, layout, photo,
* keywords, description,
* hasContent, updateTime, message?
* }
*/
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const ctx = getTenantFromContext(event, config)
const query = getQuery(event)
const modulesApiBase = config.public.modulesApiBase as string
const navigationId = query.navigationId || query.id
const path = query.path
const base = {
status: 'empty' as const,
pageId: 0,
title: '',
path: (typeof path === 'string' ? path : '') || '',
model: 'page',
parentId: 0,
content: '',
layout: null as any,
photo: null as string | null,
keywords: '',
description: '',
hasContent: false,
updateTime: null as string | null,
message: '该页面不存在或未发布'
}
// ===== 分支2:按 path 取新版单页(cms_page =====
if (typeof path === 'string' && path.trim()) {
try {
const res = await $fetch<{
code?: number
message?: string
data?: {
pageId?: number
title?: string
path?: string
content?: string | null
keywords?: string | null
description?: string | null
image?: string | null
status?: number
updateTime?: string | null
/** 附件列表:后端存 JSON 字符串(cms_page.attachments),也可能是已解析的数组 */
attachments?: unknown
} | null
}>(`/cms/cms-page/getByPath/${encodeURIComponent(path.trim())}`, {
baseURL: modulesApiBase,
headers: { TenantId: ctx.tenantId },
timeout: 10000,
retry: 1
})
const node = res?.data
if (!node) {
const code = res?.code
const isBizError = typeof code === 'number' && code !== 0 && code !== 200
if (isBizError) {
return { ...base, status: 'error' as const, message: res?.message || '上游接口返回异常' }
}
return { ...base, status: 'empty' as const, message: '该单页不存在或未发布' }
}
const content = typeof node.content === 'string' ? node.content : ''
const hasContent = content.trim().length > 0
// 解析附件列表(cms_page.attachments):后端存 JSON 字符串,容错解析
let attachments: Array<{ name: string; url: string; size?: number; ext?: string; sort?: number }> | undefined
try {
const raw = node.attachments
if (raw) {
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw
if (Array.isArray(parsed)) {
attachments = parsed
.filter((it: any) => it && typeof it.url === 'string' && typeof it.name === 'string')
.map((it: any) => ({
name: String(it.name),
url: String(it.url),
size: typeof it.size === 'number' ? it.size : undefined,
ext: typeof it.ext === 'string' ? it.ext : undefined,
sort: typeof it.sort === 'number' ? it.sort : undefined
}))
}
}
} catch (e) {
console.error(`[page/detail] parse attachments failed: path=${path}`, (e as any)?.message || e)
}
return {
status: hasContent ? ('ok' as const) : ('empty' as const),
pageId: node.pageId ?? 0,
title: node.title || '',
path: node.path || path,
model: 'page',
parentId: 0,
content,
layout: null,
photo: node.image ?? null,
keywords: node.keywords || '',
description: node.description || '',
hasContent,
updateTime: node.updateTime || null,
attachments,
message: hasContent ? undefined : '该页面正文尚未在 CMS 管理后台录入'
}
} catch (error: any) {
console.error(`[page/detail] upstream(cms-page) failed: path=${path} tenant=${ctx.tenantId}`, error?.message || error)
return { ...base, status: 'error' as const, message: '内容接口暂时不可用,请稍后再试' }
}
}
// ===== 分支1:按 navigationId 取导航节点(旧机制,保持原逻辑) =====
if (!navigationId) {
throw createError({ statusCode: 400, statusMessage: 'Missing navigationId or path parameter' })
}
const navId = Number(navigationId)
try {
const res = await $fetch<{
code?: number
message?: string
data?: {
navigationId?: number
title?: string
model?: string
parentId?: number
updateTime?: string | null
design?: {
content?: string | null
layout?: any
photo?: string | null
updateTime?: string | null
createTime?: string | null
} | null
} | null
}>(`/cms/cms-navigation/${navId}`, {
baseURL: modulesApiBase,
headers: { TenantId: ctx.tenantId },
timeout: 10000,
retry: 1
})
const node = res?.data
if (!node) {
const code = res?.code
const isBizError = typeof code === 'number' && code !== 0 && code !== 200
if (isBizError) {
return { ...base, navigationId: navId, message: res?.message || '上游接口返回异常' }
}
return { ...base, status: 'empty' as const, navigationId: navId, message: '该导航节点在 CMS 中不存在或未发布' }
}
const design = node.design || {}
const content = typeof design.content === 'string' ? design.content : ''
const hasContent = content.trim().length > 0
const updateTime = design.updateTime || design.createTime || node.updateTime || null
return {
status: hasContent ? ('ok' as const) : ('empty' as const),
navigationId: node.navigationId ?? navId,
title: node.title || '',
model: node.model || 'page',
parentId: node.parentId || 0,
content,
layout: design.layout ?? null,
photo: design.photo ?? null,
keywords: '',
description: '',
hasContent,
updateTime,
message: hasContent ? undefined : '该页面正文尚未在 CMS 管理后台录入'
}
} catch (error: any) {
console.error(`[page/detail] upstream(navigation) failed: navigationId=${navigationId} tenant=${ctx.tenantId}`, error?.message || error)
return { ...base, status: 'error' as const, navigationId: navId, message: '内容接口暂时不可用,请稍后再试' }
}
})