2b69686795
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
201 lines
7.1 KiB
TypeScript
201 lines
7.1 KiB
TypeScript
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: '内容接口暂时不可用,请稍后再试' }
|
||
}
|
||
})
|