在前端业务开发中,ZIP 压缩包上传后在线预览 是非常常见的需求(比如附件管理、文档预览、图片包查看)。本文基于Vue3 + TypeScript + Element Plus,实现了一套完整的 ZIP 压缩包预览功能:
✅ 解析 ZIP 内部文件列表
✅ 支持 ZIP 内图片 / 文档在线预览
✅ 支持 ZIP 内图片一键插入到编辑器
✅ 弹窗拖拽 + 缩放,交互更友好
✅ 全程无刷新,纯前端解析,体验流畅
一、整体实现思路
- 父页面点击预览 ,判断文件是否为
.zip格式; - 若是 ZIP,打开专属预览弹窗组件,传递文件 ID;
- 组件加载
zip.js库,请求 ZIP 文件流并前端解析; - 渲染 ZIP 内部文件列表,支持二次预览、图片插入;
- 关闭弹窗自动清空数据,避免内存泄漏。
二、父页面代码(调用层)
父页面负责判断文件类型 、控制弹窗显示 、传递事件。
1. 预览按钮(表格行操作)
javascript
<el-button link type="primary" @click="showAttachment(scope.row)"> 预览 </el-button>
2. 点击预览逻辑(判断是否为 ZIP)
javascript
// 预览附件
const showAttachment = (fileInfo) => {
// 正则判断文件名是否为 .zip 格式(不区分大小写)
const isZip = /\.(zip)$/i.test(fileInfo.fileName)
// 如果是ZIP,打开ZIP预览弹窗
if (isZip) {
zipFileId.value = fileInfo.fileId
zipDiaglogVisible.value = true
return
}
// 非ZIP,执行普通文件预览
emit('showAttachment', fileInfo)
}
// 关闭ZIP弹窗
const closeZipDiaglog = () => {
zipDiaglogVisible.value = false
}
// 预览ZIP内部文件(透传给普通预览)
const showZipEntry = (fileInfo) => {
emit('showAttachment', fileInfo)
}
3. 引入 ZIP 预览组件
javascript
<!-- ZIP预览弹窗组件 -->
<PopupZip
:zipFileId="zipFileId"
:diaglogVisible="zipDiaglogVisible"
@closeDiaglogVisible="closeZipDiaglog"
@showAttachment="showZipEntry"
/>
三、核心组件:PopupZip 压缩包预览组件
这是完整可运行的组件代码,我会拆分模块 + 逐行解读,方便你直接使用。
1. 模板部分(Dialog + 文件列表)
javascript
<template>
<!-- 可拖拽缩放的对话框 -->
<el-dialog
width="600px"
class="ba-operate-dialog attachment-list-dialog"
:close-on-click-modal="false"
:model-value="diaglogVisible"
@close="closeDiaglog"
:destroy-on-close="true"
>
<template #header>
<!-- 自定义拖拽标题 -->
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
压缩文件列表
</div>
</template>
<!-- 文件列表 -->
<div class="attachment-wrapper" style="width: 100%">
<el-table :data="fileList" border style="width: 100%; height: 320px">
<!-- 文件名 -->
<el-table-column prop="fileName" label="附件" show-overflow-tooltip />
<!-- 操作列:预览 + 插入 -->
<el-table-column fixed="right" label="操作" width="110">
<template #default="scope">
<!-- 预览按钮:仅支持可预览文件 -->
<el-button
:disabled="!canPreviewFile(scope.row.fileName)"
link
type="primary"
@click="showAttachment(scope.row)"
>
预览
</el-button>
<!-- 插入按钮:仅支持图片 -->
<el-button
:disabled="!canCopyFile(scope.row.fileName)"
link
type="primary"
@click="copyAttachment(scope.row)"
>
插入
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</template>
2. 脚本部分(核心逻辑 + TS 类型)
javascript
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { saveAs } from 'file-saver'
import { downloadMinioFile } from '/@/api/backend/project'
import { canPreviewFile, canCopyFile, dynamicLoadJs } from '/@/utils/common'
import { ElMessage } from 'element-plus'
// 1. TS 类型定义(规范入参)
interface Props {
diaglogVisible: boolean // 控制弹窗显示
zipFileId: string // ZIP文件ID
}
// 2. 定义Props + 默认值
const props = withDefaults(defineProps<Props>(), {
diaglogVisible: false,
})
// 3. 自定义事件(向父组件同步状态)
const emit = defineEmits(['closeDiaglogVisible', 'showAttachment'])
// 存储ZIP解析后的文件列表
const fileList = ref<any[]>([])
核心方法 1:关闭弹窗
javascript
// 关闭弹窗,通知父组件隐藏
const closeDiaglog = () => {
emit('closeDiaglogVisible', false)
}
核心方法 2:预览 ZIP 内部文件
javascript
// 预览ZIP内的文件(图片/PDF/文档)
const showAttachment = async (fileInfo) => {
const { entry, fileName } = fileInfo
// 判断文件是否支持预览
const isPreview = canPreviewFile(fileName)
const isPdf = /\.(pdf)$/i.test(fileName)
if (!isPreview) return
// 区分PDF与普通文件,创建对应Blob写入器
const tmp = isPdf ? new zip.BlobWriter('application/pdf') : new zip.BlobWriter()
// 从ZIP条目中提取文件流 → 转成URL → 触发预览
const blob = await entry.getData(tmp)
const url = URL.createObjectURL(blob)
// 向父组件抛出自定义预览事件
emit('showAttachment', {
fileId: '',
fileUrl: url,
fileType: isPdf ? 'pdf' : '',
fileName,
})
}
核心方法 3:ZIP 内图片一键插入编辑器
javascript
// 向父窗口发送消息,插入图片(适用于富文本编辑器)
const insertImageIntoDoc = async (imageData: any) => {
const payload = JSON.stringify({
action: 'insertImage',
imageData: imageData,
})
// 跨窗口通信(iframe/父页面)
window.parent.postMessage(payload, '*')
}
// 复制/插入图片
const copyAttachment = async (fileInfo: any) => {
const { entry, fileName } = fileInfo
// 仅支持图片格式
const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(fileName)
if (!isImage) {
ElMessage.warning('目前仅支持复制图片格式文件')
return
}
try {
// 从ZIP中提取图片Blob
const blob = await entry.getData(new zip.BlobWriter())
// 转Base64并插入编辑器
const reader = new FileReader()
reader.onload = () => insertImageIntoDoc(reader.result)
reader.readAsDataURL(blob)
} catch (err) {
console.error('复制失败:', err)
}
}
核心方法 4:动态加载 zip.js + 解析 ZIP 文件
javascript
// 解压并解析ZIP文件
const unZipFile = async () => {
if (!props.zipFileId) return
// 动态加载zip.js(避免首屏加载过大)
await dynamicLoadJs('./libs/zip.js/zip.min.js')
try {
// 1. 请求后端获取ZIP文件流
const res = await downloadMinioFile(props.zipFileId)
// 2. 创建ZIP读取器
const reader = new zip.ZipReader(new zip.BlobReader(res.data))
// 3. 获取所有文件条目
const entries = await reader.getEntries()
// 4. 格式化数据,渲染表格
fileList.value = entries.map((entry) => ({
fileName: entry.filename,
entry: entry, // 保存条目对象,用于后续提取文件
}))
} catch (err) {
console.error('ZIP解析失败', err)
}
}
监听弹窗状态(自动加载 / 清空)
javascript
// 监听弹窗显示:打开则解析ZIP,关闭则清空数据
watch(
() => props.diaglogVisible,
(val) => {
if (!val) {
fileList.value = [] // 关闭清空,防止数据残留
return
}
unZipFile() // 打开弹窗自动解析ZIP
}
)
</script>
四、关键代码深度解读(必看!)
1. ZIP 文件前端解析原理
- 使用
zip.js纯前端解析 ZIP,无需后端参与; - 通过
BlobReader读取文件流,ZipReader解析条目; - 每个文件对应一个
entry对象,保存了文件名、大小、数据流。
2. 文件预览实现
- 调用
entry.getData()从 ZIP 中提取文件; - 转成
Blob→ 生成临时 URLURL.createObjectURL(); - 抛给父组件执行预览(支持图片 / PDF / 文档)。
3. 图片插入编辑器
- 读取图片
Blob→ 转base64; - 通过
postMessage跨窗口通信,把图片插入富文本 / 编辑器; - 兼容主流编辑器(UEditor、TinyMCE、自定义编辑器)。
4. 性能与安全
- 动态加载
zip.js,不占用主包体积; - 关闭弹窗自动清空
fileList,防止内存泄漏; - 仅解析文件列表,不占用过多内存。
五、使用说明 & 依赖准备
- 下载 zip.js 放到
public/libs/zip.js/目录; - 实现工具函数:
canPreviewFile:判断文件是否支持预览canCopyFile:判断是否为图片dynamicLoadJs:动态加载 JS
- 替换文件下载接口
downloadMinioFile为你项目的下载 API; - 直接引入组件即可使用。
六、效果总结
这套方案在后台管理系统、文档管理、附件预览场景中非常实用,具备以下优势:
✅ 纯前端解析,速度快
✅ 支持二次预览、图片插入
✅ 交互友好(拖拽 + 缩放)
✅ 代码解耦,可直接复用
✅ TS 加持,类型安全,维护简单
如果你需要预览更多格式(Word/Excel),也可以基于这套代码扩展!
总结
- 本文实现了Vue3 + TS + Element Plus的 ZIP 在线预览完整方案;
- 核心是使用
zip.js前端解析压缩包,提取文件流实现预览 / 插入; - 代码结构清晰、注释完善、可直接落地业务;
- 支持预览、图片插入、弹窗拖拽,交互体验拉满。