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: '内容接口暂时不可用,请稍后再试' } } })