feat(checkin): 支持多类型文件混合上传与显示
重构文件上传逻辑,支持在同一打卡中上传并显示多种类型的文件(图片、视频、音频)。移除按类型切换时清空文件列表的限制,新增文件类型计数显示。更新多个视图中的数据处理函数以支持按文件类型分类。
Showing
5 changed files
with
185 additions
and
83 deletions
| ... | @@ -98,12 +98,12 @@ export function useCheckin() { | ... | @@ -98,12 +98,12 @@ export function useCheckin() { |
| 98 | * 用于记忆不同类型的文件列表,切换类型时恢复 | 98 | * 用于记忆不同类型的文件列表,切换类型时恢复 |
| 99 | * @type {import('vue').Ref<Object>} | 99 | * @type {import('vue').Ref<Object>} |
| 100 | */ | 100 | */ |
| 101 | - const fileListMemory = ref({ | 101 | + // const fileListMemory = ref({ |
| 102 | - text: [], | 102 | + // text: [], |
| 103 | - image: [], | 103 | + // image: [], |
| 104 | - video: [], | 104 | + // video: [], |
| 105 | - audio: [] | 105 | + // audio: [] |
| 106 | - }) | 106 | + // }) |
| 107 | 107 | ||
| 108 | /** | 108 | /** |
| 109 | * 是否可以提交 | 109 | * 是否可以提交 |
| ... | @@ -205,7 +205,17 @@ export function useCheckin() { | ... | @@ -205,7 +205,17 @@ export function useCheckin() { |
| 205 | const suffix = /.[^.]+$/.exec(file.file.name) || '' | 205 | const suffix = /.[^.]+$/.exec(file.file.name) || '' |
| 206 | let fileName = '' | 206 | let fileName = '' |
| 207 | 207 | ||
| 208 | - if (activeType.value === 'image') { | 208 | + // 根据当前 activeType 或文件类型判断 |
| 209 | + let currentFileType = activeType.value | ||
| 210 | + if (!['image', 'video', 'audio'].includes(currentFileType)) { | ||
| 211 | + // 如果 activeType 不是这三种(比如 text),尝试从文件类型推断 | ||
| 212 | + if (file.file.type.startsWith('image/')) currentFileType = 'image' | ||
| 213 | + else if (file.file.type.startsWith('video/')) currentFileType = 'video' | ||
| 214 | + else if (file.file.type.startsWith('audio/')) currentFileType = 'audio' | ||
| 215 | + else currentFileType = 'file' // 默认 | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + if (currentFileType === 'image') { | ||
| 209 | fileName = `mlaj/upload/checkin/${currentUser.value.mobile}/img/${md5}${suffix}` | 219 | fileName = `mlaj/upload/checkin/${currentUser.value.mobile}/img/${md5}${suffix}` |
| 210 | } else { | 220 | } else { |
| 211 | fileName = `mlaj/upload/checkin/${currentUser.value.mobile}/file/${md5}${suffix}` | 221 | fileName = `mlaj/upload/checkin/${currentUser.value.mobile}/file/${md5}${suffix}` |
| ... | @@ -226,13 +236,13 @@ export function useCheckin() { | ... | @@ -226,13 +236,13 @@ export function useCheckin() { |
| 226 | } | 236 | } |
| 227 | 237 | ||
| 228 | // 图片类型需要保存尺寸信息 | 238 | // 图片类型需要保存尺寸信息 |
| 229 | - if (activeType.value === 'image' && uploadResult.image_info) { | 239 | + if (currentFileType === 'image' && uploadResult.image_info) { |
| 230 | saveData.height = uploadResult.image_info.height | 240 | saveData.height = uploadResult.image_info.height |
| 231 | saveData.width = uploadResult.image_info.width | 241 | saveData.width = uploadResult.image_info.width |
| 232 | } | 242 | } |
| 233 | 243 | ||
| 234 | const { data } = await saveFileAPI(saveData) | 244 | const { data } = await saveFileAPI(saveData) |
| 235 | - return data | 245 | + return { ...data, file_type: currentFileType } |
| 236 | } | 246 | } |
| 237 | } | 247 | } |
| 238 | return null | 248 | return null |
| ... | @@ -323,6 +333,7 @@ export function useCheckin() { | ... | @@ -323,6 +333,7 @@ export function useCheckin() { |
| 323 | item.url = result.url | 333 | item.url = result.url |
| 324 | item.meta_id = result.meta_id | 334 | item.meta_id = result.meta_id |
| 325 | item.name = result.name || item.file.name | 335 | item.name = result.name || item.file.name |
| 336 | + item.file_type = result.file_type || activeType.value // 记录文件类型 | ||
| 326 | } else { | 337 | } else { |
| 327 | item.status = 'failed' | 338 | item.status = 'failed' |
| 328 | item.message = '上传失败' | 339 | item.message = '上传失败' |
| ... | @@ -438,20 +449,40 @@ export function useCheckin() { | ... | @@ -438,20 +449,40 @@ export function useCheckin() { |
| 438 | 449 | ||
| 439 | try { | 450 | try { |
| 440 | // 准备提交数据 | 451 | // 准备提交数据 |
| 452 | + // 构造 files 数组 | ||
| 453 | + const files = fileList.value | ||
| 454 | + .filter(item => item.status === 'done' && item.meta_id) | ||
| 455 | + .map(item => ({ | ||
| 456 | + meta_id: item.meta_id, | ||
| 457 | + file_type: item.file_type || activeType.value // 优先使用 item 自己的 type | ||
| 458 | + })) | ||
| 459 | + | ||
| 441 | const submitData = { | 460 | const submitData = { |
| 442 | note: message.value, | 461 | note: message.value, |
| 443 | - file_type: activeType.value, | 462 | + // file_type: activeType.value, // 不再依赖顶层 file_type |
| 444 | - meta_id: [], | 463 | + // meta_id: [], // 废弃 |
| 464 | + files: files, | ||
| 445 | makeup_time: isMakeup.value ? route.query.date : '', | 465 | makeup_time: isMakeup.value ? route.query.date : '', |
| 446 | ...extraData | 466 | ...extraData |
| 447 | } | 467 | } |
| 448 | 468 | ||
| 449 | - // 如果有文件,添加文件ID | 469 | + // 如果没有 files,尝试推断 file_type (仅为了兼容旧逻辑或文本打卡) |
| 450 | - if (fileList.value.length > 0) { | 470 | + if (files.length === 0 && activeType.value === 'text') { |
| 451 | - submitData.meta_id = fileList.value | 471 | + submitData.file_type = 'text' |
| 452 | - .filter(item => item.status === 'done' && item.meta_id) | 472 | + } else if (files.length > 0) { |
| 453 | - .map(item => item.meta_id) | 473 | + // 如果有文件,file_type 可能是 'mixed' 或者取第一个文件的类型作为主类型 |
| 474 | + // 这里暂时保留 file_type 字段以防后端必须校验,取第一个文件的类型,或者传 'mixed' | ||
| 475 | + // 如果后端已更新为只看 files 数组,则此字段可能无效 | ||
| 476 | + const types = new Set(files.map(f => f.file_type)) | ||
| 477 | + if (types.size > 1) { | ||
| 478 | + submitData.file_type = 'mixed' // 假设后端支持 mixed | ||
| 479 | + } else { | ||
| 480 | + submitData.file_type = files[0].file_type | ||
| 454 | } | 481 | } |
| 482 | + } | ||
| 483 | + | ||
| 484 | + // 如果有文件,添加文件ID (为了兼容可能还在用 meta_id 的旧接口逻辑,如果确定废弃可删除) | ||
| 485 | + // submitData.meta_id = files.map(f => f.meta_id) | ||
| 455 | 486 | ||
| 456 | let result | 487 | let result |
| 457 | if (route.query.status === 'edit') { | 488 | if (route.query.status === 'edit') { |
| ... | @@ -460,7 +491,8 @@ export function useCheckin() { | ... | @@ -460,7 +491,8 @@ export function useCheckin() { |
| 460 | i: route.query.post_id, | 491 | i: route.query.post_id, |
| 461 | subtask_id: submitData.subtask_id || route.query.subtask_id, | 492 | subtask_id: submitData.subtask_id || route.query.subtask_id, |
| 462 | note: submitData.note, | 493 | note: submitData.note, |
| 463 | - meta_id: submitData.meta_id, | 494 | + // meta_id: submitData.meta_id, |
| 495 | + files: submitData.files, | ||
| 464 | file_type: submitData.file_type, | 496 | file_type: submitData.file_type, |
| 465 | } | 497 | } |
| 466 | 498 | ||
| ... | @@ -503,19 +535,10 @@ export function useCheckin() { | ... | @@ -503,19 +535,10 @@ export function useCheckin() { |
| 503 | /** | 535 | /** |
| 504 | * 切换打卡类型 | 536 | * 切换打卡类型 |
| 505 | * @param {string} type - 打卡类型 | 537 | * @param {string} type - 打卡类型 |
| 506 | - * @description 切换时会保存当前类型的文件列表,并恢复新类型的文件列表 | 538 | + * @description 切换类型不再清空文件列表,支持多类型混合上传 |
| 507 | */ | 539 | */ |
| 508 | const switchType = (type) => { | 540 | const switchType = (type) => { |
| 509 | - if (activeType.value !== type) { | ||
| 510 | - // 保存当前类型的文件列表到记忆中 | ||
| 511 | - fileListMemory.value[activeType.value] = [...fileList.value] | ||
| 512 | - | ||
| 513 | - // 切换到新类型 | ||
| 514 | activeType.value = type | 541 | activeType.value = type |
| 515 | - | ||
| 516 | - // 恢复新类型的文件列表 | ||
| 517 | - fileList.value = [...fileListMemory.value[type]] | ||
| 518 | - } | ||
| 519 | } | 542 | } |
| 520 | 543 | ||
| 521 | /** | 544 | /** |
| ... | @@ -528,12 +551,12 @@ export function useCheckin() { | ... | @@ -528,12 +551,12 @@ export function useCheckin() { |
| 528 | uploading.value = false | 551 | uploading.value = false |
| 529 | loading.value = false | 552 | loading.value = false |
| 530 | // 清空文件列表记忆 | 553 | // 清空文件列表记忆 |
| 531 | - fileListMemory.value = { | 554 | + // fileListMemory.value = { |
| 532 | - text: [], | 555 | + // text: [], |
| 533 | - image: [], | 556 | + // image: [], |
| 534 | - video: [], | 557 | + // video: [], |
| 535 | - audio: [] | 558 | + // audio: [] |
| 536 | - } | 559 | + // } |
| 537 | } | 560 | } |
| 538 | 561 | ||
| 539 | // 计数打卡相关数据 | 562 | // 计数打卡相关数据 |
| ... | @@ -608,11 +631,12 @@ export function useCheckin() { | ... | @@ -608,11 +631,12 @@ export function useCheckin() { |
| 608 | status: 'done', | 631 | status: 'done', |
| 609 | message: '已上传', | 632 | message: '已上传', |
| 610 | meta_id: item.meta_id, | 633 | meta_id: item.meta_id, |
| 611 | - name: item.name || '' | 634 | + name: item.name || '', |
| 635 | + file_type: item.file_type || data.file_type || 'image' // 恢复 file_type | ||
| 612 | } | 636 | } |
| 613 | 637 | ||
| 614 | // 对于图片类型,添加isImage标记确保正确显示 | 638 | // 对于图片类型,添加isImage标记确保正确显示 |
| 615 | - if (activeType.value === 'image') { | 639 | + if (fileItem.file_type === 'image') { |
| 616 | fileItem.isImage = true | 640 | fileItem.isImage = true |
| 617 | } | 641 | } |
| 618 | 642 | ||
| ... | @@ -626,7 +650,7 @@ export function useCheckin() { | ... | @@ -626,7 +650,7 @@ export function useCheckin() { |
| 626 | 650 | ||
| 627 | // 将文件列表保存到当前类型的记忆中 | 651 | // 将文件列表保存到当前类型的记忆中 |
| 628 | fileList.value = files | 652 | fileList.value = files |
| 629 | - fileListMemory.value[activeType.value] = [...files] | 653 | + // fileListMemory.value[activeType.value] = [...files] |
| 630 | } | 654 | } |
| 631 | } | 655 | } |
| 632 | } catch (error) { | 656 | } catch (error) { | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-09-30 17:05 | 2 | * @Date: 2025-09-30 17:05 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-22 22:11:29 | 4 | + * @LastEditTime: 2026-01-23 16:59:38 |
| 5 | * @FilePath: /mlaj/src/views/checkin/CheckinDetailPage.vue | 5 | * @FilePath: /mlaj/src/views/checkin/CheckinDetailPage.vue |
| 6 | * @Description: 用户打卡详情页 | 6 | * @Description: 用户打卡详情页 |
| 7 | --> | 7 | --> |
| ... | @@ -84,18 +84,21 @@ | ... | @@ -84,18 +84,21 @@ |
| 84 | <div class="tab-title">{{ taskType === 'count' ? '附件类型(可选)' : '附件类型' }}</div> | 84 | <div class="tab-title">{{ taskType === 'count' ? '附件类型(可选)' : '附件类型' }}</div> |
| 85 | <div class="tabs-nav"> | 85 | <div class="tabs-nav"> |
| 86 | <div v-for="option in attachmentTypeOptions" :key="option.key" | 86 | <div v-for="option in attachmentTypeOptions" :key="option.key" |
| 87 | - @click="switchType(option.key)" :class="['tab-item', { | 87 | + @click="switchType(option.key)" :class="['tab-item', 'relative', { |
| 88 | active: activeType === option.key | 88 | active: activeType === option.key |
| 89 | }]"> | 89 | }]"> |
| 90 | <van-icon :name="getIconName(option.key)" size="1.2rem" /> | 90 | <van-icon :name="getIconName(option.key)" size="1.2rem" /> |
| 91 | <span class="tab-text">{{ option.value }}</span> | 91 | <span class="tab-text">{{ option.value }}</span> |
| 92 | + <div v-if="getTypeCount(option.key) > 0" class="absolute top-1 right-1 bg-red-500 text-white text-[10px] rounded-full min-w-[16px] h-[16px] flex items-center justify-center px-1"> | ||
| 93 | + {{ getTypeCount(option.key) }} | ||
| 94 | + </div> | ||
| 92 | </div> | 95 | </div> |
| 93 | </div> | 96 | </div> |
| 94 | </div> | 97 | </div> |
| 95 | 98 | ||
| 96 | <!-- 文件上传区域 --> | 99 | <!-- 文件上传区域 --> |
| 97 | <div v-if="activeType !== '' && activeType !== 'text'" class="upload-area"> | 100 | <div v-if="activeType !== '' && activeType !== 'text'" class="upload-area"> |
| 98 | - <van-uploader v-model="fileList" :max-count="maxCount" :max-size="maxFileSizeBytes" | 101 | + <van-uploader v-model="displayFileList" :max-count="maxCount" :max-size="maxFileSizeBytes" |
| 99 | :before-read="beforeRead" :after-read="afterRead" @delete="onDelete" | 102 | :before-read="beforeRead" :after-read="afterRead" @delete="onDelete" |
| 100 | @click-preview="onClickPreview" multiple :accept="getAcceptType()" result-type="file" | 103 | @click-preview="onClickPreview" multiple :accept="getAcceptType()" result-type="file" |
| 101 | :deletable="true" upload-icon="plus" /> | 104 | :deletable="true" upload-icon="plus" /> |
| ... | @@ -221,6 +224,67 @@ const { | ... | @@ -221,6 +224,67 @@ const { |
| 221 | gratitudeFormList | 224 | gratitudeFormList |
| 222 | } = useCheckin() | 225 | } = useCheckin() |
| 223 | 226 | ||
| 227 | +/** | ||
| 228 | + * 获取指定类型的文件数量 | ||
| 229 | + * @param {string} type - 文件类型 | ||
| 230 | + * @returns {number} 文件数量 | ||
| 231 | + */ | ||
| 232 | +const getTypeCount = (type) => { | ||
| 233 | + return fileList.value.filter(item => { | ||
| 234 | + if (item.file_type) { | ||
| 235 | + return item.file_type === type | ||
| 236 | + } | ||
| 237 | + // 处理刚选择但未上传完成的文件(尝试推断类型) | ||
| 238 | + if (item.file && item.file.type) { | ||
| 239 | + if (type === 'image') return item.file.type.startsWith('image/') | ||
| 240 | + if (type === 'video') return item.file.type.startsWith('video/') | ||
| 241 | + if (type === 'audio') return item.file.type.startsWith('audio/') | ||
| 242 | + } | ||
| 243 | + return false | ||
| 244 | + }).length | ||
| 245 | +} | ||
| 246 | + | ||
| 247 | +/** | ||
| 248 | + * 当前显示的(经过类型过滤的)文件列表 | ||
| 249 | + * @description | ||
| 250 | + * 1. getter: 根据 activeType 过滤 fileList,只显示当前类型的文件 | ||
| 251 | + * 2. setter: 处理 van-uploader 的更新(添加/删除),同步回 fileList | ||
| 252 | + */ | ||
| 253 | +const displayFileList = computed({ | ||
| 254 | + get: () => { | ||
| 255 | + return fileList.value.filter(item => { | ||
| 256 | + if (item.file_type) { | ||
| 257 | + return item.file_type === activeType.value | ||
| 258 | + } | ||
| 259 | + if (item.file && item.file.type) { | ||
| 260 | + if (activeType.value === 'image') return item.file.type.startsWith('image/') | ||
| 261 | + if (activeType.value === 'video') return item.file.type.startsWith('video/') | ||
| 262 | + if (activeType.value === 'audio') return item.file.type.startsWith('audio/') | ||
| 263 | + } | ||
| 264 | + return false | ||
| 265 | + }) | ||
| 266 | + }, | ||
| 267 | + set: (val) => { | ||
| 268 | + // 找出不属于当前视图的其他文件(保留它们) | ||
| 269 | + const otherFiles = fileList.value.filter(item => { | ||
| 270 | + if (item.file_type) { | ||
| 271 | + return item.file_type !== activeType.value | ||
| 272 | + } | ||
| 273 | + if (item.file && item.file.type) { | ||
| 274 | + if (activeType.value === 'image') return !item.file.type.startsWith('image/') | ||
| 275 | + if (activeType.value === 'video') return !item.file.type.startsWith('video/') | ||
| 276 | + if (activeType.value === 'audio') return !item.file.type.startsWith('audio/') | ||
| 277 | + } | ||
| 278 | + // 如果无法判断类型,且 activeType 不是 text,保守起见保留它? | ||
| 279 | + // 或者:如果 activeType 是 image,那么所有 image/ 相关的都算当前视图,非 image/ 的算 other | ||
| 280 | + return true | ||
| 281 | + }) | ||
| 282 | + | ||
| 283 | + // 合并其他文件和当前视图的新文件列表 | ||
| 284 | + fileList.value = [...otherFiles, ...val] | ||
| 285 | + } | ||
| 286 | +}) | ||
| 287 | + | ||
| 224 | // 动态字段文字 | 288 | // 动态字段文字 |
| 225 | const dynamicFieldText = ref('感恩') | 289 | const dynamicFieldText = ref('感恩') |
| 226 | 290 | ||
| ... | @@ -521,7 +585,7 @@ const isSubmitDisabled = computed(() => { | ... | @@ -521,7 +585,7 @@ const isSubmitDisabled = computed(() => { |
| 521 | // 文本打卡:必须填写内容且长度不少于10个字符 | 585 | // 文本打卡:必须填写内容且长度不少于10个字符 |
| 522 | return !message.value.trim() || message.value.trim().length < 10 | 586 | return !message.value.trim() || message.value.trim().length < 10 |
| 523 | } else { | 587 | } else { |
| 524 | - // 其他类型:必须有文件 | 588 | + // 其他类型:必须有文件 (如果是混合模式,只要有文件就行) |
| 525 | return fileList.value.length === 0 | 589 | return fileList.value.length === 0 |
| 526 | } | 590 | } |
| 527 | }) | 591 | }) |
| ... | @@ -745,18 +809,20 @@ const onClickPreview = (file, detail) => { | ... | @@ -745,18 +809,20 @@ const onClickPreview = (file, detail) => { |
| 745 | } | 809 | } |
| 746 | 810 | ||
| 747 | // 根据打卡类型或文件扩展名判断文件类型 | 811 | // 根据打卡类型或文件扩展名判断文件类型 |
| 748 | - if (activeType.value === 'audio' || isAudioFile(fileName)) { | 812 | + const finalFileType = file.file_type || (isAudioFile(fileName) ? 'audio' : (isVideoFile(fileName) ? 'video' : 'image')) |
| 813 | + | ||
| 814 | + if (finalFileType === 'audio') { | ||
| 749 | console.log('准备播放音频:', fileName, fileUrl) | 815 | console.log('准备播放音频:', fileName, fileUrl) |
| 750 | showAudio(fileName, fileUrl) | 816 | showAudio(fileName, fileUrl) |
| 751 | - } else if (activeType.value === 'video' || isVideoFile(fileName)) { | 817 | + } else if (finalFileType === 'video') { |
| 752 | console.log('准备播放视频:', fileName, fileUrl) | 818 | console.log('准备播放视频:', fileName, fileUrl) |
| 753 | showVideo(fileName, fileUrl) | 819 | showVideo(fileName, fileUrl) |
| 754 | - } else if (activeType.value === 'image' || isImageFile(fileName)) { | 820 | + } else if (finalFileType === 'image') { |
| 755 | console.log('图片预览由van-uploader组件处理,跳过文件列表点击预览') | 821 | console.log('图片预览由van-uploader组件处理,跳过文件列表点击预览') |
| 756 | // 图片预览由van-uploader的@click-preview事件处理,避免重复弹出 | 822 | // 图片预览由van-uploader的@click-preview事件处理,避免重复弹出 |
| 757 | return | 823 | return |
| 758 | } else { | 824 | } else { |
| 759 | - console.log('该文件类型不支持预览,文件名:', fileName, '类型:', activeType.value) | 825 | + console.log('该文件类型不支持预览,文件名:', fileName, '类型:', finalFileType) |
| 760 | showToast('该文件类型不支持预览') | 826 | showToast('该文件类型不支持预览') |
| 761 | } | 827 | } |
| 762 | } | 828 | } | ... | ... |
| ... | @@ -716,29 +716,33 @@ const formatData = (data) => { | ... | @@ -716,29 +716,33 @@ const formatData = (data) => { |
| 716 | let images = []; | 716 | let images = []; |
| 717 | let audio = []; | 717 | let audio = []; |
| 718 | let videoList = []; | 718 | let videoList = []; |
| 719 | - if (item.file_type === 'image') { | 719 | + |
| 720 | - images = item.files.map(file => { | 720 | + // 支持多类型混合显示:遍历 files 数组根据 file_type 分类 |
| 721 | - return file.value; | 721 | + if (item.files && Array.isArray(item.files)) { |
| 722 | - }); | 722 | + item.files.forEach(file => { |
| 723 | - } else if (item.file_type === 'video') { | 723 | + // 优先使用文件自身的 file_type,如果没有则回退到 item.file_type |
| 724 | - videoList = item.files.map(file => { | 724 | + const type = file.file_type || item.file_type; |
| 725 | - return { | 725 | + |
| 726 | + if (type === 'image') { | ||
| 727 | + images.push(file.value); | ||
| 728 | + } else if (type === 'video') { | ||
| 729 | + videoList.push({ | ||
| 726 | id: file.meta_id, | 730 | id: file.meta_id, |
| 727 | video: file.value, | 731 | video: file.value, |
| 728 | videoCover: file.cover, | 732 | videoCover: file.cover, |
| 729 | isPlaying: false, | 733 | isPlaying: false, |
| 730 | - } | 734 | + }); |
| 731 | - }) | 735 | + } else if (type === 'audio') { |
| 732 | - } else if (item.file_type === 'audio') { | 736 | + audio.push({ |
| 733 | - audio = item.files.map(file => { | ||
| 734 | - return { | ||
| 735 | title: file.name ? file.name : '打卡音频', | 737 | title: file.name ? file.name : '打卡音频', |
| 736 | artist: file.artist ? file.artist : '', | 738 | artist: file.artist ? file.artist : '', |
| 737 | url: file.value, | 739 | url: file.value, |
| 738 | cover: file.cover ? file.cover : '', | 740 | cover: file.cover ? file.cover : '', |
| 741 | + }); | ||
| 739 | } | 742 | } |
| 740 | - }) | 743 | + }); |
| 741 | } | 744 | } |
| 745 | + | ||
| 742 | return { | 746 | return { |
| 743 | id: item.id, | 747 | id: item.id, |
| 744 | task_id: item.task_id, | 748 | task_id: item.task_id, | ... | ... |
| ... | @@ -702,29 +702,33 @@ const formatData = (data) => { | ... | @@ -702,29 +702,33 @@ const formatData = (data) => { |
| 702 | let images = []; | 702 | let images = []; |
| 703 | let audio = []; | 703 | let audio = []; |
| 704 | let videoList = []; | 704 | let videoList = []; |
| 705 | - if (item.file_type === 'image') { | 705 | + |
| 706 | - images = item.files.map(file => { | 706 | + // 支持多类型混合显示:遍历 files 数组根据 file_type 分类 |
| 707 | - return file.value; | 707 | + if (item.files && Array.isArray(item.files)) { |
| 708 | - }); | 708 | + item.files.forEach(file => { |
| 709 | - } else if (item.file_type === 'video') { | 709 | + // 优先使用文件自身的 file_type,如果没有则回退到 item.file_type |
| 710 | - videoList = item.files.map(file => { | 710 | + const type = file.file_type || item.file_type; |
| 711 | - return { | 711 | + |
| 712 | + if (type === 'image') { | ||
| 713 | + images.push(file.value); | ||
| 714 | + } else if (type === 'video') { | ||
| 715 | + videoList.push({ | ||
| 712 | id: file.meta_id, | 716 | id: file.meta_id, |
| 713 | video: file.value, | 717 | video: file.value, |
| 714 | videoCover: file.cover, | 718 | videoCover: file.cover, |
| 715 | isPlaying: false, | 719 | isPlaying: false, |
| 716 | - } | 720 | + }); |
| 717 | - }) | 721 | + } else if (type === 'audio') { |
| 718 | - } else if (item.file_type === 'audio') { | 722 | + audio.push({ |
| 719 | - audio = item.files.map(file => { | ||
| 720 | - return { | ||
| 721 | title: file.name ? file.name : '打卡音频', | 723 | title: file.name ? file.name : '打卡音频', |
| 722 | artist: file.artist ? file.artist : '', | 724 | artist: file.artist ? file.artist : '', |
| 723 | url: file.value, | 725 | url: file.value, |
| 724 | cover: file.cover ? file.cover : '', | 726 | cover: file.cover ? file.cover : '', |
| 727 | + }); | ||
| 725 | } | 728 | } |
| 726 | - }) | 729 | + }); |
| 727 | } | 730 | } |
| 731 | + | ||
| 728 | return { | 732 | return { |
| 729 | id: item.id, | 733 | id: item.id, |
| 730 | task_id: item.task_id, | 734 | task_id: item.task_id, | ... | ... |
| ... | @@ -841,29 +841,33 @@ const formatData = (data) => { | ... | @@ -841,29 +841,33 @@ const formatData = (data) => { |
| 841 | let images = []; | 841 | let images = []; |
| 842 | let audio = []; | 842 | let audio = []; |
| 843 | let videoList = []; | 843 | let videoList = []; |
| 844 | - if (item.file_type === 'image') { | 844 | + |
| 845 | - images = item.files.map(file => { | 845 | + // 支持多类型混合显示:遍历 files 数组根据 file_type 分类 |
| 846 | - return file.value; | 846 | + if (item.files && Array.isArray(item.files)) { |
| 847 | - }); | 847 | + item.files.forEach(file => { |
| 848 | - } else if (item.file_type === 'video') { | 848 | + // 优先使用文件自身的 file_type,如果没有则回退到 item.file_type |
| 849 | - videoList = item.files.map(file => { | 849 | + const type = file.file_type || item.file_type; |
| 850 | - return { | 850 | + |
| 851 | + if (type === 'image') { | ||
| 852 | + images.push(file.value); | ||
| 853 | + } else if (type === 'video') { | ||
| 854 | + videoList.push({ | ||
| 851 | id: file.meta_id, | 855 | id: file.meta_id, |
| 852 | video: file.value, | 856 | video: file.value, |
| 853 | videoCover: file.cover, | 857 | videoCover: file.cover, |
| 854 | isPlaying: false, | 858 | isPlaying: false, |
| 855 | - } | 859 | + }); |
| 856 | - }) | 860 | + } else if (type === 'audio') { |
| 857 | - } else if (item.file_type === 'audio') { | 861 | + audio.push({ |
| 858 | - audio = item.files.map(file => { | ||
| 859 | - return { | ||
| 860 | title: file.name ? file.name : '打卡音频', | 862 | title: file.name ? file.name : '打卡音频', |
| 861 | artist: file.artist ? file.artist : '', | 863 | artist: file.artist ? file.artist : '', |
| 862 | url: file.value, | 864 | url: file.value, |
| 863 | cover: file.cover ? file.cover : '', | 865 | cover: file.cover ? file.cover : '', |
| 866 | + }); | ||
| 864 | } | 867 | } |
| 865 | - }) | 868 | + }); |
| 866 | } | 869 | } |
| 870 | + | ||
| 867 | return { | 871 | return { |
| 868 | id: item.id, | 872 | id: item.id, |
| 869 | task_id: item.task_id, | 873 | task_id: item.task_id, | ... | ... |
-
Please register or login to post a comment