From 379d29a356f3070856f8b45b8612ae1b912ada39 Mon Sep 17 00:00:00 2001 From: Dev Date: Wed, 30 Sep 2026 18:19:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=8D=E5=88=B6=E5=8F=8A=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../miniapp/pages/demand/contract/index.js | 34 ++++- frontend/miniapp/pages/demand/detail/index.js | 137 +++++++++++++++--- .../miniapp/pages/demand/detail/index.wxml | 10 ++ .../miniapp/pages/demand/detail/index.wxss | 52 +++++++ 4 files changed, 206 insertions(+), 27 deletions(-) diff --git a/frontend/miniapp/pages/demand/contract/index.js b/frontend/miniapp/pages/demand/contract/index.js index b1ad317..10c376a 100644 --- a/frontend/miniapp/pages/demand/contract/index.js +++ b/frontend/miniapp/pages/demand/contract/index.js @@ -240,6 +240,19 @@ Page({ }); }, + // 取文件扩展名对应的 openDocument 类型(默认 pdf) + getFileType(url) { + const ext = String(url || '').split('?')[0].split('#')[0].split('.').pop().toLowerCase(); + const support = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf']; + return support.indexOf(ext) > -1 ? ext : 'pdf'; + }, + + // 取域名,用于下载失败时提示配置合法域名 + getUrlHost(url) { + const match = String(url || '').match(/^https?:\/\/([^/:?#]+)/); + return match ? match[1] : ''; + }, + // 附件预览:客户点击开发者上传的附件进行查看 handlePreviewAttach(e) { // 仅客户侧可预览;开发者侧附件可删除,避免删除时误触预览 @@ -260,16 +273,22 @@ Page({ return; } // 其他文件下载后打开 + const fileType = this.getFileType(fileUrl); + // 指定带扩展名的保存路径:安卓/鸿蒙靠扩展名识别文件类型,默认 tempFilePath 无扩展名易失败 + const filePath = `${wx.env.USER_DATA_PATH}/contract_file_${Date.now()}.${fileType}`; wx.showLoading({ title: '加载中...' }); wx.downloadFile({ url: fileUrl, + filePath: filePath, success: (res) => { wx.hideLoading(); if (res.statusCode === 200) { wx.openDocument({ - filePath: res.tempFilePath, + filePath: res.filePath || res.tempFilePath || filePath, + fileType: fileType, showMenu: true, - fail: () => { + fail: (err) => { + console.error('openDocument fail', err); wx.showToast({ title: '该文件类型暂不支持预览', icon: 'none' }); } }); @@ -277,9 +296,16 @@ Page({ wx.showToast({ title: '附件加载失败', icon: 'none' }); } }, - fail: () => { + fail: (err) => { wx.hideLoading(); - wx.showToast({ title: '附件加载失败', icon: 'none' }); + console.error('downloadFile fail', err); + // 真机常见原因:downloadFile 合法域名未配置 + const host = this.getUrlHost(fileUrl); + wx.showModal({ + title: '预览失败', + showCancel: false, + content: '附件下载失败,请检查网络' + (host ? ';若为体验版/真机,请确认已配置 downloadFile 合法域名:' + host : '') + }); } }); }, diff --git a/frontend/miniapp/pages/demand/detail/index.js b/frontend/miniapp/pages/demand/detail/index.js index 6788168..c344926 100644 --- a/frontend/miniapp/pages/demand/detail/index.js +++ b/frontend/miniapp/pages/demand/detail/index.js @@ -12,7 +12,8 @@ Page({ remark: '', actualAccount: 0, activeValues: [0, 1, 2, 3, 4], - demandInfo: null + demandInfo: null, + copyUrl: '' // 剪贴板复制失败时,弹出可长按选择的链接 }, /** @@ -41,41 +42,131 @@ Page({ ); }); }, + // 取文件扩展名对应的 openDocument 类型(默认 pdf) + getFileType(url) { + const ext = String(url || '').split('?')[0].split('#')[0].split('.').pop().toLowerCase(); + const support = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf']; + return support.indexOf(ext) > -1 ? ext : 'pdf'; + }, + + // 取域名,用于下载失败时提示配置合法域名 + getUrlHost(url) { + const match = String(url || '').match(/^https?:\/\/([^/:?#]+)/); + return match ? match[1] : ''; + }, + //文件预览 fileView(e) { const fileUrl = e.currentTarget.dataset.url; + if (!fileUrl) { + wx.showToast({ + title: '文件链接为空', + icon: 'none' + }); + return; + } + const fileType = this.getFileType(fileUrl); + // 指定带扩展名的保存路径:安卓/鸿蒙靠扩展名识别文件类型, + // downloadFile 默认的 tempFilePath 无扩展名,容易打开失败 + const filePath = `${wx.env.USER_DATA_PATH}/demand_file_${Date.now()}.${fileType}`; + wx.showLoading({ + title: '加载中...', + mask: true + }); wx.downloadFile({ url: fileUrl, - success: function (res) { - const filePath = res.tempFilePath + filePath: filePath, + success: (res) => { + wx.hideLoading(); + if (res.statusCode !== 200) { + wx.showToast({ + title: '文件加载失败(' + res.statusCode + ')', + icon: 'none' + }); + return; + } + const openPath = res.filePath || res.tempFilePath || filePath; + console.log('openDocument', { openPath, fileType }); wx.openDocument({ - filePath: filePath, - success: function (res) { - console.log('打开文档成功') + filePath: openPath, + fileType: fileType, + showMenu: true, // 支持转发/用其他应用打开 + success: () => { + console.log('打开文档成功'); + }, + fail: (err) => { + console.error('openDocument fail', err); + // 安卓/鸿蒙部分机型无法直接预览,降级为复制链接到浏览器打开 + wx.showModal({ + title: '预览失败', + content: '当前系统无法直接预览该文件,是否复制链接后在浏览器中打开?', + confirmText: '复制链接', + cancelText: '关闭', + success: (res2) => { + if (res2.confirm) { + this.copyUrlToClipboard(fileUrl); + } + } + }); } - }) - } - }) - }, - //文件复制 - fileUrlCopy(e) { - const url = e.currentTarget.dataset.url; - wx.setClipboardData({ - data: url, - success: function () { - wx.showToast({ - title: '下载链接已复制', - icon: 'success' }); }, - fail: function () { - wx.showToast({ - title: '下载链接复制失败', - icon: 'none' + fail: (err) => { + wx.hideLoading(); + console.error('downloadFile fail', err); + // 真机/体验版常见原因:downloadFile 合法域名未配置(开发者工具可关闭校验,真机不行) + const host = this.getUrlHost(fileUrl); + wx.showModal({ + title: '预览失败', + showCancel: false, + content: '文件下载失败(' + (err && err.errMsg ? err.errMsg : '未知错误') + '),请检查网络' + (host ? ';请确认已配置 downloadFile 合法域名:' + host : '') }); } }); }, + + // 复制链接(失败自动重试一次,仍失败则弹出可长按选择的链接供手动复制) + copyUrlToClipboard(url) { + if (!url) { + wx.showToast({ + title: '文件链接为空', + icon: 'none' + }); + return; + } + const doCopy = (retry) => { + wx.setClipboardData({ + data: String(url), + success: () => { + wx.showToast({ + title: '下载链接已复制', + icon: 'success' + }); + }, + fail: (err) => { + console.error('setClipboardData fail', err); + if (retry > 0) { + // 系统剪贴板偶发写入失败,短暂延迟后重试一次 + setTimeout(() => doCopy(retry - 1), 200); + return; + } + // 降级:展示可长按选择的链接,用户手动复制 + this.setData({ copyUrl: String(url) }); + } + }); + }; + doCopy(1); + }, + + //文件复制 + fileUrlCopy(e) { + this.copyUrlToClipboard(e.currentTarget.dataset.url); + }, + + // 关闭手动复制弹层 + closeCopyModal() { + this.setData({ copyUrl: '' }); + }, handleChange(e) { console.log(e.detail.value); this.setData({ diff --git a/frontend/miniapp/pages/demand/detail/index.wxml b/frontend/miniapp/pages/demand/detail/index.wxml index 8ea14ef..6c80469 100644 --- a/frontend/miniapp/pages/demand/detail/index.wxml +++ b/frontend/miniapp/pages/demand/detail/index.wxml @@ -114,4 +114,14 @@ 确认收货 待发货(等待开发者交付) + + + + + + 自动复制失败,请长按下方链接手动复制 + {{copyUrl}} + 可粘贴到手机浏览器打开下载 + 关闭 + \ No newline at end of file diff --git a/frontend/miniapp/pages/demand/detail/index.wxss b/frontend/miniapp/pages/demand/detail/index.wxss index a66971a..de9da7e 100644 --- a/frontend/miniapp/pages/demand/detail/index.wxss +++ b/frontend/miniapp/pages/demand/detail/index.wxss @@ -102,6 +102,58 @@ page { word-break: break-all; } +/* 复制失败兜底弹层 */ +.copy-mask { + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 100; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + padding: 48rpx; + box-sizing: border-box; +} + +.copy-dialog { + width: 100%; + background: var(--bg-card); + border-radius: var(--radius-lg); + padding: 32rpx; + box-sizing: border-box; +} + +.copy-title { + font-size: 28rpx; + font-weight: 600; + line-height: 44rpx; + color: var(--text-primary); + margin-bottom: 20rpx; +} + +.copy-url { + display: block; + max-height: 320rpx; + overflow-y: auto; + padding: 20rpx; + background: var(--bg-grey); + border-radius: 12rpx; + font-size: 24rpx; + line-height: 36rpx; + color: var(--text-secondary); + word-break: break-all; +} + +.copy-tip { + margin: 16rpx 0 24rpx; + font-size: 24rpx; + line-height: 36rpx; + color: var(--text-tertiary); +} + .button-submit { margin: 32rpx; }