feat(app): 添加多模板关于我们页面及相关路由和404页面

- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
2026-09-08 12:13:44 +08:00
commit 2b69686795
381 changed files with 59891 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
/**
* 图片压缩工具
*
* 与小程序端(src/utils/image.ts)保持相同 API
* 通过阿里云 OSS 的图片处理参数(x-oss-process)实现等比缩放 + 质量压缩,
* 适用于商品列表、卡片等缩略图场景。
*
* 与小程序端的区别:
* 小程序端图片直连 oss.wsdns.cnensureFullUrl 直接补全 OSS 域名;
* 网站端图片统一走 /api/file/ 代理(代理会透传 query 参数),
* 因此 ensureFullUrl 将相对路径补全为代理地址,压缩参数随之转发到后端 OSS 生效。
*/
/**
* 图片压缩参数配置
*/
export interface ImageCompressOptions {
/** 图片宽度(px),默认 300 */
width?: number
/** 图片质量 0-100,默认 90 */
quality?: number
/** 是否启用压缩,默认 true */
enabled?: boolean
}
/**
* 默认压缩参数
*/
const DEFAULT_OPTIONS: Required<ImageCompressOptions> = {
width: 300,
quality: 90,
enabled: true,
}
/**
* 确保图片 URL 为可访问路径
*
* 网站端约定:
* - 完整 URLhttp/https)原样返回(如直连 OSS 的 oss.wsdns.cn 地址)
* - 相对路径(如 /uploads/xxx.jpg)补全为 /api/file/ 代理地址
*
* @param url 原始图片 URL
* @returns 可访问的图片 URL
*
* @example
* ensureFullUrl('/uploads/xxx.jpg')
* // => '/api/file/uploads/xxx.jpg'
*
* @example
* ensureFullUrl('https://oss.wsdns.cn/xxx.jpg')
* // => 'https://oss.wsdns.cn/xxx.jpg'
*/
export function ensureFullUrl(url: string): string {
if (!url) return ''
if (url.startsWith('http://') || url.startsWith('https://')) {
return url
}
// 相对路径:走 /api/file/ 代理(与 useFileUrl 行为一致)
const cleanPath = url.startsWith('/') ? url.slice(1) : url
return `/api/file/${cleanPath}`
}
/**
* 获取 OSS 压缩后的图片 URL
*
* 给图片 URL 拼接阿里云 OSS 图片处理参数,实现等比缩放 + 质量压缩。
* 使用场景:商品列表、商品卡片等缩略图展示。
*
* 设计要点(前端优先):
* - 以「本次请求的 width/quality」为准重建参数,无论原始 URL 是否已带 x-oss-process。
* 这样即便上游(上传链路)已写入 x-oss-process,前端仍可按需向下压缩,
* 不会出现旧逻辑「已带参数就原样返回、压缩失效」的问题。
* - 仅剥离已有的 x-oss-process 段,保留 URL 上其它查询参数(如有,如 v=2)。
* - 注意:预览原图时不需要调用此函数,直接用原始 URL 即可。
*
* @param url 原始图片 URL
* @param options 压缩选项
* @returns 处理后的图片 URL
*
* @example
* // 默认压缩(宽300,质量90)
* getCompressedImageUrl('/uploads/xxx.jpg')
* // => '/api/file/uploads/xxx.jpg?x-oss-process=image/resize,w_300/quality,Q_90'
*
* @example
* // 已带 x-oss-process 的图,仍以本次请求为准向下压缩
* getCompressedImageUrl('https://oss.x/x.jpg?x-oss-process=image/resize,w_1600', { width: 800 })
* // => 'https://oss.x/x.jpg?x-oss-process=image/resize,w_800/quality,Q_90'
*
* @example
* // 关闭压缩
* getCompressedImageUrl('/uploads/xxx.jpg', { enabled: false })
* // => '/api/file/uploads/xxx.jpg'
*/
export function getCompressedImageUrl(url: string, options?: ImageCompressOptions): string {
const { width, quality, enabled } = { ...DEFAULT_OPTIONS, ...options }
// 先补全为可访问路径
const fullUrl = ensureFullUrl(url)
if (!fullUrl) return ''
// 未启用压缩,原样返回(仍走 ensureFullUrl 补全)
if (!enabled) return fullUrl
// 前端优先:剥离已有的 x-oss-process 段,用本次请求的宽/质重建
const base = fullUrl.split(/[?&]x-oss-process=/)[0]
// 保留 base 上其它查询参数,按需用 ? 或 & 拼接
const separator = base.includes('?') ? '&' : '?'
return `${base}${separator}x-oss-process=image/resize,w_${width}/quality,Q_${quality}`
}