症状:企业微信端打开拖拽上传弹窗时,图片没有显示预览,而是显示红色占位图标
对比:
企业微信WebView的CSP策略不允许加载base64格式的data URL图片:
Content-Security-Policy: img-src 'self' blob: https:
原代码问题:
// ❌ 使用FileReader生成base64 dataURL
reader.readAsDataURL(uploadFile.file);
// 结果:data:image/jpeg;base64,/9j/4AAQ... (被CSP阻止)
浏览器控制台错误:
Refused to load the image 'data:image/jpeg;base64,...' because it violates the following Content Security Policy directive: "img-src 'self' blob: https:"
在生成预览时检测运行环境:
const isWxWork = this.isWxWorkEnvironment();
ObjectURL方案:
if (isWxWork) {
// 🔥 直接创建ObjectURL(更快、更可靠、符合CSP)
const objectUrl = URL.createObjectURL(uploadFile.file);
uploadFile.preview = objectUrl;
// 结果:blob:http://app.fmode.cn/12345678-abcd-... ✅
}
优势:
blob:协议)保持原有方案:
else {
// 🔥 使用FileReader生成base64(兼容性更好)
reader.readAsDataURL(uploadFile.file);
// 结果:data:image/jpeg;base64,/9j/4AAQ... ✅
}
优势:
问题:ObjectURL会占用内存,需要手动释放
// ⚠️ 不释放会导致内存泄漏
URL.createObjectURL(file); // 创建
// ... 使用 ...
URL.revokeObjectURL(url); // 必须释放 ❗
1. 弹窗关闭时清理:
closeModal(): void {
this.cleanupObjectURLs(); // 🧹 清理所有ObjectURL
this.close.emit();
}
cancelUpload(): void {
this.cleanupObjectURLs(); // 🧹 清理所有ObjectURL
this.cancel.emit();
}
2. 组件销毁时清理:
ngOnDestroy(): void {
console.log('🧹 组件销毁,清理ObjectURL资源...');
this.cleanupObjectURLs();
}
3. 清理方法实现:
private cleanupObjectURLs(): void {
this.uploadFiles.forEach(file => {
if (file.preview && file.preview.startsWith('blob:')) {
try {
URL.revokeObjectURL(file.preview);
} catch (error) {
console.error(`❌ 释放ObjectURL失败: ${file.name}`, error);
}
}
});
}
文件:drag-upload-modal.component.ts
修改1:添加OnDestroy接口
// Line 1
import { ..., OnDestroy, ... } from '@angular/core';
// Line 72
export class DragUploadModalComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
修改2:智能预览生成
// Lines 214-287
private generatePreview(uploadFile: UploadFile): Promise<void> {
return new Promise((resolve, reject) => {
try {
// 🔥 企业微信环境检测
const isWxWork = this.isWxWorkEnvironment();
if (isWxWork) {
// 🔥 企业微信环境:直接使用ObjectURL
const objectUrl = URL.createObjectURL(uploadFile.file);
uploadFile.preview = objectUrl;
console.log(`✅ 图片预览生成成功 (ObjectURL): ${uploadFile.name}`);
resolve();
} else {
// 🔥 非企业微信环境:使用base64 dataURL
const reader = new FileReader();
reader.onload = (e) => {
uploadFile.preview = e.target?.result as string;
console.log(`✅ 图片预览生成成功 (Base64): ${uploadFile.name}`);
resolve();
};
reader.readAsDataURL(uploadFile.file);
}
} catch (error) {
console.error(`❌ 图片预览生成失败: ${uploadFile.name}`, error);
resolve();
}
});
}
修改3:添加清理方法
// Lines 556-569
private cleanupObjectURLs(): void {
this.uploadFiles.forEach(file => {
if (file.preview && file.preview.startsWith('blob:')) {
try {
URL.revokeObjectURL(file.preview);
} catch (error) {
console.error(`❌ 释放ObjectURL失败: ${file.name}`, error);
}
}
});
}
修改4:关闭时清理
// Line 542-554
cancelUpload(): void {
this.cleanupObjectURLs();
this.cancel.emit();
}
closeModal(): void {
this.cleanupObjectURLs();
this.close.emit();
}
修改5:销毁时清理
// Lines 1199-1205
ngOnDestroy(): void {
console.log('🧹 组件销毁,清理ObjectURL资源...');
this.cleanupObjectURLs();
}
现有代码已正确:
<!-- Line 52-60: 缩略图显示 -->
<img
[src]="file.fileUrl || file.preview" ← 使用preview字段
[alt]="file.name"
class="file-thumbnail"
(click)="viewFullImage(file)"
(error)="onImageError($event, file)" />
<!-- Line 167: 图片查看器 -->
<img [src]="viewingImage.preview" [alt]="viewingImage.name" class="full-image" />
📎 文件:test.jpg
🖼️ 预览生成:data:image/jpeg;base64,/9j/4AAQ...
❌ CSP拦截:Refused to load the image
🔴 显示:红色占位图标
📎 文件:test.jpg
🖼️ 预览生成:blob:http://app.fmode.cn/12345678-abcd-...
✅ CSP通过:允许加载blob协议
🖼️ 显示:真实图片缩略图
✅ 点击:可查看完整大图
🧹 关闭:自动释放内存
# 构建项目
ng build yss-project --base-href=/dev/yss/
# 部署
.\deploy.ps1
🖼️ 开始为 test.jpg 生成预览
✅ 图片预览生成成功 (ObjectURL): test.jpg
objectUrl: blob:https://app.fmode.cn/12345678-abcd-...
environment: wxwork
📸 图片预览生成完成
确保非企业微信环境仍然正常工作:
| 方案 | 生成速度 | 内存占用 | CSP兼容 | 需要清理 |
|---|---|---|---|---|
| Base64 | 慢(需编码) | 大(+33%) | ❌ 企微不兼容 | ❌ 不需要 |
| ObjectURL | 快(直接引用) | 小(原始大小) | ✅ 企微兼容 | ✅ 需要手动释放 |
示例(5MB图片):
问题:ObjectURL会不会泄露文件? 答案:不会,ObjectURL是本地引用
原理:
blob:https://app.fmode.cn/12345678-abcd-...
↑ ↑
同源限制 随机ID(浏览器生成)
特点:
企业微信允许的图片来源:
img-src 'self' blob: https:
↑ ↑ ↑
同源 Blob HTTPS
检查步骤:
ObjectURL 而不是 Base64可能原因:
检查步骤:
🧹 组件销毁,清理ObjectURL资源...cleanupObjectURLs()解决方案:
ngOnDestroycloseModal() 和 cancelUpload() 调用了清理方法检查步骤:
Base64 方案isWxWorkEnvironment() 返回值可能原因:
修复时间:2025-11-29
修复人员:开发团队
文档版本:v1.0