index.vue
11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
<!--
* @Date: 2026-01-30
* @Description: 资料列表页
-->
<template>
<div class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
<!-- Navigation Header -->
<NavHeader title="资料列表" />
<!-- Search Bar -->
<div class="px-[32rpx] mt-[32rpx]">
<div class="bg-white rounded-[12rpx] flex items-center px-[32rpx] py-[24rpx]">
<IconFont name="search" size="20" color="#9CA3AF" class="mr-[16rpx]" />
<input
v-model="searchValue"
type="text"
placeholder="搜索资料..."
class="flex-1 text-[28rpx] text-[#1F2937] placeholder-gray-400 bg-transparent outline-none"
@confirm="onSearch"
/>
</div>
</div>
<!-- Material List -->
<div class="px-[32rpx] mt-[40rpx]">
<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]" @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>
<div class="flex flex-col">
<span class="text-[#1F2937] text-[28rpx] font-normal leading-[1.2] mb-[14rpx] line-clamp-2">
{{ item.title }}
</span>
<span class="text-[#6B7280] text-[24rpx] leading-[1.3] line-clamp-1">
{{ item.desc }}
</span>
</div>
</div>
</div>
<div class="flex items-center mt-[16rpx] ml-[104rpx] pb-[32rpx]">
<span class="text-[#9CA3AF] text-[24rpx] mr-[40rpx]">{{ item.size }}</span>
<div class="flex items-center" @click="toggleCollect(item)">
<IconFont
:name="item.collected ? 'StarFill' : 'Star'"
size="12"
:color="item.collected ? '#F59E0B' : '#9CA3AF'"
class="mr-[8rpx]"
/>
<span :class="['text-[24rpx]', item.collected ? 'text-[#F59E0B]' : 'text-[#9CA3AF]']">
{{ item.collected ? '已收藏' : '收藏' }}
</span>
</div>
</div>
<!-- Divider -->
<div v-if="index < list.length - 1" class="h-[1px] bg-[#E5E7EB] w-full mb-[32rpx]"></div>
</div>
</div>
</div>
<!-- Tab Bar -->
<TabBar />
</div>
</template>
<script setup>
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('')
const list = ref([
{
title: '2024年保险代理人考试大纲.pdf',
desc: '最新考试范围与重点解析',
size: '2.1MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '2019-2023年真题完整版',
size: '5.3MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '高分学员备考心得',
size: '1.8MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '核心知识点快速记忆',
size: '3.2MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '考前冲刺模拟练习',
size: '4.5MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '保险相关法规详解',
size: '2.8MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '高频错题归纳总结',
size: '1.5MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '实务案例精选练习',
size: '3.9MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '最后一周复习要点',
size: '2.3MB',
iconName: 'Order',
iconColor: '#EF4444',
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',
desc: '应试策略与时间分配',
size: '1.2MB',
iconName: 'Order',
iconColor: '#EF4444',
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'
}
])
/**
* Search handler
*/
const onSearch = () => {
console.log('Searching for:', searchValue.value)
}
/**
* Toggle collect status
* @param {Object} item
*/
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>