实现微信小程序web-view内嵌H5中的下载功能(大文件切片下载)

文章讲述了如何在uni-app构建的微信小程序里实现H5页面的下载功能,重点在于使用jweixin-module模块和uni.downloadFile接口。对于大文件,采用了分块下载的方法,通过uni.request获取文件大小,然后利用uni.downloadFile下载每个块并最终合并。此外,讨论了在web-view中处理下载的用户体验问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、项目场景:

微信小程序的开发框架是uniapp,使用uniapp脚手架搭建,其中有页面是展示另一个小程序,在这个页面主体内容使用了标签将H5的页面内容展示,H5中有页面存放了下载的路径。点击下载按钮下载文件,或者预览文件让用户手动保存。

难点

  • 如果是pc端,下载用一个<a>标签就很容易,但是在小程序里的标签中是行不通的。
  • 此外,小程序里的<web-view>与小程序的通行方式主要用postMessage,但是触发条件非常苛刻,参照微信的官方文档,只在小程序后退组件销毁,还有分享时才会触发postMessage并一次性把值全部带出来,使用起来非常不便,对于小程序实际内容主体是<web-view>内嵌的spa的H5页面的情况下,销毁组件会带来很多麻烦,因此最后放弃了这个方案

解决方案:

1、H5微信小程序:

a、首先必不可少的是安装jweixin-module模块:
npm i jweixin-module
b、在main.js中将依赖绑定:
import wx from  "jweixin-module"
Vue.prototype.$wx = wx 
c、H5对应页面点击下载时代码为:
this.$wx.miniProgram.navigateTo({
  url:"/pages/study/downLoading?url="+encodeURIComponent(fileInfo.content_url) 
})

2、uni-app的小程序

a、对应的/pages/study/downLoading页面
<template> <div>下载中。。。</div> </template>
<script lang="ts" setup>
import { onLoad } from '@dcloudio/uni-app';
onLoad((option: any) => {
  console.log('gfdjhwghjghjkjghjklghjk', option.url);
  handleDown(decodeURIComponent(option.url));
});
const handleDown = (url: string) => {
  uni.showLoading({
    title: '下载中', //提示文字
    mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
  });
  console.log(url);
  let imageType = ['gif', 'png', 'jpg', 'tif', 'bmp', 'webp', 'jpeg', 'JPG'];
  const downloadTask = uni.downloadFile({
    url: url,
    success: res => {
      console.log(res, 'resresresres');
      uni.hideLoading();
      if (res.statusCode === 200) {
        console.log(res.tempFilePath, '1111111');
        let originFileNameSplit = res.tempFilePath.split('.');
        let fileNameType = originFileNameSplit[originFileNameSplit.length - 1];
        uni.getFileSystemManager().saveFile({
          //微信保存文件,这个存储有点复杂
          // 临时存储路径,先有临时存储路径方可使用wx官方的存储本地路径( uni.env.USER_DATA_PATH )
          tempFilePath: res.tempFilePath,
          //定义本地的存储路径及名称
          success(res) {
            const savedFilePath = res.savedFilePath;
            uni.setStorageSync('uploadData', savedFilePath); //保存传入的参数
            uni.setStorageSync('isImageType', imageType.includes(fileNameType)); //保存传入的参数
            uni.hideLoading();
            uni.navigateBack({
              delta: 1,
            });
          },
          fail(err) {
            uni.showToast({
              title: '预览失败',
              icon: 'error',
              duration: 1500,
            });
            setTimeout(() => {
              uni.navigateBack({
                delta: 1,
              });
            }, 3000);
          },
        });
      }
    },
    fail: err => {
      uni.hideLoading();
      uni.showToast({
        title: '下载失败',
        icon: 'error',
        duration: 1500,
      });
      console.log(err);
      setTimeout(() => {
        uni.navigateBack({
          delta: 1,
        });
      }, 3000);
    },
  });
  downloadTask.onProgressUpdate(res => {
    // console.log('下载进度', res.progress);
    // console.log('已经下载的数据长度', res.totalBytesWritten);
    // console.log('预期需要下载的数据总长度', res.totalBytesExpectedToWrite);
  });
};
</script>
b、可以预览的时候返回到web-view页面:
<template>
  <view class="study">
    <web-view class="webviewOut" :src="你的url地址"></web-view>
  </view>
</template>

<script setup lang="ts">
onShow(() => {
  const url = uni.getStorageSync('uploadData');
  console.log('1111111111111111111111111111111111', url);
  if (url) {
    uni.hideHomeButton();
    handleDown(url); //具体的微信下载文件的方法
    //每次onShow执行完,还有上面的下载方法执行完后要把这个标记重置为false,这样不同情况触发的onShow才能区分是否是下载文件页面回来的。可能写的重复但是多写几次比较放心
  }
});
const handleDown = (savedFilePath: string) => {
  const isImageType = uni.getStorageSync('isImageType');
  if (isImageType) {
    uni.previewImage({
      urls: [savedFilePath],
      success: function (res) {
        uni.removeStorageSync('uploadData');
        uni.removeStorageSync('isImageType');
      },
      fail: function (err) {
        console.log(res);
        uni.showToast({
          title: '预览失败',
          icon: 'error',
          duration: 1500,
        });
        uni.removeStorageSync('uploadData');
        uni.removeStorageSync('isImageType');
      },
    });
  } else {
    uni.openDocument({
      //微信打开文件
      filePath: savedFilePath,
      showMenu: true,
      success: function (res) {
        uni.removeStorageSync('uploadData');
        uni.removeStorageSync('isImageType');
      },
      fail: function (err) {
        console.log(res);
        uni.showToast({
          title: '预览失败',
          icon: 'error',
          duration: 1500,
        });
        uni.removeStorageSync('uploadData');
        uni.removeStorageSync('isImageType');
      },
    });
  }
};
</script>

<style scoped lang="scss">
.study {
  height: 100vh;
  box-sizing: border-box;
  padding-bottom: 98rpx;
}
</style>

二、注意(下载大文件)

uni.downloadFile 主要包含两个参数:urlfilePath,其中 url 代表下载文件的路径,filePath 代表下载文件保存的路径。
uni.downloadFile 可以用来下载较小的文件,但是对于大文件的下载需要通过 分片下载方式 。具体来说,可以使用 uni.downloadFile 和 uni.uploadFile 结合使用实现分片下载。同时,需要注意为下载文件分配足够的存储空间,以便能够完整保存整个文件。

具体步骤如下:

1.获取文件的总大小。

uni.request({
    url: 'http://example.com/fileUrl',
    method: 'HEAD',
    success: function (res) {
        let contentLength = res.header['Content-Length'];
        // 计算出下载总块数
        let blockCount = Math.ceil(contentLength / BLOCK_SIZE);
        // 创建一个和块数相同的数组
        let blockList = new Array(blockCount).fill(0);
        // 进行分块下载
        downloadFileByBlock(url, savePath, blockList);
    }
});

2.将文件分成若干个块(BLOCK_SIZE 为每个块的大小),以便分批下载。

3.循环下载每一块。

function downloadFileByBlock(url, savePath, blockList) {
    blockList.forEach(function (v, i) {
        let rangeStart = i * BLOCK_SIZE;
        let rangeEnd = (i + 1) * BLOCK_SIZE - 1;
        // 利用 Range 请求头指定下载区间
        uni.downloadFile({
            url: url,
            filePath: savePath,
            header: {
                'Range': 'bytes=' + rangeStart + '-' + rangeEnd
            },
            success: function (res) {
                console.log(res);
            },
            fail: function (err) {
                console.log(err);
            }
        });
    });
}

大文件上传也可以采用类似的方式, 即将文件分成若干块,每次上传一个块,直到上传完整个文件。

完整代码(自己改了改)

这里给出针对微信小程序 web-view 内嵌 H5 中大文件切片下载的完整代码实现:

// download.js

const downloadUrl = 'http://example.com/files/file.zip' // 下载地址
const fileName = 'file.zip' // 下载的文件名
const fileSize = 1024 * 1024 * 100 // 文件大小,100MB
const chunkSize = 1024 * 1024 * 2 // 分片大小,2MB
const tempFilePath = wx.env.USER_DATA_PATH + '/downloads/' // 临时文件存储路径

Page({
  data: {
    progress: 0, // 下载进度
    isDownloading: false, // 是否正在下载
    downloadTask: null // 下载任务句柄
  },

  // 开始下载
  startDownload() {
    if (this.data.isDownloading) {
      return
    }
    this.setData({ isDownloading: true })
    this.downloadChunks()
  },

  // 取消下载
  cancelDownload() {
    if (this.data.downloadTask) {
      this.data.downloadTask.abort()
    }
    this.resetState()
  },

  // 处理分片下载任务队列,完成后进行合并操作
  downloadChunks() {
    const chunkNum = Math.ceil(fileSize / chunkSize)
    const chunks = []
    for (let i = 0; i < chunkNum; i++) {
      const start = i * chunkSize
      const end = Math.min(start + chunkSize, fileSize)
      chunks.push({
        index: i,
        start,
        end,
        downloaded: false,
        filePath: `${tempFilePath}${i}`
      })
    }

    // 进度状态:已下载的字节数
    let loadedBytes = 0

    // 下载分片
    const downloadChunk = (chunk) => {
      return new Promise((resolve, reject) => {
        const downloadTask = wx.downloadFile({
          url: downloadUrl,
          header: {},
          filePath: chunk.filePath,
          success: (res) => {
            if (res.statusCode === 200) {
              loadedBytes += chunk.end - chunk.start + 1
              chunk.downloaded = true
              resolve()
            } else {
              reject(new Error('Download chunk error.'))
            }
          },
          fail: () => {
            reject(new Error('Download chunk fail.'))
          }
        })
        downloadTask.onProgressUpdate((res) => {
          const progress = Math.floor((loadedBytes + res.totalBytesWritten) / fileSize * 100)
          console.log('分片下载进度:', progress)
          this.setData({ progress })
        })
        this.setData({ downloadTask })
      })
    }

    Promise.all(chunks.map(downloadChunk))
      .then(() => {
        this.mergeFiles()
      })
      .catch((err) => {
        console.log(err)
        this.resetState()
      })
  },

  // 合并已下载的分片文件
  mergeFiles() {
    const fs = wx.getFileSystemManager()
    const stream = fs.createWriteStream(`${wx.env.USER_DATA_PATH}/downloads/${fileName}`)

    const write = (index) => {
      const chunk = chunks[index]
      if (!chunk.downloaded) {
        console.log(`Chunk ${chunk.index} not downloaded.`)
        return
      }
      fs.readFile(chunk.filePath, 'binary', (err, data) => {
        if (err) {
          console.log(err)
          return
        }
        stream.write(data, 'binary')
        if (index < chunks.length - 1) {
          write(index + 1)
        } else {
          // 文件合并完成
          stream.end()
          this.resetState()
        }
      })
    }

    write(0)
  },

  // 重置下载状态
  resetState() {
    this.setData({
      progress: 0,
      isDownloading: false,
      downloadTask: null
    })
  }
})

在 H5 页面中可以引入此文件作为下载操作的逻辑处理。同时还需要在 wxml 文件中添加下载按钮及进度条,代码如下:

<!-- download.wxml -->

<view class="download-container">
  <button class="download-btn" bindtap="startDownload">下载</button>
  <progress percent="{{ progress }}%" />
</view>

需要在小程序的 app.json 文件中添加下载临时路径配置,代码如下:

抱歉,似乎代码显示出现了截断,请继续查看完整的 app.json 中的配置:

{
  "pages": [
    "pages/index/index",
    "pages/download/download"
  ],
  "permission": {
    "scope.userLocation": {
      "desc": "小程序将获取您的定位信息"
    }
  },
  "downloadFile": {
    "domain": [ "example.com" ],
    "timeout": 10000
  },
  "subpackages": []
}

其中,downloadFile 字段下对应了下载文件的配置,包括限定下载域名、超时时间等。文件下载的域名必须在此处进行配置,否则会被微信限制,无法正常下载。

总结:

大致过程就是这样,网上有很多解决方案都是在uni-app小程序的loading页面直接去缓存一下h5发过来的下载地址之后,直接返回到web-view页面去进行下载预览,他的 缺点点击下载时页面会一闪而过空白的微信页面再回来,我把下载直接放到了downLoading页面就是为了可以告诉用户我已经在下载了,可以给用户一点好的体验。

uni.navigateBack({delta: 1}); 返回到web-view的目的是为了你在预览界面点击左上角返回时可以回到web-view页面,直接下载预览都在downLoading页面的话,你在预览页面点击返回就会回到downLoading页面,体验不是很好。

微信小程序webview中,要实现文件下载功能,可以通过以下步骤进行操作: 1. 在小程序webview中,可以使用标准的HTML元素`<a>`来触发文件下载。首先,在小程序webview页面中,添加一个下载按钮或者链接,例如: ```html <a href="https://example.com/path/to/file.pdf" download>点击下载文件</a> ``` 其中`https://example.com/path/to/file.pdf`是要下载文件的URL,`download`属性表示要下载文件而不是在浏览器中打开。 2. 在小程序webview页面的JS代码中,可以监听这个下载链接的点击事件,并在点击时触发文件下载。例如: ```javascript document.querySelector('a').addEventListener('click', function(e) { e.preventDefault(); // 阻止默认的链接跳转行为 var url = this.getAttribute('href'); wx.downloadFile({ url: url, success: function(res) { var filePath = res.tempFilePath; // 下载后的临时文件路径 wx.saveFile({ tempFilePath: filePath, success: function(res) { var savedFilePath = res.savedFilePath; // 保存后的文件路径 // 文件保存成功后的操作 }, fail: function(res) { // 文件保存失败后的操作 } }); }, fail: function(res) { // 文件下载失败后的操作 } }); }); ``` 以上代码中,`document.querySelector('a')`用于获取第一个`<a>`元素,根据实际情况可能需要修改选择器;`wx.downloadFile`用于下载文件,`wx.saveFile`用于保存文件到本地。 需要注意的是,下载文件需要在小程序的`app.json`配置文件中添加相应的权限设置,例如: ```json { "mp-weixin": { "permission": { "scope.userLocation": { "desc": "获取您的地理位置信息将用于小程序定位" }, "scope.writePhotosAlbum": { "desc": "保存图片到相册" }, "scope.camera": { "desc": "拍摄照片或者录像" }, "scope.record": { "desc": "录制音频" }, "scope.userInfo": { "desc": "获取您的基本信息将用于小程序登录" }, "scope.userLocationBackground": { "desc": "获取您的地理位置信息将用于小程序定位" }, "scope.invoiceTitle": { "desc": "获取你发票抬头" }, "scope.invoice": { "desc": "获取你发票" }, "scope.werun": { "desc": "微信运动步数" }, "scope.writeVideosAlbum": { "desc": "保存视频到相册" } } } } ``` 以上是在微信小程序webview实现文件下载的一种方式,你可以根据具体需求进行调整和扩展。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值