问题背景:开发与生产环境的图片显示差异
在项目开发过程中,产品详情页的图片加载出现了一个典型的环境差异问题:在开发环境(本地调试)中,图片能正常显示;但当使用 Vite 进行生产打包构建后,详情页的图片却全部无法加载。这一问题直接影响了产品的展示效果,需要快速定位并解决。
问题根源:Vite 构建的资源处理机制与原始逻辑的冲突
要解决这个问题,首先需要理解开发环境与生产环境的核心差异 ------Vite 对静态资源的处理方式。
在开发环境中,Vite 会直接使用资源的原始路径进行加载,此时图片文件的名称(如A_01.jpg)和路径保持不变。而项目原始的图片加载逻辑(productImages.ts)正是基于这种 "固定路径" 设计的:
typescript
php
// 原始逻辑:基于固定路径匹配加载图片
const GatewayImages = import.meta.glob('@Gateway/**/*.jpg', {
eager: true,
import: 'default'
}) as Record<string, string>
const PlusImages = import.meta.glob('@Plus/**/*.jpg', {
eager: true,
import: 'default'
}) as Record<string, string>
这段代码通过import.meta.glob按路径匹配导入图片,并依赖路径中的文件名和文件夹结构进行筛选排序。
但在生产构建时,Vite 为了优化缓存和资源管理,会对静态资源进行哈希化重命名 (如将A_01.jpg重命名为A_01-BB7YyWF4.jpg),这一过程在vite.config.ts中通过配置明确指定:
typescript
css
build: {
rollupOptions: {
output: {
// 静态资源分类打包,包含哈希值
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
...
}
}
},
哈希化后,原始路径中的文件名被修改,导致依赖 "固定路径字符串匹配" 的逻辑彻底失效 ------ 代码无法再通过原始文件名找到对应的图片资源,最终表现为生产环境图片无法显示。
解决方案:重构图片加载逻辑,摆脱路径依赖
要解决这个问题,核心思路是让图片加载逻辑不依赖具体的路径或文件名格式,转而通过 "产品标识" 与 "图片资源" 建立直接映射。具体实现如下:
1. 统一导入所有产品图片
不再按文件夹(@Gateway/@Plus)拆分导入,而是一次性导入所有产品图片,避免路径分割导致的匹配问题:
typescript
php
// 导入所有产品图片(无论所在文件夹)
const allImages = import.meta.glob('/src/assets/images/product/**/*.jpg', {
eager: true,
import: 'default'
}) as Record<string, string>
2. 构建产品图片映射表
创建一个映射结构(ProductImagesMap),将 "产品 ID" 和 "图片尺寸" 直接关联到对应的图片 URL 数组,实现 "按标识查找" 而非 "按路径匹配":
typescript
csharp
// 定义映射结构:产品ID -> 尺寸 -> 图片URL数组
interface ProductImagesMap {
[productId: string]: {
[size: string]: string[]
}
}
const productImagesMap: ProductImagesMap = {}
3. 从路径中提取关键信息,填充映射表
通过正则表达式从图片的原始路径中提取产品系列、尺寸、产品 ID 等核心信息,忽略具体的文件名(因为文件名会被哈希化),只保留稳定的标识信息:
typescript
scss
// 遍历所有图片,构建映射关系
Object.entries(allImages).forEach(([path, url]) => {
// 路径格式示例: /src/assets/images/product/Nano/1000/Plus-CPN-3528A/A_01.jpg
// 正则提取:产品系列、尺寸、产品ID(从文件夹名获取,更稳定)
const match = path.match(//product/([^/]+)/(\d+)/([^/]+)/([^/]+).jpg/)
if (match) {
const [, series, size, productFolder, filename] = match
// 用产品文件夹名作为产品ID(如'Plus-CPN-3528A',不随文件名变化)
const productId = productFolder
// 初始化映射结构(确保层级存在)
if (!productImagesMap[productId]) {
productImagesMap[productId] = {}
}
if (!productImagesMap[productId][size]) {
productImagesMap[productId][size] = []
}
// 将图片URL添加到对应产品ID和尺寸的数组中
productImagesMap[productId][size].push(url)
}
})
4. 保持图片显示顺序的一致性
为了确保图片展示顺序与开发环境一致,可对图片数组进行排序(例如按原始文件名中的序号排序):
typescript
javascript
// 对每个产品、每个尺寸的图片数组排序(示例:按文件名中的数字序号)
Object.values(productImagesMap).forEach(sizeMap => {
Object.values(sizeMap).forEach(images => {
images.sort((a, b) => {
// 从URL中提取原始文件名(忽略哈希部分)
const aName = a.split('/').pop()?.split('-')[0] || ''
const bName = b.split('/').pop()?.split('-')[0] || ''
// 按文件名中的数字排序(如A_01、A_02)
return parseInt(aName.match(/\d+/)?.[0] || '0') - parseInt(bName.match(/\d+/)?.[0] || '0')
})
})
})
方案优势:适配 Vite 构建特性,提升逻辑稳定性
重构后的图片加载逻辑具有以下优势:
- 摆脱路径依赖:不再依赖具体的文件名或路径格式,即使 Vite 对资源重命名(加哈希),也能通过稳定的 "产品 ID" 和 "尺寸" 找到图片。
- 适配构建特性:完全兼容 Vite 的静态资源处理机制,开发 / 生产环境表现一致。
- 逻辑更清晰:通过映射表直接关联 "产品标识" 与 "图片资源",避免复杂的路径字符串匹配,降低维护成本。
- 扩展性更强:新增产品图片时,无需修改加载逻辑,只需按统一目录结构存放即可自动被识别。