在微信小程序开发中,文件处理是常见需求,尤其是涉及合同、文档等场景。本文将通过一个实际案例,详细讲解如何实现文件的下载、解压、列表展示及预览功能。
功能概述
该页面主要实现了以下核心功能:
- 列表展示可下载的文件信息
- 支持 ZIP 文件下载与解压
- 解压后文件列表展示
- 多种类型文件预览(图片、文档等)
- 分页加载列表数据
核心代码实现
页面结构(Template)
javascript
<template>
<view class="contractClass">
<!-- 滚动列表区域 -->
<scroll-view scroll-y class="scrollClass" @scrolltolower="handleToLower">
<view class="contentClass">
<!-- 文件列表项 -->
<view class="contentItemClass" v-for="(item,index) in bookList" :key="index">
<view class="headClass">
{{ item.state_text }}
</view>
<van-divider />
<!-- 操作按钮区 -->
<view class="buttonClass">
<view class="downloadClass" @click="downloadFile(item)">
下载文件并解压
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 解压文件列表弹窗 -->
<van-popup :show="fileShow" round position="bottom">
<view class="fileHeaderClass">
<view></view>
<view>解压文件列表</view>
<uni-icons type="closeempty" @click="closeFn"></uni-icons>
</view>
<scroll-view scroll-y class="filesListClass">
<view v-for="(item , index) in files" :key="index" class="fileItemClass" @click="previewFn(item)">
{{item}}
</view>
</scroll-view>
</van-popup>
</view>
</template>
逻辑处理(Script)
javascript
<script>
export default {
data() {
return {
// 分页数据
pageData: {
page: 1,
pageSize: 10,
total: 0
},
// 文件列表数据
bookList: [],
// 弹窗显示控制
fileShow: false,
// 文件系统管理器
FileSystemManager: '',
// 支持预览的文档类型
fileTypeArr: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'],
// 解压后的文件列表
files: [],
// 当前操作的文件信息
fileObj: {
fileName: ''
},
}
},
onShow() {
// 页面显示时获取列表数据
this.getList();
},
methods: {
/**
* 预览文件
* @param {string} item - 文件名
*/
previewFn(item) {
// 获取文件类型
const fileType = this.onchangecb(item);
// 构建文件完整路径
const fullPath = `${wx.env.USER_DATA_PATH}/extracted/${this.fileObj.fileName}/${item}`;
// 图片类型直接预览
if (this.isImageFile(item)) {
this.previewMediaFn(fullPath);
}
// 支持的文档类型直接打开
else if (this.fileTypeArr.some(type => type === fileType)) {
this.openDocumentFn(fullPath);
}
// 处理目录情况
else {
this.FileSystemManager.stat({
path: fullPath,
success: (statRes) => {
if (statRes.stats.isDirectory()) {
// 如果是目录,读取目录下的文件
this.FileSystemManager.readdir({
dirPath: fullPath,
success: (readRes) => {
if (readRes.files && readRes.files.length > 0) {
// 递归处理目录下的第一个文件
const firstFile = readRes.files[0];
this.previewFn(`${item}/${firstFile}`);
} else {
uni.showToast({
title: this.$t('invoicePages.dirEmpty'),
icon: 'none',
duration: 2000
});
}
},
fail: () => {
uni.showToast({
title: this.$t('invoicePages.nosee'),
icon: 'none',
duration: 2000
});
}
});
} else {
// 不支持的文件类型
uni.showToast({
title: this.$t('invoicePages.nosee'),
icon: 'none',
duration: 2000
});
}
},
fail: () => {
uni.showToast({
title: this.$t('invoicePages.fileNotFound'),
icon: 'none',
duration: 2000
});
}
});
}
},
/**
* 关闭文件列表弹窗
*/
closeFn() {
this.fileShow = false;
this.removeSavedFileFn();
this.fileObj.fileName = '';
},
/**
* 获取文件列表数据
* @param {string} e - 区分是否是分页加载
*/
async getList(e) {
const data = {};
const res = await this.userService.getBusinessList(data);
if (res.code === 1) {
// 分页加载时合并数据,否则直接替换
if (e === 'paging') {
this.bookList = [...this.bookList, ...res.data.data];
} else {
this.bookList = res.data.data;
}
this.pageData.total = res.data.total || 0;
}
},
/**
* 处理滚动到底部事件(分页加载)
*/
handleToLower() {
let paginationTotal = 0;
// 计算总页数
if (this.pageData.total % 10 === 0) {
paginationTotal = Math.floor(this.total / 10)
} else {
paginationTotal = Math.ceil(this.total / 10)
};
// 如果还有下一页,加载更多数据
if (this.pageData.page < paginationTotal) {
this.pageData.page = this.pageData.page + 1;
this.getList('paging');
}
},
/**
* 获取文件类型
* @param {string} e - 文件名
* @returns {string} 文件扩展名
*/
onchangecb(e) {
const index = e.lastIndexOf(".");
const ext = e.substr(index + 1);
return ext;
},
/**
* 判断是否为图片文件
* @param {string} filename - 文件名
* @returns {boolean} 是否为图片
*/
isImageFile(filename) {
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'];
const extension = filename.split('.').pop().toLowerCase();
return imageExtensions.includes(extension);
},
/**
* 下载文件
* @param {object} item - 文件信息对象
*/
downloadFile(item) {
const that = this;
const fileType = this.onchangecb(item.attachment);
// 处理ZIP文件
if (fileType == 'zip') {
this.FileSystemManager = uni.getFileSystemManager();
uni.showLoading({
title: "加载中...",
mask: false
});
// 下载ZIP文件
uni.downloadFile({
url: item.attachment,
success: res => {
// 下载成功后解压
that.unzipHandler(res.tempFilePath);
},
fail: res => {
uni.hideLoading();
},
})
}
// 处理文档类型
else if (this.fileTypeArr.some(type => type == fileType)) {
uni.downloadFile({
url: item.attachment,
success: (res) => {
that.openDocumentFn(res.tempFilePath);
},
});
}
// 处理图片类型
else if(this.isImageFile(item)){
uni.downloadFile({
url: item.attachment,
success: (res) => {
that.previewMediaFn(res.tempFilePath);
},
});
}
// 不支持的文件类型
else {
wx.showToast({
title: this.$t('invoicePages.nosee'),
icon: 'none',
duration: 2000,
mask: true,
});
}
},
/**
* 解压文件
* @param {string} bookZipPath - ZIP文件路径
*/
unzipHandler(bookZipPath) {
console.log('解压文件')
let { FileSystemManager } = this;
let that = this;
FileSystemManager.unzip({
zipFilePath: bookZipPath,
targetPath: `${wx.env.USER_DATA_PATH}/extracted`, // 解压目标路径
success(res) {
// 解压成功后获取文件列表
that.lookFileListFn();
},
})
},
/**
* 获取解压后的文件列表
*/
lookFileListFn() {
let { FileSystemManager } = this;
let that = this;
FileSystemManager.readdir({
dirPath: `${wx.env.USER_DATA_PATH}/extracted`,
success(res) {
// 记录文件夹名称
that.fileObj.fileName = res.files[0];
// 获取文件夹内文件列表
that.lookFileListFn1();
},
fail(err) {
// 处理错误
}
})
},
/**
* 获取指定文件夹内的文件列表
*/
lookFileListFn1() {
let { FileSystemManager } = this;
let that = this;
FileSystemManager.readdir({
dirPath: `${wx.env.USER_DATA_PATH}/extracted/${that.fileObj.fileName}`,
success(res) {
// 保存文件列表并显示弹窗
that.files = res.files;
that.fileShow = true;
uni.hideLoading();
},
fail(err) {
// 处理错误
}
})
},
/**
* 打开文档
* @param {string} attachment - 文件路径
*/
openDocumentFn(attachment) {
uni.openDocument({
filePath: attachment,
showMenu: true, // 显示菜单
success(res) {
// 打开成功
},
fail(err) {
// 打开失败
}
})
},
/**
* 预览图片
* @param {string} imagePath - 图片路径
*/
previewMediaFn(imagePath) {
uni.previewMedia({
sources:[{
url: imagePath,
type:'image',
}],
showShareButton: true, // 显示分享按钮
success(res){
// 预览成功
},
fail(err){
// 预览失败
}
})
},
}
}
</script>
样式设计(Style)
javascript
<style lang="less" scoped>
.contractClass {
width: 750rpx;
height: 100vh;
background-color: #F5F7FB;
.scrollClass {
width: 100%;
height: 100%;
.contentClass {
padding: 0rpx 40rpx 50px 40rpx;
.contentItemClass{
background-color: #fff;
border-radius: 13rpx;
padding: 30rpx;
margin-top: 40rpx;
.headClass{
font-weight: bold;
font-size: 29rpx;
color: #333333;
}
.buttonClass{
margin-top: 40rpx;
display: flex;
justify-content: flex-end;
align-items: center;
.downloadClass{
height: 60rpx;
background-color: #1B7AFE;
color: #fff;
border-radius: 30rpx;
display: flex;
justify-content: center;
align-items: center;
font-weight: 400;
font-size: 29rpx;
min-width: 180rpx;
}
}
}
}
}
/* 弹窗样式 */
.fileHeaderClass {
padding: 24rpx 24rpx 0 24rpx;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 36rpx;
font-weight: bold;
}
.filesListClass {
padding: 0 24rpx;
height: 400rpx;
.fileItemClass {
color: #2875DA;
margin-top: 20rpx;
text-decoration: underline;
}
}
}
</style>
核心功能解析
1. 文件下载与解压流程
- 用户点击下载按钮触发downloadFile方法
- 根据文件类型进行不同处理:
- ZIP 文件:下载后调用unzipHandler进行解压
- 文档文件:直接下载并调用openDocumentFn打开
- 图片文件:下载后调用previewMediaFn预览
- 解压处理:
- 使用FileSystemManager.unzip进行解压
- 解压路径使用小程序本地存储路径wx.env.USER_DATA_PATH
- 解压完成后读取文件列表并显示在弹窗中
2. 文件预览机制
系统支持多种类型文件预览,主要通过以下方法实现:
- previewMediaFn:用于预览图片,支持常见图片格式
- openDocumentFn:用于打开文档,支持 doc、docx、xls、xlsx、ppt、pptx、pdf 等格式
showMenu: true
, 显示分享菜单
- 递归处理:对于解压后包含文件夹的情况,通过递归方式查找可预览的文件
showShareButton: true,
, 显示分享菜单
3. 分页加载实现
通过scroll-view的scrolltolower事件实现分页加载:
- 初始加载第一页数据
- 滚动到底部时触发handleToLower方法
- 计算总页数与当前页数,判断是否还有更多数据
- 有更多数据则加载下一页并合并到现有列表
注意事项
- 文件路径处理:小程序中文件操作需使用wx.env.USER_DATA_PATH作为基础路径
- 权限问题:文件系统操作需要相应的权限,部分操作在不同平台可能有差异
- 错误处理:需考虑文件下载失败、解压失败、文件不存在等异常情况
- 性能优化:大文件处理可能影响性能,建议添加加载提示并优化用户体验
通过以上实现,我们可以构建一个功能完善的文件管理页面,满足用户下载、解压和预览多种类型文件的需求。