hookehuyr

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

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -20,8 +20,8 @@
<!-- List Section -->
<view class="w-[706rpx] mt-[32rpx] flex flex-col gap-[24rpx]">
<view v-for="(item, index) in filteredList" :key="index"
class="bg-white rounded-[24rpx] p-[32rpx] shadow-sm flex items-center justify-between" @tap="onView(item)">
<view class="flex items-center flex-1 mr-[24rpx]">
class="bg-white rounded-[24rpx] p-[32rpx] shadow-sm flex items-center justify-between">
<view class="flex items-center flex-1 mr-[24rpx]" @tap="onView(item)">
<!-- Icon (Text Type Icons as requested) -->
<view class="w-[80rpx] h-[80rpx] rounded-[16rpx] flex items-center justify-center mr-[24rpx]"
:class="item.iconBgClass">
......@@ -41,10 +41,6 @@
<!-- Action Buttons -->
<view class="flex items-center gap-[16rpx]">
<!-- Download Button -->
<view class="p-[10rpx]" @tap.stop="onDownload(item)">
<IconFont name="Download" size="18" color="#007AFF" />
</view>
<!-- Delete Button -->
<view class="p-[10rpx]" @tap.stop="onDelete(item)">
<IconFont name="Del" size="18" color="#999" />
......@@ -139,7 +135,24 @@ const filteredList = computed(() => {
})
const onView = (item) => {
Taro.showToast({ title: `打开: ${item.title}`, icon: 'none' })
// 检查是否有下载地址
if (!item.downloadUrl) {
Taro.showToast({
title: '该文件暂无查看地址',
icon: 'none',
duration: 2000
})
return
}
// 显示加载提示
Taro.showLoading({
title: '打开中...',
mask: true
})
// 下载并打开文件
downloadAndOpenFile(item)
}
// 打开文件的通用函数
......@@ -216,95 +229,69 @@ const onDelete = (item) => {
})
}
const onDownload = (item) => {
// 检查是否有下载地址
if (!item.downloadUrl) {
Taro.showToast({
title: '该文件暂无下载地址',
icon: 'none',
duration: 2000
// 下载并打开文件的内部函数
const downloadAndOpenFile = async (item) => {
try {
// 下载文件
const downloadResult = await Taro.downloadFile({
url: item.downloadUrl
})
return
}
// 显示确认弹窗
Taro.showModal({
title: '下载确认',
content: `确定要下载"${item.title}"吗?`,
confirmText: '下载',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
// 显示加载提示
Taro.showLoading({
title: '下载中...',
mask: true
})
try {
// 下载文件
const downloadResult = await Taro.downloadFile({
url: item.downloadUrl
})
// 检查下载结果
if (downloadResult.statusCode !== 200) {
throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
}
// 检查下载结果
if (downloadResult.statusCode !== 200) {
throw new Error(`下载失败: HTTP ${downloadResult.statusCode}`)
}
if (!downloadResult.tempFilePath) {
throw new Error('打开失败: 未获取到文件')
}
if (!downloadResult.tempFilePath) {
throw new Error('下载失败: 未获取到文件')
// 隐藏加载提示
Taro.hideLoading()
// 获取文件扩展名
const fileExt = item.title.split('.').pop()?.toLowerCase() || ''
// 微信小程序对 Office 文档支持有限,提前提示用户
const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
if (unsupportedFormats.includes(fileExt)) {
// 对于 Office 文档,先提示用户,但仍尝试打开
Taro.showModal({
title: '预览提示',
content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
confirmText: '继续',
cancelText: '取消',
success: (modalRes) => {
if (modalRes.confirm) {
openFile(downloadResult.tempFilePath, item)
}
}
})
} else {
// 其他格式直接打开
await openFile(downloadResult.tempFilePath, item)
}
} catch (error) {
// 确保隐藏加载提示
Taro.hideLoading()
// 隐藏加载提示
Taro.hideLoading()
// 获取文件扩展名
const fileExt = item.title.split('.').pop()?.toLowerCase() || ''
// 微信小程序对 Office 文档支持有限,提前提示用户
const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
if (unsupportedFormats.includes(fileExt)) {
// 对于 Office 文档,先提示用户,但仍尝试打开
Taro.showModal({
title: '预览提示',
content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
confirmText: '继续',
cancelText: '取消',
success: (modalRes) => {
if (modalRes.confirm) {
openFile(downloadResult.tempFilePath, item)
}
}
})
} else {
// 其他格式直接打开
await openFile(downloadResult.tempFilePath, item)
}
} catch (error) {
// 确保隐藏加载提示
Taro.hideLoading()
console.error('下载过程出错:', error)
// 根据错误类型显示不同的提示
let errorMessage = '下载失败,请重试'
if (error.errMsg && error.errMsg.includes('network')) {
errorMessage = '网络连接失败,请检查网络'
} else if (error.errMsg && error.errMsg.includes('TLS')) {
errorMessage = '安全连接失败,请检查网络'
}
console.error('打开文件出错:', error)
Taro.showToast({
title: errorMessage,
icon: 'none',
duration: 2000
})
}
}
// 根据错误类型显示不同的提示
let errorMessage = '打开失败,请重试'
if (error.errMsg && error.errMsg.includes('network')) {
errorMessage = '网络连接失败,请检查网络'
} else if (error.errMsg && error.errMsg.includes('TLS')) {
errorMessage = '安全连接失败,请检查网络'
}
})
Taro.showToast({
title: errorMessage,
icon: 'none',
duration: 2000
})
}
}
</script>
......
......@@ -26,7 +26,7 @@
<div class="bg-white rounded-[32rpx] p-[32rpx] shadow-sm">
<div v-for="(item, index) in list" :key="index">
<div class="flex justify-between items-start pt-[32rpx] first:pt-0">
<div class="flex items-start flex-1 mr-[20rpx]">
<div class="flex items-start flex-1 mr-[20rpx]" @tap="onView(item)">
<div class="w-[80rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-blue-50 rounded-[12rpx]">
<IconFont :name="item.iconName || 'Order'" size="32" :color="item.iconColor || '#2563EB'" />
</div>
......@@ -39,8 +39,6 @@
</span>
</div>
</div>
<!-- Action Icon (Download/Detail) -->
<IconFont name="Download" size="20" color="#9CA3AF" />
</div>
<div class="flex items-center mt-[16rpx] ml-[104rpx] pb-[32rpx]">
......@@ -74,6 +72,7 @@ import { ref } from 'vue'
import NavHeader from '@/components/NavHeader.vue'
import TabBar from '@/components/TabBar.vue'
import IconFont from '@/components/IconFont.vue'
import Taro from '@tarojs/taro'
const searchValue = ref('')
......@@ -84,7 +83,10 @@ const list = ref([
size: '2.1MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: true
collected: true,
// 添加文件相关数据
fileName: '2024年保险代理人考试大纲.pdf',
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'
},
{
title: '历年真题汇总及解析.pdf',
......@@ -92,7 +94,9 @@ const list = ref([
size: '5.3MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '历年真题汇总及解析.pdf',
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'
},
{
title: '考试技巧与经验分享.pdf',
......@@ -100,7 +104,9 @@ const list = ref([
size: '1.8MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '考试技巧与经验分享.pdf',
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'
},
{
title: '保险基础知识速记手册.pdf',
......@@ -108,7 +114,9 @@ const list = ref([
size: '3.2MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '保险基础知识速记手册.pdf',
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'
},
{
title: '模拟试卷10套及答案.pdf',
......@@ -116,7 +124,9 @@ const list = ref([
size: '4.5MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: true
collected: true,
fileName: '模拟试卷10套及答案.pdf',
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'
},
{
title: '法律法规重点条款解读.pdf',
......@@ -124,7 +134,9 @@ const list = ref([
size: '2.8MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '法律法规重点条款解读.pdf',
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'
},
{
title: '考试常见易错题分析.pdf',
......@@ -132,7 +144,9 @@ const list = ref([
size: '1.5MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '考试常见易错题分析.pdf',
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'
},
{
title: '案例分析题库及解答.pdf',
......@@ -140,7 +154,9 @@ const list = ref([
size: '3.9MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '案例分析题库及解答.pdf',
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'
},
{
title: '考前冲刺复习资料.pdf',
......@@ -148,7 +164,9 @@ const list = ref([
size: '2.3MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '考前冲刺复习资料.pdf',
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'
},
{
title: '考场注意事项及答题技巧.pdf',
......@@ -156,7 +174,9 @@ const list = ref([
size: '1.2MB',
iconName: 'Order',
iconColor: '#EF4444',
collected: false
collected: false,
fileName: '考场注意事项及答题技巧.pdf',
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'
}
])
......@@ -174,4 +194,151 @@ const onSearch = () => {
const toggleCollect = (item) => {
item.collected = !item.collected
}
// 打开文件的通用函数
const openFile = async (filePath, item) => {
try {
await Taro.openDocument({
filePath: filePath,
showMenu: true, // 显示右上角菜单,用户可以转发、保存等
success: () => {
console.log('文件打开成功')
// 文件打开后,延迟提示用户如果看不到内容该如何操作
const fileExt = item.fileName.split('.').pop()?.toLowerCase() || ''
const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
if (unsupportedFormats.includes(fileExt)) {
setTimeout(() => {
Taro.showToast({
title: '如无法预览,请使用右上角菜单分享',
icon: 'none',
duration: 3000
})
}, 1500)
}
},
fail: (err) => {
console.error('打开文件失败:', err)
// 获取文件扩展名
const fileExt = item.fileName.split('.').pop()?.toLowerCase() || ''
// 根据文件类型给出提示
let message = '文件打开失败'
let suggestion = ''
if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) {
message = '暂不支持预览 Office 文档'
suggestion = '\n\n建议:\n1. 点击右上角"..."菜单\n2. 选择"发送给朋友"\n3. 在电脑或支持的应用中打开'
} else if (['pdf'].includes(fileExt)) {
message = 'PDF 文件打开失败'
suggestion = '\n\n文件可能已损坏,请联系管理员'
} else {
message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件`
suggestion = '\n\n请在电脑或其他应用中打开'
}
Taro.showModal({
title: '提示',
content: message + suggestion,
showCancel: false,
confirmText: '我知道了'
})
}
})
} catch (error) {
console.error('打开文件异常:', error)
Taro.showToast({
title: '打开文件失败',
icon: 'none',
duration: 2000
})
}
}
// 下载并打开文件的内部函数
const downloadAndOpenFile = async (item) => {
try {
// 下载文件
const downloadResult = await Taro.downloadFile({
url: item.downloadUrl
})
// 检查下载结果
if (downloadResult.statusCode !== 200) {
throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
}
if (!downloadResult.tempFilePath) {
throw new Error('打开失败: 未获取到文件')
}
// 隐藏加载提示
Taro.hideLoading()
// 获取文件扩展名
const fileExt = item.fileName.split('.').pop()?.toLowerCase() || ''
// 微信小程序对 Office 文档支持有限,提前提示用户
const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
if (unsupportedFormats.includes(fileExt)) {
// 对于 Office 文档,先提示用户,但仍尝试打开
Taro.showModal({
title: '预览提示',
content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
confirmText: '继续',
cancelText: '取消',
success: (modalRes) => {
if (modalRes.confirm) {
openFile(downloadResult.tempFilePath, item)
}
}
})
} else {
// 其他格式直接打开
await openFile(downloadResult.tempFilePath, item)
}
} catch (error) {
// 确保隐藏加载提示
Taro.hideLoading()
console.error('打开文件出错:', error)
// 根据错误类型显示不同的提示
let errorMessage = '打开失败,请重试'
if (error.errMsg && error.errMsg.includes('network')) {
errorMessage = '网络连接失败,请检查网络'
} else if (error.errMsg && error.errMsg.includes('TLS')) {
errorMessage = '安全连接失败,请检查网络'
}
Taro.showToast({
title: errorMessage,
icon: 'none',
duration: 2000
})
}
}
const onView = (item) => {
// 检查是否有下载地址
if (!item.downloadUrl) {
Taro.showToast({
title: '该文件暂无查看地址',
icon: 'none',
duration: 2000
})
return
}
// 显示加载提示
Taro.showLoading({
title: '打开中...',
mask: true
})
// 下载并打开文件
downloadAndOpenFile(item)
}
</script>
......