Files
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

129 lines
4.9 KiB
TypeScript
Raw Permalink 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'
/**
* 获取轮播图列表
* GET /api/banner
*
* 真实数据结构为「分组(cms_banner_group) + 明细(cms_banner_item)」:
* - 明细(item)内嵌在分组的 items[] 中返回,单独 /cms/cms-banner-item/page 接口不存在(404)
* - 旧的扁平接口 /cms/cms-banner/page 对本仓库对接的租户无数据(已废弃)。
*
* 本接口代理 /cms/cms-banner-group/page,按「启用分组 + 未删除明细」扁平化后,
* 映射成前端 CmsBanner 结构返回。支持:
* - position 查询参数:只取对应位置的轮播(如 home_slider),缺省返回全部启用分组;
* - 时间与状态过滤:分组 status=1、未删除、且在 [startTime,endTime] 窗口内(若设置)才展示。
*/
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const ctx = getTenantFromContext(event, config)
const query = getQuery(event)
const modulesApiBase = config.public.modulesApiBase as string
// position 由本接口自行过滤,不往上游透传(避免 CMS 无此参数导致异常)
const position = (query.position as string | undefined) || undefined
const { position: _omit, ...upstreamQuery } = query
try {
const res: any = await $fetch('/cms/cms-banner-group/page', {
baseURL: modulesApiBase,
headers: {
TenantId: ctx.tenantId
},
query: {
page: 1,
limit: 50,
...upstreamQuery
}
})
const groups: any[] = res?.data?.list ?? res?.list ?? []
const now = Date.now()
// 1) 筛选启用分组
const enabledGroups = groups.filter((g) => {
if (g.deleted === 1) return false
if (g.status !== 1) return false
if (position && g.position !== position) return false
// 时间窗口:startTime/endTime 任一为空则视为不限;两者都有时需在窗口内
// ⚠️ CMS 时间字符串为「中国时间(Asia/Shanghai, UTC+8)」,必须显式按 +08:00 解析,
// 否则 dev/server 在 UTC 时区下会把窗口整体算错(实测曾把在用分组误判为未生效)。
if (g.startTime && g.endTime) {
const start = parseCmsTime(g.startTime) // 显式按 UTC+8 解析(中国时间)
const end = parseCmsTime(g.endTime) // 显式按 UTC+8 解析(中国时间)
if (start !== null && end !== null && (now < start || now > end)) {
return false
}
}
return true
})
// 2) 扁平化明细并过滤未删除项
const list: any[] = []
for (const g of enabledGroups) {
const items: any[] = Array.isArray(g.items) ? g.items : []
for (const it of items) {
if (it.deleted === 1) continue
list.push({
// 兼容前端 id 字段
id: it.itemId ?? it.id,
itemId: it.itemId,
groupId: it.groupId ?? g.groupId,
// 图片地址(优先 image,兼容其它字段名)
image: it.image || it.imageUrl || it.pic || it.url || '',
imageUrl: it.imageUrl || it.image,
pic: it.pic,
url: it.url,
// 文案
title: it.title || g.title || '',
subtitle: it.subtitle || '',
// 跳转(linkType: 0=无 1=外链 2=站内)
link: it.linkUrl || '',
linkUrl: it.linkUrl || '',
linkType: it.linkType ?? 0,
linkTargetId: it.linkTargetId ?? null,
// 排序(组内 sortNum);分组 sortNum 作为兜底
sortNum: it.sortNum ?? g.sortNum ?? 0,
sortOrder: it.sortNum ?? g.sortNum ?? 0,
// 状态/删除标记:沿用分组状态,前端按「非 2 即显示、deleted!==1」处理
status: it.status ?? g.status ?? 1,
deleted: it.deleted ?? 0
})
}
}
// 3) 按 sortNum 升序
list.sort((a, b) => (a.sortNum ?? 0) - (b.sortNum ?? 0))
return {
code: res?.code ?? 0,
message: res?.message ?? '操作成功',
data: {
list,
count: list.length
}
}
} catch (error: any) {
throw createError({
statusCode: error?.statusCode || error?.response?.status || 502,
statusMessage: error?.statusMessage || 'Failed to fetch banners'
})
}
})
/**
* 将 CMS 时间字符串(如 "2026-07-30 12:14:47",中国时间 UTC+8)解析为毫秒时间戳。
* 显式按 Asia/Shanghai 解析,避免 server 处于 UTC 时区时窗口计算错误。
* 解析失败返回 null。
*/
function parseCmsTime(s?: string | null): number | null {
if (!s) return null
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})/)
if (!m) return null
const [, Y, Mo, D, h, mi, sec] = m
// 视为 UTC+8:先按本地组件构造,再减 8 小时换算成 UTC 时间戳
return Date.UTC(+Y, +Mo - 1, +D, +h - 8, +mi, +sec)
}