通过微信小程序原生接口,开发者可快速实现PDF下载、预览及本地保存功能,提升用户体验。
在安卓微信中,当 PDF 标题包含/字符时会导致下载失败,这是因为/在文件系统中是路径分隔符,不能作为文件名的一部分。我们需要对文件名进行特殊处理来解决这个问题:
关键修复说明
-
特殊字符处理:
-
新增
sanitizeFileName方法,专门处理文件名中的特殊字符 -
将
/以及其他安卓系统不允许的字符(\ : * ? " < > |)替换为下划线_ -
确保文件名合规,避免文件系统报错
-
-
文件名分离策略:
fileName:原始文件名(可能包含特殊字符,仅用于展示)
safeFileName:处理后的安全文件名(用于实际文件操作)
displayName:去除扩展名的显示用名称(优化用户体验)
-
下载和保存优化:
-
下载和保存文件时都使用
safeFileName -
确保文件名包含
.pdf扩展名,帮助系统正确识别文件类型 -
限制文件名长度,避免过长文件名导致的问题
-
-
兼容性考虑:
-
同时兼容 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'
});
}
};
}
});



4753

被折叠的 条评论
为什么被折叠?



