轻松处理 PDF?微信小程序预览 + 保存 + 转发全实现

通过微信小程序原生接口,开发者可快速实现PDF下载、预览及本地保存功能,提升用户体验。

在安卓微信中,当 PDF 标题包含/字符时会导致下载失败,这是因为/在文件系统中是路径分隔符,不能作为文件名的一部分。我们需要对文件名进行特殊处理来解决这个问题:

关键修复说明

  1. 特殊字符处理

    • 新增sanitizeFileName方法,专门处理文件名中的特殊字符

    • /以及其他安卓系统不允许的字符(\ : * ? " < > |)替换为下划线_

    • 确保文件名合规,避免文件系统报错

  2. 文件名分离策略

    • fileName

      :原始文件名(可能包含特殊字符,仅用于展示)

    • safeFileName

      :处理后的安全文件名(用于实际文件操作)

    • displayName

      :去除扩展名的显示用名称(优化用户体验)

  3. 下载和保存优化

    • 下载和保存文件时都使用safeFileName

    • 确保文件名包含.pdf扩展名,帮助系统正确识别文件类型

    • 限制文件名长度,避免过长文件名导致的问题

  4. 兼容性考虑

    • 同时兼容 iOS 和 Android 系统的文件命名规则

    • 在存储记录中同时保存原始名称和安全文件名,方便后续管理和展示


Page({
    data: {
      title: 'PDF文件预览',
      pdfUrl: '',
      localFilePath: '',
      loading: false,
      error: '',
      fileName: '',          // 原始文件名(可能包含特殊字符)
      safeFileName: '',      // 处理后的安全文件名
      displayName: '',       // 显示用的文件名
      isIOS: false,
      shareTitle: ''
    },
    onLoad(options) {
      const systemInfo = wx.getSystemInfoSync();
      this.setData({
        isIOS: systemInfo.platform === 'ios'
      });
      options.url = "316222348167479298.pdf" //'/src/DB32_T 4791—2024.pdf'
      if (options.url) {
        const pdfUrl = decodeURIComponent(options.url);
        const originalFileName = this.extractFileNameFromUrl(pdfUrl);
        const title = options.title || originalFileName || 'PDF文件';
        // 处理文件名,移除特殊字符
        const safeFileName = this.sanitizeFileName(title);
        this.setData({
          pdfUrl: pdfUrl,
          title: title,
          fileName: title,
          safeFileName: safeFileName,  // 使用处理后的安全文件名
          displayName: this.getDisplayName(title),
          shareTitle: title
        });
        this.downloadPdf();
      } else {
        this.setData({
          error: '未找到PDF文件地址',
          loading: false
        });
      }
    },
    // 提取文件名
    extractFileNameFromUrl(url) {
      if (!url) return '';
      const urlWithoutParams = url.split('?')[0];
      const parts = urlWithoutParams.split('/');
      return parts[parts.length - 1] || '';
    },
    // 关键修复:清理文件名中的特殊字符
    sanitizeFileName(name) {
      if (!name) return '未知文件';
      // 移除所有不允许的文件名字符
      // 特别是安卓系统不允许的 / \ : * ? " < > |
      const invalidChars = /[\\/:*?"<>|]/g;
      let sanitized = name.replace(invalidChars, '_');
      // 确保文件名有PDF扩展名
      if (!sanitized.toLowerCase().endsWith('.pdf')) {
        sanitized += '.pdf';
      }
      // 限制文件名长度,避免过长导致问题
      if (sanitized.length > 50) {
        const ext = '.pdf';
        sanitized = sanitized.substring(0, 50 - ext.length) + ext;
      }
      return sanitized;
    },
    // 获取显示用的文件名(去除扩展名)
    getDisplayName(name) {
      if (!name) return '未知文件';
      return name.replace(/\.pdf$/i, '');
    },
    // 下载PDF文件(使用处理后的安全文件名)
    downloadPdf() {
      if (!this.data.pdfUrl) {
        this.setData({
          error: 'PDF文件地址为空',
          loading: false
        });
        return;
      }
      this.setData({
        loading: true,
        error: ''
      });
      // 关键修复:使用安全文件名下载
      const downloadPath = wx.env.USER_DATA_PATH + '/' + this.data.safeFileName;
      wx.downloadFile({
        url: this.data.pdfUrl,
        filePath: downloadPath,  // 使用处理后的路径
        success: (res) => {
          if (res.statusCode === 200) {
            this.setData({
              localFilePath: res.filePath || res.tempFilePath,
              loading: false
            });
            if (this.data.isIOS) {
              this.openPdf();
            }
          } else {
            this.setData({
              error: '下载失败,状态码:' + res.statusCode,
              loading: false
            });
          }
        },
        fail: (err) => {
          this.setData({
            error: '下载失败:' + err.errMsg,
            loading: false
          });
          console.error('下载PDF失败:', err);
        }
      });
    },
    // 保存文件(使用安全文件名)
    saveFile() {
      if (!this.data.localFilePath) {
        wx.showToast({
          title: '文件未准备好',
          icon: 'none'
        });
        return;
      }
      wx.showLoading({
        title: `保存中...`
      });
      wx.saveFile({
        tempFilePath: this.data.localFilePath,
        // 关键修复:指定安全的保存文件名
        filePath: wx.env.USER_DATA_PATH + '/' + this.data.safeFileName,
        success: (res) => {
          wx.hideLoading();
          wx.showToast({
            title: `${this.data.displayName}保存成功`,
            icon: 'success'
          });
          console.log('文件保存路径:', res.savedFilePath);
          // 存储时同时记录原始名称和安全路径
          const savedPdfs = wx.getStorageSync('savedPdfs') || [];
          savedPdfs.push({
            name: this.data.displayName,  // 显示原始名称
            safeName: this.data.safeFileName,  // 保存安全文件名
            path: res.savedFilePath,
            time: new Date().getTime()
          });
          wx.setStorageSync('savedPdfs', savedPdfs);
        },
        fail: (err) => {
          wx.hideLoading();
          console.error('保存文件失败:', err);
          wx.showToast({
            title: `${this.data.displayName}保存失败`,
            icon: 'none'
          });
        }
      });
    },
    openPdf() {
      if (!this.data.localFilePath) {
        wx.showToast({
          title: '文件未准备好',
          icon: 'none'
        });
        return;
      }
      const openOptions = {
        filePath: this.data.localFilePath,
        fileType: 'pdf',
        showMenu: true,
        success: (res) => {
          console.log('打开文档成功');
        },
        fail: (err) => {
          console.error('打开文档失败:', err);
          wx.showToast({
            title: '打开失败',
            icon: 'none'
          });
        }
      };
      if (this.data.isIOS) {
        openOptions.filePath = wx.env.USER_DATA_PATH + '/' + this.data.safeFileName;
      }
      wx.openDocument(openOptions);
    },
    sharePdfFile() {
      if (!this.data.localFilePath) {
        wx.showToast({
          title: '请先下载文件',
          icon: 'none'
        });
        return;
      }
      wx.showModal({
        title: '分享文件',
        content: '点击"确定"打开文档,然后通过文档内的分享功能发送给好友',
        confirmText: '确定',
        cancelText: '取消',
        success: (res) => {
          if (res.confirm) {
            this.openPdfWithShare();
          }
        }
      });
    },
    openPdfWithShare() {
      if (!this.data.localFilePath) return;
      wx.openDocument({
        filePath: this.data.localFilePath,
        fileType: 'pdf',
        showMenu: true,
        success: (res) => {
          console.log('文档已打开,请用户通过内置菜单分享');
          wx.showToast({
            title: '请点击文档右上角分享',
            icon: 'none',
            duration: 3000
          });
        },
        fail: (err) => {
          console.error('打开文档失败', err);
          wx.showToast({
            title: '打开文档失败',
            icon: 'none'
          });
        }
      });
    },
    onShareAppMessage() {
      return {
        title: `推荐PDF:${this.data.shareTitle}`,
        path: `/pages/pdfViewer/pdfViewer?url=${encodeURIComponent(this.data.pdfUrl)}&title=${encodeURIComponent(this.data.shareTitle)}`,
        success: () => {
          wx.showToast({
            title: '分享成功',
            icon: 'success'
          });
        }
      };
    }
  });
  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值