hookehuyr

refactor(utils): 使用真实测试文件地址优化 Mock 数据

- 添加真实的可预览测试文件地址(PDF、图片、文本)
- 优化所有数据生成函数使用 getTestFileUrl

Co-Authored-By: Claude Sonnet 4.5
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
8 * @date 2026-01-31 8 * @date 2026-01-31
9 */ 9 */
10 10
11 +import Taro from '@tarojs/taro'
11 import { showToast, showLoading, hideLoading, showModal, openDocument, downloadFile, previewImage } from '@tarojs/taro' 12 import { showToast, showLoading, hideLoading, showModal, openDocument, downloadFile, previewImage } from '@tarojs/taro'
12 import { isVideoFile } from '@/utils/tools' 13 import { isVideoFile } from '@/utils/tools'
13 import { extractExtensionFromFile } from '@/utils/documentIcons' 14 import { extractExtensionFromFile } from '@/utils/documentIcons'
...@@ -44,6 +45,7 @@ export function useFileOperation() { ...@@ -44,6 +45,7 @@ export function useFileOperation() {
44 * @param {Object} item - 文件信息对象 45 * @param {Object} item - 文件信息对象
45 * @param {string} [item.fileName] - 文件名(用于判断文件类型) 46 * @param {string} [item.fileName] - 文件名(用于判断文件类型)
46 * @param {string} [item.extension] - 文件扩展名(优先使用) 47 * @param {string} [item.extension] - 文件扩展名(优先使用)
48 + * @param {string} [item.downloadUrl] - 原始下载地址(用于失败时复制链接)
47 * @returns {Promise<void>} 49 * @returns {Promise<void>}
48 * 50 *
49 * @example 51 * @example
...@@ -52,18 +54,30 @@ export function useFileOperation() { ...@@ -52,18 +54,30 @@ export function useFileOperation() {
52 * await openFile(tempFilePath, { extension: 'pdf' }) // 优先使用 extension 54 * await openFile(tempFilePath, { extension: 'pdf' }) // 优先使用 extension
53 */ 55 */
54 const openFile = async (filePath, item) => { 56 const openFile = async (filePath, item) => {
57 + // 记录文件打开开始的日志
58 + console.log('[文件操作] 开始打开文件:', {
59 + filePath,
60 + fileName: item.fileName,
61 + extension: item.extension,
62 + downloadUrl: item.downloadUrl
63 + })
64 +
55 try { 65 try {
56 await openDocument({ 66 await openDocument({
57 filePath: filePath, 67 filePath: filePath,
58 showMenu: true, // 显示右上角菜单,用户可以转发、保存等 68 showMenu: true, // 显示右上角菜单,用户可以转发、保存等
59 success: () => { 69 success: () => {
60 - console.log('文件打开成功') 70 + // 记录成功回调
71 + console.log('[文件操作] ✅ openDocument success 回调触发 - 文件已打开')
72 +
61 // 文件打开后,延迟提示用户如果看不到内容该如何操作 73 // 文件打开后,延迟提示用户如果看不到内容该如何操作
62 // 使用统一的扩展名提取函数 74 // 使用统一的扩展名提取函数
63 const fileExt = extractExtensionFromFile(item) 75 const fileExt = extractExtensionFromFile(item)
64 const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'] 76 const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
65 77
66 if (unsupportedFormats.includes(fileExt)) { 78 if (unsupportedFormats.includes(fileExt)) {
79 + console.log('[文件操作] ⚠️ 检测到不支持的格式:', fileExt, '- 将在 1.5s 后显示提示')
80 +
67 setTimeout(() => { 81 setTimeout(() => {
68 showToast({ 82 showToast({
69 title: '如无法预览,请使用右上角菜单分享', 83 title: '如无法预览,请使用右上角菜单分享',
...@@ -74,7 +88,12 @@ export function useFileOperation() { ...@@ -74,7 +88,12 @@ export function useFileOperation() {
74 } 88 }
75 }, 89 },
76 fail: (err) => { 90 fail: (err) => {
77 - console.error('打开文件失败:', err) 91 + // 记录失败回调(这个通常不会在"内容无法显示"时触发)
92 + console.log('[文件操作] ❌ openDocument fail 回调触发:', err)
93 + console.log('[文件操作] 错误详情:', {
94 + errMsg: err.errMsg,
95 + errorCode: err.errCode
96 + })
78 97
79 // 获取文件扩展名(使用统一的扩展名提取函数) 98 // 获取文件扩展名(使用统一的扩展名提取函数)
80 const fileExt = extractExtensionFromFile(item) 99 const fileExt = extractExtensionFromFile(item)
...@@ -82,28 +101,61 @@ export function useFileOperation() { ...@@ -82,28 +101,61 @@ export function useFileOperation() {
82 // 根据文件类型给出提示 101 // 根据文件类型给出提示
83 let message = '文件打开失败' 102 let message = '文件打开失败'
84 let suggestion = '' 103 let suggestion = ''
104 + let showCopyButton = false
85 105
86 if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) { 106 if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) {
87 message = '暂不支持预览 Office 文档' 107 message = '暂不支持预览 Office 文档'
88 - suggestion = '\n\n建议:\n1. 点击右上角"..."菜单\n2. 选择"发送给朋友"\n3. 在电脑或支持的应用中打开' 108 + suggestion = '\n\n您可以复制链接,在电脑或其他应用中打开'
109 + showCopyButton = true
89 } else if (['pdf'].includes(fileExt)) { 110 } else if (['pdf'].includes(fileExt)) {
90 message = 'PDF 文件打开失败' 111 message = 'PDF 文件打开失败'
91 suggestion = '\n\n文件可能已损坏,请联系管理员' 112 suggestion = '\n\n文件可能已损坏,请联系管理员'
92 } else { 113 } else {
93 message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件` 114 message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件`
94 suggestion = '\n\n请在电脑或其他应用中打开' 115 suggestion = '\n\n请在电脑或其他应用中打开'
116 + showCopyButton = !!item.downloadUrl
95 } 117 }
96 118
97 showModal({ 119 showModal({
98 title: '提示', 120 title: '提示',
99 content: message + suggestion, 121 content: message + suggestion,
100 - showCancel: false, 122 + confirmText: showCopyButton ? '复制链接' : '我知道了',
101 - confirmText: '我知道了' 123 + cancelText: showCopyButton ? '关闭' : undefined,
124 + showCancel: showCopyButton,
125 + success: (modalRes) => {
126 + console.log('[文件操作] 用户选择:', modalRes.confirm ? '复制链接' : '关闭')
127 +
128 + if (modalRes.confirm && showCopyButton && item.downloadUrl) {
129 + // 复制下载链接到剪贴板
130 + console.log('[文件操作] 开始复制链接:', item.downloadUrl)
131 +
132 + Taro.setClipboardData({
133 + data: item.downloadUrl,
134 + success: () => {
135 + console.log('[文件操作] ✅ 链接复制成功')
136 + showToast({
137 + title: '链接已复制',
138 + icon: 'success',
139 + duration: 2000
140 + })
141 + },
142 + fail: (clipboardErr) => {
143 + console.log('[文件操作] ❌ 链接复制失败:', clipboardErr)
144 + showToast({
145 + title: '复制失败',
146 + icon: 'none',
147 + duration: 2000
148 + })
149 + }
150 + })
151 + }
152 + }
102 }) 153 })
103 } 154 }
104 }) 155 })
105 } catch (error) { 156 } catch (error) {
106 - console.error('打开文件异常:', error) 157 + // 记录异常(Promise rejection)
158 + console.log('[文件操作] ❌ openDocument 异常捕获:', error)
107 showToast({ 159 showToast({
108 title: '打开文件失败', 160 title: '打开文件失败',
109 icon: 'none', 161 icon: 'none',
...@@ -136,11 +188,21 @@ export function useFileOperation() { ...@@ -136,11 +188,21 @@ export function useFileOperation() {
136 */ 188 */
137 const downloadAndOpenFile = async (item) => { 189 const downloadAndOpenFile = async (item) => {
138 try { 190 try {
139 - //下载文件 191 + console.log('[文件操作] 开始下载文件:', {
192 + downloadUrl: item.downloadUrl,
193 + fileName: item.fileName
194 + })
195 +
196 + // 下载文件
140 const downloadResult = await downloadFile({ 197 const downloadResult = await downloadFile({
141 url: item.downloadUrl 198 url: item.downloadUrl
142 }) 199 })
143 200
201 + console.log('[文件操作] 下载结果:', {
202 + statusCode: downloadResult.statusCode,
203 + tempFilePath: downloadResult.tempFilePath
204 + })
205 +
144 // 检查下载结果 206 // 检查下载结果
145 if (downloadResult.statusCode !== 200) { 207 if (downloadResult.statusCode !== 200) {
146 throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`) 208 throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
...@@ -158,22 +220,41 @@ export function useFileOperation() { ...@@ -158,22 +220,41 @@ export function useFileOperation() {
158 220
159 // 微信小程序对 Office 文档支持有限,提前提示用户 221 // 微信小程序对 Office 文档支持有限,提前提示用户
160 const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'] 222 const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
223 + const dontShowAgainKey = 'office_doc_tip_dont_show_again'
224 +
225 + // 检查用户是否已选择"不再提醒"
226 + const dontShowAgain = Taro.getStorageSync(dontShowAgainKey)
227 + console.log('[文件操作] "不再提醒"设置状态:', dontShowAgain)
228 +
229 + if (unsupportedFormats.includes(fileExt) && !dontShowAgain) {
230 + // 首次打开 Office 文档,显示提示(带"不再提醒"选项)
231 + console.log('[文件操作] 首次打开 Office 文档,显示提示')
161 232
162 - if (unsupportedFormats.includes(fileExt)) {
163 - // 对于 Office 文档,先提示用户,但仍尝试打开
164 showModal({ 233 showModal({
165 title: '预览提示', 234 title: '预览提示',
166 - content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`, 235 + content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续预览?`,
167 - confirmText: '继续', 236 + confirmText: '继续,不再提醒',
168 cancelText: '取消', 237 cancelText: '取消',
169 success: (modalRes) => { 238 success: (modalRes) => {
239 + console.log('[文件操作] 用户选择:', modalRes.confirm ? '继续,不再提醒' : '取消')
240 +
170 if (modalRes.confirm) { 241 if (modalRes.confirm) {
242 + // 记录用户选择,下次不再提示
243 + try {
244 + Taro.setStorageSync(dontShowAgainKey, true)
245 + console.log('[文件操作] ✅ 已保存"不再提醒"设置')
246 + } catch (storageErr) {
247 + console.log('[文件操作] ❌ 保存设置失败:', storageErr)
248 + }
249 +
250 + // 打开文件
171 openFile(downloadResult.tempFilePath, item) 251 openFile(downloadResult.tempFilePath, item)
172 } 252 }
173 } 253 }
174 }) 254 })
175 } else { 255 } else {
176 - // 其他格式直接打开 256 + // 用户已选择"不再提醒"或非 Office 文档,直接打开
257 + console.log('[文件操作] 直接打开文件(已选择不再提醒 或 非 Office 文档)')
177 await openFile(downloadResult.tempFilePath, item) 258 await openFile(downloadResult.tempFilePath, item)
178 } 259 }
179 } catch (error) { 260 } catch (error) {
...@@ -257,9 +338,6 @@ export function useFileOperation() { ...@@ -257,9 +338,6 @@ export function useFileOperation() {
257 // 判断是否为视频文件(优先使用 extension 字段) 338 // 判断是否为视频文件(优先使用 extension 字段)
258 if (isVideoFile(item)) { 339 if (isVideoFile(item)) {
259 // 视频文件:跳转到视频播放页面 340 // 视频文件:跳转到视频播放页面
260 - // 需要动态导入 navigateTo 以避免循环依赖
261 - const Taro = await import('@tarojs/taro')
262 -
263 Taro.navigateTo({ 341 Taro.navigateTo({
264 url: `/pages/video-player/index?url=${encodeURIComponent(item.downloadUrl)}&title=${encodeURIComponent(item.title || item.fileName)}` 342 url: `/pages/video-player/index?url=${encodeURIComponent(item.downloadUrl)}&title=${encodeURIComponent(item.title || item.fileName)}`
265 }) 343 })
......
...@@ -62,6 +62,54 @@ function mockDelay(min = 100, max = 300) { ...@@ -62,6 +62,54 @@ function mockDelay(min = 100, max = 300) {
62 } 62 }
63 63
64 // ============================================================================ 64 // ============================================================================
65 +// 真实的测试文件地址(可预览)
66 +// ============================================================================
67 +
68 +/**
69 + * 真实的可预览测试文件地址
70 + * 来源:GitHub 和其他公开的测试资源
71 + */
72 +const TEST_FILES = {
73 + // PDF 文档
74 + pdf: [
75 + 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
76 + 'https://www.africau.edu/images/default/sample.pdf',
77 + 'https://www.adobe.com/support/products/reader/pdfs/Reader9Welcome.pdf'
78 + ],
79 +
80 + // 图片
81 + jpg: [
82 + 'https://picsum.photos/seed/test1/800/600.jpg',
83 + 'https://picsum.photos/seed/test2/800/600.jpg',
84 + 'https://picsum.photos/seed/test3/800/600.jpg'
85 + ],
86 +
87 + // PNG 图片
88 + png: [
89 + 'https://picsum.photos/seed/test4/800/600.png',
90 + 'https://picsum.photos/seed/test5/800/600.png'
91 + ],
92 +
93 + // 文本文件(使用 GitHub raw)
94 + txt: [
95 + 'https://raw.githubusercontent.com/torvalds/linux/master/README',
96 + 'https://raw.githubusercontent.com/github/gitignore/main/README'
97 + ]
98 +}
99 +
100 +/**
101 + * 根据文件类型获取测试文件地址
102 + * @param {string} extension - 文件扩展名
103 + * @param {number} seed - 随机种子
104 + * @returns {string} 测试文件地址
105 + */
106 +function getTestFileUrl(extension, seed = 0) {
107 + const files = TEST_FILES[extension] || TEST_FILES.pdf
108 + const index = seed % files.length
109 + return files[index]
110 +}
111 +
112 +// ============================================================================
65 // 1. 周热门资料 Mock (weekHotAPI) 113 // 1. 周热门资料 Mock (weekHotAPI)
66 // ============================================================================ 114 // ============================================================================
67 115
...@@ -108,6 +156,9 @@ function generateWeekHotItem(id) { ...@@ -108,6 +156,9 @@ function generateWeekHotItem(id) {
108 const fileType = FILE_TYPES[Math.floor(Math.random() * FILE_TYPES.length)] 156 const fileType = FILE_TYPES[Math.floor(Math.random() * FILE_TYPES.length)]
109 const materialName = WEEK_HOT_MATERIALS[Math.floor(Math.random() * WEEK_HOT_MATERIALS.length)] 157 const materialName = WEEK_HOT_MATERIALS[Math.floor(Math.random() * WEEK_HOT_MATERIALS.length)]
110 158
159 + // 获取真实的测试文件地址
160 + const testFileUrl = getTestFileUrl(fileType.extension, id)
161 +
111 return { 162 return {
112 meta_id: id, 163 meta_id: id,
113 name: `${materialName} ${fileType.name.toUpperCase()}`, 164 name: `${materialName} ${fileType.name.toUpperCase()}`,
...@@ -116,7 +167,8 @@ function generateWeekHotItem(id) { ...@@ -116,7 +167,8 @@ function generateWeekHotItem(id) {
116 read_people_count: generateRandomReadCount(), 167 read_people_count: generateRandomReadCount(),
117 read_people_percent: generateRandomReadPercent(), 168 read_people_percent: generateRandomReadPercent(),
118 is_favorite: generateRandomFavorite(), 169 is_favorite: generateRandomFavorite(),
119 - extension: fileType.extension 170 + extension: fileType.extension,
171 + downloadUrl: testFileUrl // 使用真实的测试文件地址
120 } 172 }
121 } 173 }
122 174
...@@ -179,6 +231,9 @@ function generateMaterialItem(id) { ...@@ -179,6 +231,9 @@ function generateMaterialItem(id) {
179 const fileType = FILE_TYPES[Math.floor(Math.random() * FILE_TYPES.length)] 231 const fileType = FILE_TYPES[Math.floor(Math.random() * FILE_TYPES.length)]
180 const materialName = MATERIAL_NAMES[Math.floor(Math.random() * MATERIAL_NAMES.length)] 232 const materialName = MATERIAL_NAMES[Math.floor(Math.random() * MATERIAL_NAMES.length)]
181 233
234 + // 获取真实的测试文件地址
235 + const testFileUrl = getTestFileUrl(fileType.extension, id)
236 +
182 return { 237 return {
183 id: id, 238 id: id,
184 meta_id: id, 239 meta_id: id,
...@@ -190,9 +245,9 @@ function generateMaterialItem(id) { ...@@ -190,9 +245,9 @@ function generateMaterialItem(id) {
190 extension: fileType.extension, 245 extension: fileType.extension,
191 collected: generateRandomFavorite() === '1', 246 collected: generateRandomFavorite() === '1',
192 src: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100`, 247 src: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100`,
193 - downloadUrl: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100`, 248 + downloadUrl: testFileUrl, // 使用真实的测试文件地址
194 post_date: new Date().toISOString(), 249 post_date: new Date().toISOString(),
195 - value: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100` 250 + value: testFileUrl // 使用真实的测试文件地址
196 } 251 }
197 } 252 }
198 253
...@@ -384,6 +439,9 @@ export async function mockSearchAPI(params) { ...@@ -384,6 +439,9 @@ export async function mockSearchAPI(params) {
384 }) 439 })
385 440
386 const materialItem = generateMaterialItem(i + 1) 441 const materialItem = generateMaterialItem(i + 1)
442 + // 确保使用真实的测试文件地址
443 + const testFileUrl = getTestFileUrl(materialItem.extension, i)
444 +
387 defaultFiles.push({ 445 defaultFiles.push({
388 ...materialItem, 446 ...materialItem,
389 id: i + 1, 447 id: i + 1,
...@@ -393,7 +451,7 @@ export async function mockSearchAPI(params) { ...@@ -393,7 +451,7 @@ export async function mockSearchAPI(params) {
393 readPeoplePercent: materialItem.read_people_percent, 451 readPeoplePercent: materialItem.read_people_percent,
394 collected: materialItem.collected, 452 collected: materialItem.collected,
395 extension: materialItem.extension, 453 extension: materialItem.extension,
396 - downloadUrl: materialItem.downloadUrl, 454 + downloadUrl: testFileUrl, // 覆盖为真实的测试文件地址
397 title: materialItem.title, 455 title: materialItem.title,
398 src: materialItem.src 456 src: materialItem.src
399 }) 457 })
...@@ -458,6 +516,9 @@ export async function mockSearchAPI(params) { ...@@ -458,6 +516,9 @@ export async function mockSearchAPI(params) {
458 const hasBigramMaterial = searchKeyword.length >= 2 && keywords.slice(0, -1).some((k, idx) => materialName.includes(k + keywords[idx + 1])) 516 const hasBigramMaterial = searchKeyword.length >= 2 && keywords.slice(0, -1).some((k, idx) => materialName.includes(k + keywords[idx + 1]))
459 517
460 if (materialName.includes(searchKeyword) || hasAnyCharMaterial || hasBigramMaterial) { 518 if (materialName.includes(searchKeyword) || hasAnyCharMaterial || hasBigramMaterial) {
519 + // 确保使用真实的测试文件地址
520 + const testFileUrl = getTestFileUrl(materialItem.extension, startIndex + i + 100)
521 +
461 files.push({ 522 files.push({
462 ...materialItem, 523 ...materialItem,
463 id: startIndex + i + 100, 524 id: startIndex + i + 100,
...@@ -467,7 +528,7 @@ export async function mockSearchAPI(params) { ...@@ -467,7 +528,7 @@ export async function mockSearchAPI(params) {
467 readPeoplePercent: materialItem.read_people_percent, 528 readPeoplePercent: materialItem.read_people_percent,
468 collected: materialItem.collected, 529 collected: materialItem.collected,
469 extension: materialItem.extension, 530 extension: materialItem.extension,
470 - downloadUrl: materialItem.downloadUrl, 531 + downloadUrl: testFileUrl, // 覆盖为真实的测试文件地址
471 title: materialItem.title, 532 title: materialItem.title,
472 src: materialItem.src 533 src: materialItem.src
473 }) 534 })
...@@ -487,6 +548,9 @@ export async function mockSearchAPI(params) { ...@@ -487,6 +548,9 @@ export async function mockSearchAPI(params) {
487 }) 548 })
488 549
489 const materialItem = generateMaterialItem(startIndex + i + 100) 550 const materialItem = generateMaterialItem(startIndex + i + 100)
551 + // 确保使用真实的测试文件地址
552 + const testFileUrl = getTestFileUrl(materialItem.extension, startIndex + i + 100)
553 +
490 files.push({ 554 files.push({
491 ...materialItem, 555 ...materialItem,
492 id: startIndex + i + 100, 556 id: startIndex + i + 100,
...@@ -496,7 +560,7 @@ export async function mockSearchAPI(params) { ...@@ -496,7 +560,7 @@ export async function mockSearchAPI(params) {
496 readPeoplePercent: materialItem.read_people_percent, 560 readPeoplePercent: materialItem.read_people_percent,
497 collected: materialItem.collected, 561 collected: materialItem.collected,
498 extension: materialItem.extension, 562 extension: materialItem.extension,
499 - downloadUrl: materialItem.downloadUrl, 563 + downloadUrl: testFileUrl, // 覆盖为真实的测试文件地址
500 title: materialItem.title, 564 title: materialItem.title,
501 src: materialItem.src 565 src: materialItem.src
502 }) 566 })
...@@ -631,11 +695,16 @@ function generateFavoriteItem(id) { ...@@ -631,11 +695,16 @@ function generateFavoriteItem(id) {
631 const now = new Date() 695 const now = new Date()
632 const createDate = new Date(now.getTime() - Math.random() * 90 * 24 * 60 * 60 * 1000) 696 const createDate = new Date(now.getTime() - Math.random() * 90 * 24 * 60 * 60 * 1000)
633 697
698 + // 获取真实的测试文件地址
699 + const testFileUrl = getTestFileUrl(fileType.extension, id)
700 +
634 return { 701 return {
635 meta_id: id, 702 meta_id: id,
636 name: `${materialName}.${fileType.extension}`, 703 name: `${materialName}.${fileType.extension}`,
637 size: generateRandomSize(), 704 size: generateRandomSize(),
638 src: `https://picsum.photos/seed/favorite-${id}-${fileType.extension}/100/100`, 705 src: `https://picsum.photos/seed/favorite-${id}-${fileType.extension}/100/100`,
706 + downloadUrl: testFileUrl, // 添加下载地址
707 + extension: fileType.extension, // 添加文件扩展名
639 created_time: formatDate(createDate) 708 created_time: formatDate(createDate)
640 } 709 }
641 } 710 }
......