/** * 图片压缩工具 * * 与小程序端(src/utils/image.ts)保持相同 API: * 通过阿里云 OSS 的图片处理参数(x-oss-process)实现等比缩放 + 质量压缩, * 适用于商品列表、卡片等缩略图场景。 * * 与小程序端的区别: * 小程序端图片直连 oss.wsdns.cn,ensureFullUrl 直接补全 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 = { width: 300, quality: 90, enabled: true, } /** * 确保图片 URL 为可访问路径 * * 网站端约定: * - 完整 URL(http/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}` }