【Taro小程序开发:海报生成和下载功能】

828 阅读3分钟

环境:在小程序 taro 中 webview h5 页面中

解决问题:

小程序 taro 嵌入的 h5 页面,实现海报生成和下载功能

思路:

  1. 使用 html2canvas 将 dom 元素转成 base64 数据
  2. 海报 将 base64 数据传递给小程序的一个下载页面
  3. 在小程序对应的下载海报

生成海报

// 如果存在截图对象
if (screenshot.current) {
  // 设置加载状态为true
  setLoading(true);
  // 使用html2canvas获取截图
  html2canvas(screenshot.current, {
    useCORS: true, // 开启跨域配置
    allowTaint: true, // 允许跨域图片
    scale: 4, // 缩放倍数
    scrollY: 0, // 纵向偏移量 写死0 可以避免滚动造成偶尔偏移的现象
  })
    .then((canvas) => {
      // 将canvas转换为Blob
      canvas.toBlob((blob) => {
        if (blob) {
          // 停止加载状态
          setLoading(false);
          // 将Blob转换为Base64格式
          getBase64(blob).then((data: any) => {
            // 异步导入weixin-js-sdk库
            import("weixin-js-sdk").then((wx) => {
              // 打印微信JS-SDK对象以便调试
              // console.log("wx", wx, wx.miniProgram);
              // 使用微信小程序JS-SDK跳转到指定页面
              wx.miniProgram.navigateTo({
                url: `/pages/download/index?type=base64&url=${data}&page=poster`,
              });
            });
          });
        }
      });
    })
    .finally(() => {
      // 无论成功还是失败,都将加载状态设为false
      setLoading(false);
    });
}
​

Taro 小程序下载 (这里安卓下载没有问题,ios 点击不能下载)

import Taro from "@tarojs/taro";
​
export const downloadBase64 = (base64Image) => {
  // 将 base64 图片数据转换为临时文件路径
  const path = `${Taro.env.USER_DATA_PATH}/image.png`;
​
  Taro.getFileSystemManager().writeFile({
    filePath: path,
    data: base64Image.replace(/^data:image/\w+;base64,/, ""),
    encoding: "base64",
    success: (res) => {
      // console.log("res", res);
      // 保存图片到相册
      Taro.saveImageToPhotosAlbum({
        filePath: path,
        success: (saveRes) => {
          // console.log("图片保存成功", saveRes);
          Taro.showToast({
            title: "图片已保存到相册",
            icon: "success",
          });
        },
        fail: (saveError) => {
          console.error("保存图片失败", saveError);
          Taro.showToast({
            title: "保存失败",
            icon: "none",
          });
        },
      });
    },
    fail: (writeError) => {
      console.error("写入文件失败", writeError);
      Taro.showToast({
        title: "保存失败",
        icon: "none",
      });
    },
  });
};
​

处理微信小程序的文件写入时,确实有一些坑需要注意,特别是在 iOS 端可能会涉及到 base64 编码的问题。

方案是将数据类型从 base64 改为 binary,这是一个有效的调整。 下面是实现代码

/**
 * 将 base64 图片数据转换为文件并保存到本地相册
 * @param {string} base64data - 包含图像信息的base64字符串
 * @param {function} callback - 回调函数,接收保存后的文件路径或错误信息
 */
const base64ToBufferAndSave = (
  base64data: string,
  callback: (path: string | null, err: Error | null) => void
) => {
  // 使用正则表达式提取图像格式和主体数据
  let [, format, bodyData] =
    /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
  
  // 如果未找到格式,返回错误
  if (!format) {
    callback(null, new Error("ERROR_BASE64SRC_PARSE"));
    return;
  }
  
  // 生成基于当前时间戳的唯一文件名
  const FILE_BASE_NAME = new Date().getTime();
  
  // 构建文件路径,使用小程序的 USER_DATA_PATH
  const filePath = `${Taro.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
  
  // 将 base64 数据转换为 ArrayBuffer
  const buffer = Taro.base64ToArrayBuffer(bodyData);
  
  // 获取文件系统管理器
  const fsm = Taro.getFileSystemManager();

  // 将数据写入指定的文件路径
  fsm.writeFile({
    filePath,
    data: buffer,
    encoding: "binary",
    success() {
      // 如果写入成功,调用回调并传递文件路径
      callback(filePath, null);
    },
    fail() {
      // 如果写入失败,调用回调并传递错误信息
      callback(null, new Error("ERROR_BASE64SRC_WRITE"));
    },
  });
}

export const downloadBase64 = (base64Image) => {
  // 假设 base64Image 是包含图像信息的 base64 字符串
  base64ToBufferAndSave(
    base64Image,
    (localUrl, err) => {
      // 将保存后的文件路径传递给回调函数
      if (localUrl) {
        Taro.saveImageToPhotosAlbum({
          filePath: localUrl,
          success: (res2) => {
            // 如果保存成功,显示保存成功的提示
            Taro.showToast({
              title: "保存海报成功",
              icon: "none",
              duration: 2000,
            });
          },
          fail: (err) => {
            // 如果保存失败,显示保存失败的提示
            Taro.showToast({
              title: "保存失败",
              icon: "none",
              duration: 2000,
            });
          },
        });
      } else {
        console.log(err);
      }
    },
  );
};