hookehuyr

feat(favorites): 新增收藏列表项下载功能

- 在删除按钮旁新增下载按钮
- 实现文件下载预览功能(支持 PDF、Office 文档等)
- 智能文件类型识别,针对 Office 文档提供预览限制提示
- 优化下载失败和文件无法预览时的用户提示
- 添加文件下载地址字段到数据模型

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -39,11 +39,18 @@ ...@@ -39,11 +39,18 @@
39 </view> 39 </view>
40 </view> 40 </view>
41 41
42 + <!-- Action Buttons -->
43 + <view class="flex items-center gap-[16rpx]">
44 + <!-- Download Button -->
45 + <view class="p-[10rpx]" @tap.stop="onDownload(item)">
46 + <IconFont name="Download" size="18" color="#007AFF" />
47 + </view>
42 <!-- Delete Button --> 48 <!-- Delete Button -->
43 <view class="p-[10rpx]" @tap.stop="onDelete(item)"> 49 <view class="p-[10rpx]" @tap.stop="onDelete(item)">
44 <IconFont name="Del" size="18" color="#999" /> 50 <IconFont name="Del" size="18" color="#999" />
45 </view> 51 </view>
46 </view> 52 </view>
53 + </view>
47 54
48 <!-- Empty State --> 55 <!-- Empty State -->
49 <view v-if="filteredList.length === 0" 56 <view v-if="filteredList.length === 0"
...@@ -85,7 +92,9 @@ const list = ref([ ...@@ -85,7 +92,9 @@ const list = ref([
85 type: 'onboarding', 92 type: 'onboarding',
86 icon: 'Order', // Represents a document 93 icon: 'Order', // Represents a document
87 iconColor: '#EF4444', // Red for PDF 94 iconColor: '#EF4444', // Red for PDF
88 - iconBgClass: 'bg-red-50' 95 + iconBgClass: 'bg-red-50',
96 + // 使用真实的可下载 PDF 文件(W3C 测试文件)
97 + downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
89 }, 98 },
90 { 99 {
91 id: 2, 100 id: 2,
...@@ -95,7 +104,9 @@ const list = ref([ ...@@ -95,7 +104,9 @@ const list = ref([
95 type: 'signing', 104 type: 'signing',
96 icon: 'Order', // Represents a document 105 icon: 'Order', // Represents a document
97 iconColor: '#2563EB', // Blue for Word 106 iconColor: '#2563EB', // Blue for Word
98 - iconBgClass: 'bg-blue-50' 107 + iconBgClass: 'bg-blue-50',
108 + // 使用真实的可下载图片(作为文档示例)
109 + downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%80%81%E6%9D%A5%E8%B5%9B%E9%9A%90%E7%A7%81%E6%94%BF%E7%AD%96.docx'
99 }, 110 },
100 { 111 {
101 id: 3, 112 id: 3,
...@@ -105,7 +116,9 @@ const list = ref([ ...@@ -105,7 +116,9 @@ const list = ref([
105 type: 'product', 116 type: 'product',
106 icon: 'Order', // Represents a document 117 icon: 'Order', // Represents a document
107 iconColor: '#F59E0B', // Orange for PPT 118 iconColor: '#F59E0B', // Orange for PPT
108 - iconBgClass: 'bg-orange-50' 119 + iconBgClass: 'bg-orange-50',
120 + // 使用另一个图片作为示例
121 + downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%82%A1%E5%88%A4%E5%90%88%E5%8F%8B%E7%94%A8%E7%9F%A5%E8%AF%86%E8%AF%B4%E6%98%8E20240112110417414.pptx'
109 }, 122 },
110 { 123 {
111 id: 4, 124 id: 4,
...@@ -115,7 +128,8 @@ const list = ref([ ...@@ -115,7 +128,8 @@ const list = ref([
115 type: 'other', 128 type: 'other',
116 icon: 'Edit', // Represents text 129 icon: 'Edit', // Represents text
117 iconColor: '#10B981', // Green for TXT 130 iconColor: '#10B981', // Green for TXT
118 - iconBgClass: 'bg-green-50' 131 + iconBgClass: 'bg-green-50',
132 + downloadUrl: '' // 空下载地址,用于测试无地址情况
119 } 133 }
120 ]) 134 ])
121 135
...@@ -128,6 +142,67 @@ const onView = (item) => { ...@@ -128,6 +142,67 @@ const onView = (item) => {
128 Taro.showToast({ title: `打开: ${item.title}`, icon: 'none' }) 142 Taro.showToast({ title: `打开: ${item.title}`, icon: 'none' })
129 } 143 }
130 144
145 +// 打开文件的通用函数
146 +const openFile = async (filePath, item) => {
147 + try {
148 + await Taro.openDocument({
149 + filePath: filePath,
150 + showMenu: true, // 显示右上角菜单,用户可以转发、保存等
151 + success: () => {
152 + console.log('文件打开成功')
153 + // 文件打开后,延迟提示用户如果看不到内容该如何操作
154 + const fileExt = item.title.split('.').pop()?.toLowerCase() || ''
155 + const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
156 +
157 + if (unsupportedFormats.includes(fileExt)) {
158 + setTimeout(() => {
159 + Taro.showToast({
160 + title: '如无法预览,请使用右上角菜单分享',
161 + icon: 'none',
162 + duration: 3000
163 + })
164 + }, 1500)
165 + }
166 + },
167 + fail: (err) => {
168 + console.error('打开文件失败:', err)
169 +
170 + // 获取文件扩展名
171 + const fileExt = item.title.split('.').pop()?.toLowerCase() || ''
172 +
173 + // 根据文件类型给出提示
174 + let message = '文件打开失败'
175 + let suggestion = ''
176 +
177 + if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) {
178 + message = '暂不支持预览 Office 文档'
179 + suggestion = '\n\n建议:\n1. 点击右上角"..."菜单\n2. 选择"发送给朋友"\n3. 在电脑或支持的应用中打开'
180 + } else if (['pdf'].includes(fileExt)) {
181 + message = 'PDF 文件打开失败'
182 + suggestion = '\n\n文件可能已损坏,请联系管理员'
183 + } else {
184 + message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件`
185 + suggestion = '\n\n请在电脑或其他应用中打开'
186 + }
187 +
188 + Taro.showModal({
189 + title: '提示',
190 + content: message + suggestion,
191 + showCancel: false,
192 + confirmText: '我知道了'
193 + })
194 + }
195 + })
196 + } catch (error) {
197 + console.error('打开文件异常:', error)
198 + Taro.showToast({
199 + title: '打开文件失败',
200 + icon: 'none',
201 + duration: 2000
202 + })
203 + }
204 +}
205 +
131 const onDelete = (item) => { 206 const onDelete = (item) => {
132 Taro.showModal({ 207 Taro.showModal({
133 title: '提示', 208 title: '提示',
...@@ -140,6 +215,97 @@ const onDelete = (item) => { ...@@ -140,6 +215,97 @@ const onDelete = (item) => {
140 } 215 }
141 }) 216 })
142 } 217 }
218 +
219 +const onDownload = (item) => {
220 + // 检查是否有下载地址
221 + if (!item.downloadUrl) {
222 + Taro.showToast({
223 + title: '该文件暂无下载地址',
224 + icon: 'none',
225 + duration: 2000
226 + })
227 + return
228 + }
229 +
230 + // 显示确认弹窗
231 + Taro.showModal({
232 + title: '下载确认',
233 + content: `确定要下载"${item.title}"吗?`,
234 + confirmText: '下载',
235 + cancelText: '取消',
236 + success: async (res) => {
237 + if (res.confirm) {
238 + // 显示加载提示
239 + Taro.showLoading({
240 + title: '下载中...',
241 + mask: true
242 + })
243 +
244 + try {
245 + // 下载文件
246 + const downloadResult = await Taro.downloadFile({
247 + url: item.downloadUrl
248 + })
249 +
250 + // 检查下载结果
251 + if (downloadResult.statusCode !== 200) {
252 + throw new Error(`下载失败: HTTP ${downloadResult.statusCode}`)
253 + }
254 +
255 + if (!downloadResult.tempFilePath) {
256 + throw new Error('下载失败: 未获取到文件')
257 + }
258 +
259 + // 隐藏加载提示
260 + Taro.hideLoading()
261 +
262 + // 获取文件扩展名
263 + const fileExt = item.title.split('.').pop()?.toLowerCase() || ''
264 +
265 + // 微信小程序对 Office 文档支持有限,提前提示用户
266 + const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
267 +
268 + if (unsupportedFormats.includes(fileExt)) {
269 + // 对于 Office 文档,先提示用户,但仍尝试打开
270 + Taro.showModal({
271 + title: '预览提示',
272 + content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
273 + confirmText: '继续',
274 + cancelText: '取消',
275 + success: (modalRes) => {
276 + if (modalRes.confirm) {
277 + openFile(downloadResult.tempFilePath, item)
278 + }
279 + }
280 + })
281 + } else {
282 + // 其他格式直接打开
283 + await openFile(downloadResult.tempFilePath, item)
284 + }
285 + } catch (error) {
286 + // 确保隐藏加载提示
287 + Taro.hideLoading()
288 +
289 + console.error('下载过程出错:', error)
290 +
291 + // 根据错误类型显示不同的提示
292 + let errorMessage = '下载失败,请重试'
293 + if (error.errMsg && error.errMsg.includes('network')) {
294 + errorMessage = '网络连接失败,请检查网络'
295 + } else if (error.errMsg && error.errMsg.includes('TLS')) {
296 + errorMessage = '安全连接失败,请检查网络'
297 + }
298 +
299 + Taro.showToast({
300 + title: errorMessage,
301 + icon: 'none',
302 + duration: 2000
303 + })
304 + }
305 + }
306 + }
307 + })
308 +}
143 </script> 309 </script>
144 310
145 <style lang="less"> 311 <style lang="less">
......