hookehuyr

refactor(pages): 优化资料和收藏页面的文件打开交互

- 移除下载/查看按钮,改为点击内容直接打开文件
- 收藏页面:点击图标、标题、分类、日期触发打开
- 资料列表页面:点击标题和描述触发打开
- 将"下载中"改为"打开中",更符合用户心理预期
- 统一两个页面的文件打开逻辑和错误处理

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
20 <!-- List Section --> 20 <!-- List Section -->
21 <view class="w-[706rpx] mt-[32rpx] flex flex-col gap-[24rpx]"> 21 <view class="w-[706rpx] mt-[32rpx] flex flex-col gap-[24rpx]">
22 <view v-for="(item, index) in filteredList" :key="index" 22 <view v-for="(item, index) in filteredList" :key="index"
23 - class="bg-white rounded-[24rpx] p-[32rpx] shadow-sm flex items-center justify-between" @tap="onView(item)"> 23 + class="bg-white rounded-[24rpx] p-[32rpx] shadow-sm flex items-center justify-between">
24 - <view class="flex items-center flex-1 mr-[24rpx]"> 24 + <view class="flex items-center flex-1 mr-[24rpx]" @tap="onView(item)">
25 <!-- Icon (Text Type Icons as requested) --> 25 <!-- Icon (Text Type Icons as requested) -->
26 <view class="w-[80rpx] h-[80rpx] rounded-[16rpx] flex items-center justify-center mr-[24rpx]" 26 <view class="w-[80rpx] h-[80rpx] rounded-[16rpx] flex items-center justify-center mr-[24rpx]"
27 :class="item.iconBgClass"> 27 :class="item.iconBgClass">
...@@ -41,10 +41,6 @@ ...@@ -41,10 +41,6 @@
41 41
42 <!-- Action Buttons --> 42 <!-- Action Buttons -->
43 <view class="flex items-center gap-[16rpx]"> 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>
48 <!-- Delete Button --> 44 <!-- Delete Button -->
49 <view class="p-[10rpx]" @tap.stop="onDelete(item)"> 45 <view class="p-[10rpx]" @tap.stop="onDelete(item)">
50 <IconFont name="Del" size="18" color="#999" /> 46 <IconFont name="Del" size="18" color="#999" />
...@@ -139,7 +135,24 @@ const filteredList = computed(() => { ...@@ -139,7 +135,24 @@ const filteredList = computed(() => {
139 }) 135 })
140 136
141 const onView = (item) => { 137 const onView = (item) => {
142 - Taro.showToast({ title: `打开: ${item.title}`, icon: 'none' }) 138 + // 检查是否有下载地址
139 + if (!item.downloadUrl) {
140 + Taro.showToast({
141 + title: '该文件暂无查看地址',
142 + icon: 'none',
143 + duration: 2000
144 + })
145 + return
146 + }
147 +
148 + // 显示加载提示
149 + Taro.showLoading({
150 + title: '打开中...',
151 + mask: true
152 + })
153 +
154 + // 下载并打开文件
155 + downloadAndOpenFile(item)
143 } 156 }
144 157
145 // 打开文件的通用函数 158 // 打开文件的通用函数
...@@ -216,31 +229,8 @@ const onDelete = (item) => { ...@@ -216,31 +229,8 @@ const onDelete = (item) => {
216 }) 229 })
217 } 230 }
218 231
219 -const onDownload = (item) => { 232 +// 下载并打开文件的内部函数
220 - // 检查是否有下载地址 233 +const downloadAndOpenFile = async (item) => {
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 { 234 try {
245 // 下载文件 235 // 下载文件
246 const downloadResult = await Taro.downloadFile({ 236 const downloadResult = await Taro.downloadFile({
...@@ -249,11 +239,11 @@ const onDownload = (item) => { ...@@ -249,11 +239,11 @@ const onDownload = (item) => {
249 239
250 // 检查下载结果 240 // 检查下载结果
251 if (downloadResult.statusCode !== 200) { 241 if (downloadResult.statusCode !== 200) {
252 - throw new Error(`下载失败: HTTP ${downloadResult.statusCode}`) 242 + throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
253 } 243 }
254 244
255 if (!downloadResult.tempFilePath) { 245 if (!downloadResult.tempFilePath) {
256 - throw new Error('下载失败: 未获取到文件') 246 + throw new Error('打开失败: 未获取到文件')
257 } 247 }
258 248
259 // 隐藏加载提示 249 // 隐藏加载提示
...@@ -286,10 +276,10 @@ const onDownload = (item) => { ...@@ -286,10 +276,10 @@ const onDownload = (item) => {
286 // 确保隐藏加载提示 276 // 确保隐藏加载提示
287 Taro.hideLoading() 277 Taro.hideLoading()
288 278
289 - console.error('下载过程出错:', error) 279 + console.error('打开文件出错:', error)
290 280
291 // 根据错误类型显示不同的提示 281 // 根据错误类型显示不同的提示
292 - let errorMessage = '下载失败,请重试' 282 + let errorMessage = '打开失败,请重试'
293 if (error.errMsg && error.errMsg.includes('network')) { 283 if (error.errMsg && error.errMsg.includes('network')) {
294 errorMessage = '网络连接失败,请检查网络' 284 errorMessage = '网络连接失败,请检查网络'
295 } else if (error.errMsg && error.errMsg.includes('TLS')) { 285 } else if (error.errMsg && error.errMsg.includes('TLS')) {
...@@ -302,9 +292,6 @@ const onDownload = (item) => { ...@@ -302,9 +292,6 @@ const onDownload = (item) => {
302 duration: 2000 292 duration: 2000
303 }) 293 })
304 } 294 }
305 - }
306 - }
307 - })
308 } 295 }
309 </script> 296 </script>
310 297
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
26 <div class="bg-white rounded-[32rpx] p-[32rpx] shadow-sm"> 26 <div class="bg-white rounded-[32rpx] p-[32rpx] shadow-sm">
27 <div v-for="(item, index) in list" :key="index"> 27 <div v-for="(item, index) in list" :key="index">
28 <div class="flex justify-between items-start pt-[32rpx] first:pt-0"> 28 <div class="flex justify-between items-start pt-[32rpx] first:pt-0">
29 - <div class="flex items-start flex-1 mr-[20rpx]"> 29 + <div class="flex items-start flex-1 mr-[20rpx]" @tap="onView(item)">
30 <div class="w-[80rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-blue-50 rounded-[12rpx]"> 30 <div class="w-[80rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-blue-50 rounded-[12rpx]">
31 <IconFont :name="item.iconName || 'Order'" size="32" :color="item.iconColor || '#2563EB'" /> 31 <IconFont :name="item.iconName || 'Order'" size="32" :color="item.iconColor || '#2563EB'" />
32 </div> 32 </div>
...@@ -39,8 +39,6 @@ ...@@ -39,8 +39,6 @@
39 </span> 39 </span>
40 </div> 40 </div>
41 </div> 41 </div>
42 - <!-- Action Icon (Download/Detail) -->
43 - <IconFont name="Download" size="20" color="#9CA3AF" />
44 </div> 42 </div>
45 43
46 <div class="flex items-center mt-[16rpx] ml-[104rpx] pb-[32rpx]"> 44 <div class="flex items-center mt-[16rpx] ml-[104rpx] pb-[32rpx]">
...@@ -74,6 +72,7 @@ import { ref } from 'vue' ...@@ -74,6 +72,7 @@ import { ref } from 'vue'
74 import NavHeader from '@/components/NavHeader.vue' 72 import NavHeader from '@/components/NavHeader.vue'
75 import TabBar from '@/components/TabBar.vue' 73 import TabBar from '@/components/TabBar.vue'
76 import IconFont from '@/components/IconFont.vue' 74 import IconFont from '@/components/IconFont.vue'
75 +import Taro from '@tarojs/taro'
77 76
78 const searchValue = ref('') 77 const searchValue = ref('')
79 78
...@@ -84,7 +83,10 @@ const list = ref([ ...@@ -84,7 +83,10 @@ const list = ref([
84 size: '2.1MB', 83 size: '2.1MB',
85 iconName: 'Order', 84 iconName: 'Order',
86 iconColor: '#EF4444', 85 iconColor: '#EF4444',
87 - collected: true 86 + collected: true,
87 + // 添加文件相关数据
88 + fileName: '2024年保险代理人考试大纲.pdf',
89 + 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'
88 }, 90 },
89 { 91 {
90 title: '历年真题汇总及解析.pdf', 92 title: '历年真题汇总及解析.pdf',
...@@ -92,7 +94,9 @@ const list = ref([ ...@@ -92,7 +94,9 @@ const list = ref([
92 size: '5.3MB', 94 size: '5.3MB',
93 iconName: 'Order', 95 iconName: 'Order',
94 iconColor: '#EF4444', 96 iconColor: '#EF4444',
95 - collected: false 97 + collected: false,
98 + fileName: '历年真题汇总及解析.pdf',
99 + 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'
96 }, 100 },
97 { 101 {
98 title: '考试技巧与经验分享.pdf', 102 title: '考试技巧与经验分享.pdf',
...@@ -100,7 +104,9 @@ const list = ref([ ...@@ -100,7 +104,9 @@ const list = ref([
100 size: '1.8MB', 104 size: '1.8MB',
101 iconName: 'Order', 105 iconName: 'Order',
102 iconColor: '#EF4444', 106 iconColor: '#EF4444',
103 - collected: false 107 + collected: false,
108 + fileName: '考试技巧与经验分享.pdf',
109 + 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'
104 }, 110 },
105 { 111 {
106 title: '保险基础知识速记手册.pdf', 112 title: '保险基础知识速记手册.pdf',
...@@ -108,7 +114,9 @@ const list = ref([ ...@@ -108,7 +114,9 @@ const list = ref([
108 size: '3.2MB', 114 size: '3.2MB',
109 iconName: 'Order', 115 iconName: 'Order',
110 iconColor: '#EF4444', 116 iconColor: '#EF4444',
111 - collected: false 117 + collected: false,
118 + fileName: '保险基础知识速记手册.pdf',
119 + 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'
112 }, 120 },
113 { 121 {
114 title: '模拟试卷10套及答案.pdf', 122 title: '模拟试卷10套及答案.pdf',
...@@ -116,7 +124,9 @@ const list = ref([ ...@@ -116,7 +124,9 @@ const list = ref([
116 size: '4.5MB', 124 size: '4.5MB',
117 iconName: 'Order', 125 iconName: 'Order',
118 iconColor: '#EF4444', 126 iconColor: '#EF4444',
119 - collected: true 127 + collected: true,
128 + fileName: '模拟试卷10套及答案.pdf',
129 + 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'
120 }, 130 },
121 { 131 {
122 title: '法律法规重点条款解读.pdf', 132 title: '法律法规重点条款解读.pdf',
...@@ -124,7 +134,9 @@ const list = ref([ ...@@ -124,7 +134,9 @@ const list = ref([
124 size: '2.8MB', 134 size: '2.8MB',
125 iconName: 'Order', 135 iconName: 'Order',
126 iconColor: '#EF4444', 136 iconColor: '#EF4444',
127 - collected: false 137 + collected: false,
138 + fileName: '法律法规重点条款解读.pdf',
139 + 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'
128 }, 140 },
129 { 141 {
130 title: '考试常见易错题分析.pdf', 142 title: '考试常见易错题分析.pdf',
...@@ -132,7 +144,9 @@ const list = ref([ ...@@ -132,7 +144,9 @@ const list = ref([
132 size: '1.5MB', 144 size: '1.5MB',
133 iconName: 'Order', 145 iconName: 'Order',
134 iconColor: '#EF4444', 146 iconColor: '#EF4444',
135 - collected: false 147 + collected: false,
148 + fileName: '考试常见易错题分析.pdf',
149 + 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'
136 }, 150 },
137 { 151 {
138 title: '案例分析题库及解答.pdf', 152 title: '案例分析题库及解答.pdf',
...@@ -140,7 +154,9 @@ const list = ref([ ...@@ -140,7 +154,9 @@ const list = ref([
140 size: '3.9MB', 154 size: '3.9MB',
141 iconName: 'Order', 155 iconName: 'Order',
142 iconColor: '#EF4444', 156 iconColor: '#EF4444',
143 - collected: false 157 + collected: false,
158 + fileName: '案例分析题库及解答.pdf',
159 + 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'
144 }, 160 },
145 { 161 {
146 title: '考前冲刺复习资料.pdf', 162 title: '考前冲刺复习资料.pdf',
...@@ -148,7 +164,9 @@ const list = ref([ ...@@ -148,7 +164,9 @@ const list = ref([
148 size: '2.3MB', 164 size: '2.3MB',
149 iconName: 'Order', 165 iconName: 'Order',
150 iconColor: '#EF4444', 166 iconColor: '#EF4444',
151 - collected: false 167 + collected: false,
168 + fileName: '考前冲刺复习资料.pdf',
169 + 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'
152 }, 170 },
153 { 171 {
154 title: '考场注意事项及答题技巧.pdf', 172 title: '考场注意事项及答题技巧.pdf',
...@@ -156,7 +174,9 @@ const list = ref([ ...@@ -156,7 +174,9 @@ const list = ref([
156 size: '1.2MB', 174 size: '1.2MB',
157 iconName: 'Order', 175 iconName: 'Order',
158 iconColor: '#EF4444', 176 iconColor: '#EF4444',
159 - collected: false 177 + collected: false,
178 + fileName: '考场注意事项及答题技巧.pdf',
179 + 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'
160 } 180 }
161 ]) 181 ])
162 182
...@@ -174,4 +194,151 @@ const onSearch = () => { ...@@ -174,4 +194,151 @@ const onSearch = () => {
174 const toggleCollect = (item) => { 194 const toggleCollect = (item) => {
175 item.collected = !item.collected 195 item.collected = !item.collected
176 } 196 }
197 +
198 +// 打开文件的通用函数
199 +const openFile = async (filePath, item) => {
200 + try {
201 + await Taro.openDocument({
202 + filePath: filePath,
203 + showMenu: true, // 显示右上角菜单,用户可以转发、保存等
204 + success: () => {
205 + console.log('文件打开成功')
206 + // 文件打开后,延迟提示用户如果看不到内容该如何操作
207 + const fileExt = item.fileName.split('.').pop()?.toLowerCase() || ''
208 + const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
209 +
210 + if (unsupportedFormats.includes(fileExt)) {
211 + setTimeout(() => {
212 + Taro.showToast({
213 + title: '如无法预览,请使用右上角菜单分享',
214 + icon: 'none',
215 + duration: 3000
216 + })
217 + }, 1500)
218 + }
219 + },
220 + fail: (err) => {
221 + console.error('打开文件失败:', err)
222 +
223 + // 获取文件扩展名
224 + const fileExt = item.fileName.split('.').pop()?.toLowerCase() || ''
225 +
226 + // 根据文件类型给出提示
227 + let message = '文件打开失败'
228 + let suggestion = ''
229 +
230 + if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) {
231 + message = '暂不支持预览 Office 文档'
232 + suggestion = '\n\n建议:\n1. 点击右上角"..."菜单\n2. 选择"发送给朋友"\n3. 在电脑或支持的应用中打开'
233 + } else if (['pdf'].includes(fileExt)) {
234 + message = 'PDF 文件打开失败'
235 + suggestion = '\n\n文件可能已损坏,请联系管理员'
236 + } else {
237 + message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件`
238 + suggestion = '\n\n请在电脑或其他应用中打开'
239 + }
240 +
241 + Taro.showModal({
242 + title: '提示',
243 + content: message + suggestion,
244 + showCancel: false,
245 + confirmText: '我知道了'
246 + })
247 + }
248 + })
249 + } catch (error) {
250 + console.error('打开文件异常:', error)
251 + Taro.showToast({
252 + title: '打开文件失败',
253 + icon: 'none',
254 + duration: 2000
255 + })
256 + }
257 +}
258 +
259 +// 下载并打开文件的内部函数
260 +const downloadAndOpenFile = async (item) => {
261 + try {
262 + // 下载文件
263 + const downloadResult = await Taro.downloadFile({
264 + url: item.downloadUrl
265 + })
266 +
267 + // 检查下载结果
268 + if (downloadResult.statusCode !== 200) {
269 + throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
270 + }
271 +
272 + if (!downloadResult.tempFilePath) {
273 + throw new Error('打开失败: 未获取到文件')
274 + }
275 +
276 + // 隐藏加载提示
277 + Taro.hideLoading()
278 +
279 + // 获取文件扩展名
280 + const fileExt = item.fileName.split('.').pop()?.toLowerCase() || ''
281 +
282 + // 微信小程序对 Office 文档支持有限,提前提示用户
283 + const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
284 +
285 + if (unsupportedFormats.includes(fileExt)) {
286 + // 对于 Office 文档,先提示用户,但仍尝试打开
287 + Taro.showModal({
288 + title: '预览提示',
289 + content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
290 + confirmText: '继续',
291 + cancelText: '取消',
292 + success: (modalRes) => {
293 + if (modalRes.confirm) {
294 + openFile(downloadResult.tempFilePath, item)
295 + }
296 + }
297 + })
298 + } else {
299 + // 其他格式直接打开
300 + await openFile(downloadResult.tempFilePath, item)
301 + }
302 + } catch (error) {
303 + // 确保隐藏加载提示
304 + Taro.hideLoading()
305 +
306 + console.error('打开文件出错:', error)
307 +
308 + // 根据错误类型显示不同的提示
309 + let errorMessage = '打开失败,请重试'
310 + if (error.errMsg && error.errMsg.includes('network')) {
311 + errorMessage = '网络连接失败,请检查网络'
312 + } else if (error.errMsg && error.errMsg.includes('TLS')) {
313 + errorMessage = '安全连接失败,请检查网络'
314 + }
315 +
316 + Taro.showToast({
317 + title: errorMessage,
318 + icon: 'none',
319 + duration: 2000
320 + })
321 + }
322 +}
323 +
324 +const onView = (item) => {
325 + // 检查是否有下载地址
326 + if (!item.downloadUrl) {
327 + Taro.showToast({
328 + title: '该文件暂无查看地址',
329 + icon: 'none',
330 + duration: 2000
331 + })
332 + return
333 + }
334 +
335 + // 显示加载提示
336 + Taro.showLoading({
337 + title: '打开中...',
338 + mask: true
339 + })
340 +
341 + // 下载并打开文件
342 + downloadAndOpenFile(item)
343 +}
177 </script> 344 </script>
......