hookehuyr

fix(file): 修复文件扩展名解析逻辑

- 修复 JPEG 等图片文件显示为 "DOC" 标签的问题
- 优化 extractExtensionFromFile 函数的 fallback 逻辑
- 移除首页、搜索页、周热门资料页的错误扩展名提取逻辑

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 +## [2026-02-14] - 修复文件扩展名解析逻辑
2 +
3 +### 修复
4 +- 修复 JPEG 等图片文件显示为 "DOC" 标签的问题
5 +- 优化 `extractExtensionFromFile` 函数的 fallback 逻辑
6 +- 移除首页、搜索页、周热门资料页的错误扩展名提取逻辑
7 +
8 +---
9 +
10 +**详细信息**
11 +- **影响文件**: src/utils/documentIcons.js, src/pages/index/index.vue, src/pages/search/index.vue, src/pages/week-hot-material/index.vue
12 +- **技术栈**: Vue 3, Taro 4, 文件处理
13 +- **测试状态**: 待测试
14 +- **备注**:
15 + - 问题根因:`fileName.split('.').pop()` 对 "IMG_6395大" 返回文件名本身而非扩展名
16 + - 解决方案:让组件内部使用 `extractExtensionFromFile` 自动从 downloadUrl 解析扩展名
17 + - 优先级:extension 字段 > fileName > src > downloadUrl
18 + - 涉及页面:首页周热门资料、搜索页文件结果
19 +
20 +---
21 +
1 ## [2026-02-13] - 收藏页空名称处理 22 ## [2026-02-13] - 收藏页空名称处理
2 23
3 ### 修复 24 ### 修复
......
...@@ -361,19 +361,15 @@ const fetchHotMaterials = async () => { ...@@ -361,19 +361,15 @@ const fetchHotMaterials = async () => {
361 if (res.code === 1 && res.data && res.data.list) { 361 if (res.code === 1 && res.data && res.data.list) {
362 // 转换 API 数据格式为组件所需格式 362 // 转换 API 数据格式为组件所需格式
363 hotMaterials.value = res.data.list.map(item => { 363 hotMaterials.value = res.data.list.map(item => {
364 - // 提取文件扩展名
365 - const fileName = item.name || '未命名文件'
366 - const extension = item.extension || fileName.split('.').pop()?.toLowerCase() || ''
367 -
368 return { 364 return {
369 id: item.meta_id, 365 id: item.meta_id,
370 title: item.name || '未命名资料', 366 title: item.name || '未命名资料',
371 - fileName: fileName, 367 + fileName: item.name || '未命名文件',
372 downloadUrl: item.src, 368 downloadUrl: item.src,
373 fileSize: item.size, 369 fileSize: item.size,
374 - extension: extension, 370 + // 不在这里提取扩展名,让 MaterialCard 内部使用 extractExtensionFromFile 自动从 URL 解析
375 learners: `${item.read_people_count}人学习`, 371 learners: `${item.read_people_count}人学习`,
376 - readPeoplePercent: item.read_people_percent, // 学习人数比例 372 + readPeoplePercent: item.read_people_percent,
377 collected: item.is_favorite 373 collected: item.is_favorite
378 } 374 }
379 }); 375 });
......
...@@ -275,17 +275,13 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo ...@@ -275,17 +275,13 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo
275 275
276 // 映射资料列表(进行字段映射,与首页保持一致) 276 // 映射资料列表(进行字段映射,与首页保持一致)
277 const newFiles = (res.data.files.list || []).map(item => { 277 const newFiles = (res.data.files.list || []).map(item => {
278 - // 提取文件扩展名
279 - const fileName = item.name || '未命名文件'
280 - const extension = item.extension || fileName.split('.').pop()?.toLowerCase() || ''
281 -
282 return { 278 return {
283 id: item.meta_id || item.id, 279 id: item.meta_id || item.id,
284 title: item.name, 280 title: item.name,
285 - fileName: fileName, 281 + fileName: item.name || '未命名文件',
286 fileSize: item.size || item.file_size, 282 fileSize: item.size || item.file_size,
287 downloadUrl: item.src || item.value, 283 downloadUrl: item.src || item.value,
288 - extension: extension, 284 + // 不手动提取 extension,让 MaterialCard 内部使用 extractExtensionFromFile 自动从 URL 解析
289 learners: item.read_people_count ? `${item.read_people_count }人学习` : '', 285 learners: item.read_people_count ? `${item.read_people_count }人学习` : '',
290 readPeoplePercent: item.read_people_percent, 286 readPeoplePercent: item.read_people_percent,
291 is_favorite: item.is_favorite, // 保留原始字段 287 is_favorite: item.is_favorite, // 保留原始字段
......
...@@ -134,15 +134,13 @@ const fetchWeekHotList = async (params = {}, isLoadMore = false) => { ...@@ -134,15 +134,13 @@ const fetchWeekHotList = async (params = {}, isLoadMore = false) => {
134 // 处理列表数据 134 // 处理列表数据
135 if (res.data.list?.length) { 135 if (res.data.list?.length) {
136 // 直接映射为 MaterialCard 需要的格式 136 // 直接映射为 MaterialCard 需要的格式
137 - // 注意:extension 字段可以让 extractExtensionFromFile 函数自动从 URL 解析 137 + // 不手动提取 extension,让 MaterialCard 内部使用 extractExtensionFromFile 自动从 URL 解析
138 const listData = res.data.list.map(item => { 138 const listData = res.data.list.map(item => {
139 return { 139 return {
140 meta_id: item.meta_id, 140 meta_id: item.meta_id,
141 name: item.name || '未命名文件', 141 name: item.name || '未命名文件',
142 size: item.size || '', 142 size: item.size || '',
143 downloadUrl: item.src, 143 downloadUrl: item.src,
144 - // extension 可以为空,MaterialCard 会使用 extractExtensionFromFile 从 src 解析
145 - extension: item.extension || '',
146 collected: item.is_favorite === '1' || item.is_favorite === 1 || item.is_favorite === true, 144 collected: item.is_favorite === '1' || item.is_favorite === 1 || item.is_favorite === true,
147 read_people_count: item.read_people_count, 145 read_people_count: item.read_people_count,
148 read_people_percent: item.read_people_percent 146 read_people_percent: item.read_people_percent
......
...@@ -138,6 +138,7 @@ export function getDocumentIcon(fileNameOrItem) { ...@@ -138,6 +138,7 @@ export function getDocumentIcon(fileNameOrItem) {
138 * 从文件名或文件对象中提取扩展名(统一工具函数) 138 * 从文件名或文件对象中提取扩展名(统一工具函数)
139 * 139 *
140 * @description 支持传入文件名或包含 extension 字段的对象,优先使用 extension 字段。 140 * @description 支持传入文件名或包含 extension 字段的对象,优先使用 extension 字段。
141 + * 如果 extension 为空,会依次从 fileName、src、downloadUrl 解析,直到找到扩展名。
141 * 这是项目中所有文件类型判断的核心工具函数。 142 * 这是项目中所有文件类型判断的核心工具函数。
142 * @param {string|Object} fileNameOrItem - 文件名(如:document.pdf)或文件对象(包含 extension 字段) 143 * @param {string|Object} fileNameOrItem - 文件名(如:document.pdf)或文件对象(包含 extension 字段)
143 * @param {string} [fileNameOrItem.fileName] - 文件名 144 * @param {string} [fileNameOrItem.fileName] - 文件名
...@@ -152,6 +153,7 @@ export function getDocumentIcon(fileNameOrItem) { ...@@ -152,6 +153,7 @@ export function getDocumentIcon(fileNameOrItem) {
152 * extractExtensionFromFile({ fileName: 'document.DOC' }) // 'doc'(从文件名解析) 153 * extractExtensionFromFile({ fileName: 'document.DOC' }) // 'doc'(从文件名解析)
153 * extractExtensionFromFile({ extension: 'pdf', fileName: 'backup.doc' }) // 'pdf'(优先使用 extension) 154 * extractExtensionFromFile({ extension: 'pdf', fileName: 'backup.doc' }) // 'pdf'(优先使用 extension)
154 * extractExtensionFromFile({ src: 'https://example.com/file.png' }) // 'png'(从 URL 解析) 155 * extractExtensionFromFile({ src: 'https://example.com/file.png' }) // 'png'(从 URL 解析)
156 + * extractExtensionFromFile({ fileName: '无扩展名文件', src: 'https://cdn.com/file.jpeg' }) // 'jpeg'(src 补位)
155 * extractExtensionFromFile({ downloadUrl: 'https://cdn.com/file.jpg' }) // 'jpg'(从 URL 解析) 157 * extractExtensionFromFile({ downloadUrl: 'https://cdn.com/file.jpg' }) // 'jpg'(从 URL 解析)
156 */ 158 */
157 export function extractExtensionFromFile(fileNameOrItem) { 159 export function extractExtensionFromFile(fileNameOrItem) {
...@@ -167,12 +169,14 @@ export function extractExtensionFromFile(fileNameOrItem) { ...@@ -167,12 +169,14 @@ export function extractExtensionFromFile(fileNameOrItem) {
167 if (fileNameOrItem.fileName) { 169 if (fileNameOrItem.fileName) {
168 extension = extractExtensionFromString(fileNameOrItem.fileName); 170 extension = extractExtensionFromString(fileNameOrItem.fileName);
169 } 171 }
170 - // 如果 fileName 没有扩展名,尝试从 src 解析 172 +
171 - else if (fileNameOrItem.src) { 173 + // 如果 fileName 没有扩展名(extension 仍为空),继续尝试其他字段
174 + if (!extension && fileNameOrItem.src) {
172 extension = extractExtensionFromString(fileNameOrItem.src); 175 extension = extractExtensionFromString(fileNameOrItem.src);
173 } 176 }
174 - // 如果 src 也没有,尝试从 downloadUrl 解析 177 +
175 - else if (fileNameOrItem.downloadUrl) { 178 + // 如果 src 也没有扩展名,继续尝试 downloadUrl
179 + if (!extension && fileNameOrItem.downloadUrl) {
176 extension = extractExtensionFromString(fileNameOrItem.downloadUrl); 180 extension = extractExtensionFromString(fileNameOrItem.downloadUrl);
177 } 181 }
178 } 182 }
......