hookehuyr

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

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

Co-Authored-By: Claude Sonnet 4.5
......@@ -8,6 +8,7 @@
* @date 2026-01-31
*/
import Taro from '@tarojs/taro'
import { showToast, showLoading, hideLoading, showModal, openDocument, downloadFile, previewImage } from '@tarojs/taro'
import { isVideoFile } from '@/utils/tools'
import { extractExtensionFromFile } from '@/utils/documentIcons'
......@@ -44,6 +45,7 @@ export function useFileOperation() {
* @param {Object} item - 文件信息对象
* @param {string} [item.fileName] - 文件名(用于判断文件类型)
* @param {string} [item.extension] - 文件扩展名(优先使用)
* @param {string} [item.downloadUrl] - 原始下载地址(用于失败时复制链接)
* @returns {Promise<void>}
*
* @example
......@@ -52,18 +54,30 @@ export function useFileOperation() {
* await openFile(tempFilePath, { extension: 'pdf' }) // 优先使用 extension
*/
const openFile = async (filePath, item) => {
// 记录文件打开开始的日志
console.log('[文件操作] 开始打开文件:', {
filePath,
fileName: item.fileName,
extension: item.extension,
downloadUrl: item.downloadUrl
})
try {
await openDocument({
filePath: filePath,
showMenu: true, // 显示右上角菜单,用户可以转发、保存等
success: () => {
console.log('文件打开成功')
// 记录成功回调
console.log('[文件操作] ✅ openDocument success 回调触发 - 文件已打开')
// 文件打开后,延迟提示用户如果看不到内容该如何操作
// 使用统一的扩展名提取函数
const fileExt = extractExtensionFromFile(item)
const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
if (unsupportedFormats.includes(fileExt)) {
console.log('[文件操作] ⚠️ 检测到不支持的格式:', fileExt, '- 将在 1.5s 后显示提示')
setTimeout(() => {
showToast({
title: '如无法预览,请使用右上角菜单分享',
......@@ -74,7 +88,12 @@ export function useFileOperation() {
}
},
fail: (err) => {
console.error('打开文件失败:', err)
// 记录失败回调(这个通常不会在"内容无法显示"时触发)
console.log('[文件操作] ❌ openDocument fail 回调触发:', err)
console.log('[文件操作] 错误详情:', {
errMsg: err.errMsg,
errorCode: err.errCode
})
// 获取文件扩展名(使用统一的扩展名提取函数)
const fileExt = extractExtensionFromFile(item)
......@@ -82,28 +101,61 @@ export function useFileOperation() {
// 根据文件类型给出提示
let message = '文件打开失败'
let suggestion = ''
let showCopyButton = false
if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) {
message = '暂不支持预览 Office 文档'
suggestion = '\n\n建议:\n1. 点击右上角"..."菜单\n2. 选择"发送给朋友"\n3. 在电脑或支持的应用中打开'
suggestion = '\n\n您可以复制链接,在电脑或其他应用中打开'
showCopyButton = true
} else if (['pdf'].includes(fileExt)) {
message = 'PDF 文件打开失败'
suggestion = '\n\n文件可能已损坏,请联系管理员'
} else {
message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件`
suggestion = '\n\n请在电脑或其他应用中打开'
showCopyButton = !!item.downloadUrl
}
showModal({
title: '提示',
content: message + suggestion,
showCancel: false,
confirmText: '我知道了'
confirmText: showCopyButton ? '复制链接' : '我知道了',
cancelText: showCopyButton ? '关闭' : undefined,
showCancel: showCopyButton,
success: (modalRes) => {
console.log('[文件操作] 用户选择:', modalRes.confirm ? '复制链接' : '关闭')
if (modalRes.confirm && showCopyButton && item.downloadUrl) {
// 复制下载链接到剪贴板
console.log('[文件操作] 开始复制链接:', item.downloadUrl)
Taro.setClipboardData({
data: item.downloadUrl,
success: () => {
console.log('[文件操作] ✅ 链接复制成功')
showToast({
title: '链接已复制',
icon: 'success',
duration: 2000
})
},
fail: (clipboardErr) => {
console.log('[文件操作] ❌ 链接复制失败:', clipboardErr)
showToast({
title: '复制失败',
icon: 'none',
duration: 2000
})
}
})
}
}
})
}
})
} catch (error) {
console.error('打开文件异常:', error)
// 记录异常(Promise rejection)
console.log('[文件操作] ❌ openDocument 异常捕获:', error)
showToast({
title: '打开文件失败',
icon: 'none',
......@@ -136,11 +188,21 @@ export function useFileOperation() {
*/
const downloadAndOpenFile = async (item) => {
try {
//下载文件
console.log('[文件操作] 开始下载文件:', {
downloadUrl: item.downloadUrl,
fileName: item.fileName
})
// 下载文件
const downloadResult = await downloadFile({
url: item.downloadUrl
})
console.log('[文件操作] 下载结果:', {
statusCode: downloadResult.statusCode,
tempFilePath: downloadResult.tempFilePath
})
// 检查下载结果
if (downloadResult.statusCode !== 200) {
throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
......@@ -158,22 +220,41 @@ export function useFileOperation() {
// 微信小程序对 Office 文档支持有限,提前提示用户
const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']
const dontShowAgainKey = 'office_doc_tip_dont_show_again'
// 检查用户是否已选择"不再提醒"
const dontShowAgain = Taro.getStorageSync(dontShowAgainKey)
console.log('[文件操作] "不再提醒"设置状态:', dontShowAgain)
if (unsupportedFormats.includes(fileExt) && !dontShowAgain) {
// 首次打开 Office 文档,显示提示(带"不再提醒"选项)
console.log('[文件操作] 首次打开 Office 文档,显示提示')
if (unsupportedFormats.includes(fileExt)) {
// 对于 Office 文档,先提示用户,但仍尝试打开
showModal({
title: '预览提示',
content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
confirmText: '继续',
content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续预览?`,
confirmText: '继续,不再提醒',
cancelText: '取消',
success: (modalRes) => {
console.log('[文件操作] 用户选择:', modalRes.confirm ? '继续,不再提醒' : '取消')
if (modalRes.confirm) {
// 记录用户选择,下次不再提示
try {
Taro.setStorageSync(dontShowAgainKey, true)
console.log('[文件操作] ✅ 已保存"不再提醒"设置')
} catch (storageErr) {
console.log('[文件操作] ❌ 保存设置失败:', storageErr)
}
// 打开文件
openFile(downloadResult.tempFilePath, item)
}
}
})
} else {
// 其他格式直接打开
// 用户已选择"不再提醒"或非 Office 文档,直接打开
console.log('[文件操作] 直接打开文件(已选择不再提醒 或 非 Office 文档)')
await openFile(downloadResult.tempFilePath, item)
}
} catch (error) {
......@@ -257,9 +338,6 @@ export function useFileOperation() {
// 判断是否为视频文件(优先使用 extension 字段)
if (isVideoFile(item)) {
// 视频文件:跳转到视频播放页面
// 需要动态导入 navigateTo 以避免循环依赖
const Taro = await import('@tarojs/taro')
Taro.navigateTo({
url: `/pages/video-player/index?url=${encodeURIComponent(item.downloadUrl)}&title=${encodeURIComponent(item.title || item.fileName)}`
})
......
......@@ -62,6 +62,54 @@ function mockDelay(min = 100, max = 300) {
}
// ============================================================================
// 真实的测试文件地址(可预览)
// ============================================================================
/**
* 真实的可预览测试文件地址
* 来源:GitHub 和其他公开的测试资源
*/
const TEST_FILES = {
// PDF 文档
pdf: [
'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
'https://www.africau.edu/images/default/sample.pdf',
'https://www.adobe.com/support/products/reader/pdfs/Reader9Welcome.pdf'
],
// 图片
jpg: [
'https://picsum.photos/seed/test1/800/600.jpg',
'https://picsum.photos/seed/test2/800/600.jpg',
'https://picsum.photos/seed/test3/800/600.jpg'
],
// PNG 图片
png: [
'https://picsum.photos/seed/test4/800/600.png',
'https://picsum.photos/seed/test5/800/600.png'
],
// 文本文件(使用 GitHub raw)
txt: [
'https://raw.githubusercontent.com/torvalds/linux/master/README',
'https://raw.githubusercontent.com/github/gitignore/main/README'
]
}
/**
* 根据文件类型获取测试文件地址
* @param {string} extension - 文件扩展名
* @param {number} seed - 随机种子
* @returns {string} 测试文件地址
*/
function getTestFileUrl(extension, seed = 0) {
const files = TEST_FILES[extension] || TEST_FILES.pdf
const index = seed % files.length
return files[index]
}
// ============================================================================
// 1. 周热门资料 Mock (weekHotAPI)
// ============================================================================
......@@ -108,6 +156,9 @@ function generateWeekHotItem(id) {
const fileType = FILE_TYPES[Math.floor(Math.random() * FILE_TYPES.length)]
const materialName = WEEK_HOT_MATERIALS[Math.floor(Math.random() * WEEK_HOT_MATERIALS.length)]
// 获取真实的测试文件地址
const testFileUrl = getTestFileUrl(fileType.extension, id)
return {
meta_id: id,
name: `${materialName} ${fileType.name.toUpperCase()}`,
......@@ -116,7 +167,8 @@ function generateWeekHotItem(id) {
read_people_count: generateRandomReadCount(),
read_people_percent: generateRandomReadPercent(),
is_favorite: generateRandomFavorite(),
extension: fileType.extension
extension: fileType.extension,
downloadUrl: testFileUrl // 使用真实的测试文件地址
}
}
......@@ -179,6 +231,9 @@ function generateMaterialItem(id) {
const fileType = FILE_TYPES[Math.floor(Math.random() * FILE_TYPES.length)]
const materialName = MATERIAL_NAMES[Math.floor(Math.random() * MATERIAL_NAMES.length)]
// 获取真实的测试文件地址
const testFileUrl = getTestFileUrl(fileType.extension, id)
return {
id: id,
meta_id: id,
......@@ -190,9 +245,9 @@ function generateMaterialItem(id) {
extension: fileType.extension,
collected: generateRandomFavorite() === '1',
src: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100`,
downloadUrl: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100`,
downloadUrl: testFileUrl, // 使用真实的测试文件地址
post_date: new Date().toISOString(),
value: `https://picsum.photos/seed/file-${id}-${fileType.extension}/100/100`
value: testFileUrl // 使用真实的测试文件地址
}
}
......@@ -384,6 +439,9 @@ export async function mockSearchAPI(params) {
})
const materialItem = generateMaterialItem(i + 1)
// 确保使用真实的测试文件地址
const testFileUrl = getTestFileUrl(materialItem.extension, i)
defaultFiles.push({
...materialItem,
id: i + 1,
......@@ -393,7 +451,7 @@ export async function mockSearchAPI(params) {
readPeoplePercent: materialItem.read_people_percent,
collected: materialItem.collected,
extension: materialItem.extension,
downloadUrl: materialItem.downloadUrl,
downloadUrl: testFileUrl, // 覆盖为真实的测试文件地址
title: materialItem.title,
src: materialItem.src
})
......@@ -458,6 +516,9 @@ export async function mockSearchAPI(params) {
const hasBigramMaterial = searchKeyword.length >= 2 && keywords.slice(0, -1).some((k, idx) => materialName.includes(k + keywords[idx + 1]))
if (materialName.includes(searchKeyword) || hasAnyCharMaterial || hasBigramMaterial) {
// 确保使用真实的测试文件地址
const testFileUrl = getTestFileUrl(materialItem.extension, startIndex + i + 100)
files.push({
...materialItem,
id: startIndex + i + 100,
......@@ -467,7 +528,7 @@ export async function mockSearchAPI(params) {
readPeoplePercent: materialItem.read_people_percent,
collected: materialItem.collected,
extension: materialItem.extension,
downloadUrl: materialItem.downloadUrl,
downloadUrl: testFileUrl, // 覆盖为真实的测试文件地址
title: materialItem.title,
src: materialItem.src
})
......@@ -487,6 +548,9 @@ export async function mockSearchAPI(params) {
})
const materialItem = generateMaterialItem(startIndex + i + 100)
// 确保使用真实的测试文件地址
const testFileUrl = getTestFileUrl(materialItem.extension, startIndex + i + 100)
files.push({
...materialItem,
id: startIndex + i + 100,
......@@ -496,7 +560,7 @@ export async function mockSearchAPI(params) {
readPeoplePercent: materialItem.read_people_percent,
collected: materialItem.collected,
extension: materialItem.extension,
downloadUrl: materialItem.downloadUrl,
downloadUrl: testFileUrl, // 覆盖为真实的测试文件地址
title: materialItem.title,
src: materialItem.src
})
......@@ -631,11 +695,16 @@ function generateFavoriteItem(id) {
const now = new Date()
const createDate = new Date(now.getTime() - Math.random() * 90 * 24 * 60 * 60 * 1000)
// 获取真实的测试文件地址
const testFileUrl = getTestFileUrl(fileType.extension, id)
return {
meta_id: id,
name: `${materialName}.${fileType.extension}`,
size: generateRandomSize(),
src: `https://picsum.photos/seed/favorite-${id}-${fileType.extension}/100/100`,
downloadUrl: testFileUrl, // 添加下载地址
extension: fileType.extension, // 添加文件扩展名
created_time: formatDate(createDate)
}
}
......