微信小程序文件下载与预览功能实现详解

在微信小程序开发中,文件处理是常见需求,尤其是涉及合同、文档等场景。本文将通过一个实际案例,详细讲解如何实现文件的下载、解压、列表展示及预览功能。

功能概述

该页面主要实现了以下核心功能:

  • 列表展示可下载的文件信息
  • 支持 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. 文件下载与解压流程

  1. 用户点击下载按钮触发downloadFile方法
  2. 根据文件类型进行不同处理:
    • ZIP 文件:下载后调用unzipHandler进行解压
    • 文档文件:直接下载并调用openDocumentFn打开
    • 图片文件:下载后调用previewMediaFn预览
  3. 解压处理:
    • 使用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方法
  • 计算总页数与当前页数,判断是否还有更多数据
  • 有更多数据则加载下一页并合并到现有列表

注意事项

  1. 文件路径处理:小程序中文件操作需使用wx.env.USER_DATA_PATH作为基础路径
  2. 权限问题:文件系统操作需要相应的权限,部分操作在不同平台可能有差异
  3. 错误处理:需考虑文件下载失败、解压失败、文件不存在等异常情况
  4. 性能优化:大文件处理可能影响性能,建议添加加载提示并优化用户体验

通过以上实现,我们可以构建一个功能完善的文件管理页面,满足用户下载、解压和预览多种类型文件的需求。

相关推荐
说私域3 小时前
公域流量向私域流量转化策略研究——基于开源AI智能客服、AI智能名片与S2B2C商城小程序的融合应用
人工智能·小程序
源码_V_saaskw3 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
weixin_lynhgworld6 小时前
淘宝扭蛋机小程序系统开发:重塑电商互动模式
大数据·小程序
996幸存者7 小时前
uni-app区域选择、支持静态、动态数据
微信小程序·uni-app
ᥬ 小月亮9 小时前
Uniapp编写微信小程序,绘制动态圆环进度条
微信小程序·小程序·uni-app
The_era_achievs_hero16 小时前
UniappDay03
vue.js·微信小程序·uni-app
说私域19 小时前
技术赋能与营销创新:开源链动2+1模式AI智能名片S2B2C商城小程序的流量转化路径研究
人工智能·小程序·开源
游戏开发爱好者81 天前
没有 Mac,如何上架 iOS App?多项目复用与流程标准化实战分享
android·ios·小程序·https·uni-app·iphone·webview
weixin_lynhgworld1 天前
代驾小程序系统开发:引领出行行业数字化转型
小程序