| | |
| | | <view v-for="(file, index) in getCurrentFiles()" |
| | | :key="index" |
| | | class="file-item"> |
| | | <view class="file-preview-container"> |
| | | <view class="file-preview-container" |
| | | @click="previewUploadedMedia(file)"> |
| | | <image v-if="isImageFile(file)" |
| | | :src="getFileAccessUrl(file)" |
| | | class="file-preview" |
| | |
| | | </view> |
| | | <view class="video-modal-body"> |
| | | <video v-if="currentVideoFile" |
| | | :src="getFileAccessUrl(currentVideoFile)" |
| | | :src="currentVideoFile._playUrl" |
| | | class="video-player" |
| | | controls |
| | | autoplay |
| | |
| | | return { |
| | | ...file, |
| | | // 用于三标签页分组:0=生产前 1=生产中 2=生产后 |
| | | type: viewType, |
| | | viewType, |
| | | name: file?.name || file?.originalFilename || file?.bucketFilename, |
| | | bucketFilename: file?.bucketFilename || file?.name, |
| | | originalFilename: file?.originalFilename || file?.name, |
| | |
| | | |
| | | // 根据type获取对应分类的附件 |
| | | const getAttachmentsByType = typeValue => { |
| | | return attachmentList.value.filter(file => file.type === typeValue) || []; |
| | | return attachmentList.value.filter(file => file.viewType === typeValue) || []; |
| | | }; |
| | | // 获取type值 |
| | | const getTabType = () => { |
| | |
| | | |
| | | const normalizeFileUrl = (rawUrl = "") => { |
| | | let fileUrl = rawUrl || ""; |
| | | if (typeof fileUrl === "string") { |
| | | fileUrl = fileUrl.trim().replace(/^['"]|['"]$/g, ""); |
| | | } |
| | | const javaApi = filePreviewBase; |
| | | const localPrefixes = ["wxfile://", "file://", "content://", "blob:", "data:"]; |
| | | |
| | |
| | | if (fileUrl && fileUrl.indexOf("\\") > -1) { |
| | | const lowerPath = fileUrl.toLowerCase(); |
| | | const uploadPathIndex = lowerPath.indexOf("uploadpath"); |
| | | const prodIndex = lowerPath.indexOf("\\prod\\"); |
| | | |
| | | if (uploadPathIndex > -1) { |
| | | fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/"); |
| | | } else if (prodIndex > -1) { |
| | | fileUrl = fileUrl |
| | | .substring(prodIndex + "\\prod\\".length) |
| | | .replace(/\\/g, "/"); |
| | | } else { |
| | | fileUrl = fileUrl.replace(/\\/g, "/"); |
| | | } |
| | | } |
| | | fileUrl = fileUrl.replace(/^\/?uploadPath/, "/profile"); |
| | | // /javaWork/.../file/prod/xxx -> /profile/prod/xxx |
| | | const normalizedLower = String(fileUrl).toLowerCase(); |
| | | const fileProdIdx = normalizedLower.indexOf("/file/prod/"); |
| | | if (fileProdIdx > -1) { |
| | | fileUrl = `/profile/prod/${fileUrl.substring(fileProdIdx + "/file/prod/".length)}`; |
| | | } |
| | | // /javaWork/.../file/temp/xxx -> /profile/temp/xxx |
| | | const fileTempIdx = normalizedLower.indexOf("/file/temp/"); |
| | | if (fileTempIdx > -1) { |
| | | fileUrl = `/profile/temp/${fileUrl.substring(fileTempIdx + "/file/temp/".length)}`; |
| | | } |
| | | |
| | | if (/^\/?uploadPath/i.test(fileUrl)) { |
| | | fileUrl = fileUrl.replace(/^\/?uploadPath/i, "/profile"); |
| | | } |
| | | |
| | | if (fileUrl && !fileUrl.startsWith("http")) { |
| | | if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl; |
| | |
| | | if (String(file.link).startsWith("http")) return file.link; |
| | | return normalizeFileUrl(file.link); |
| | | } |
| | | return normalizeFileUrl(file?.url || file?.downloadUrl || ""); |
| | | const remoteUrl = normalizeFileUrl( |
| | | file?.url || |
| | | file?.downloadUrl || |
| | | file?.tempPath || |
| | | "" |
| | | ); |
| | | if (remoteUrl) return remoteUrl; |
| | | // 兜底本地路径(上传当次预览) |
| | | if (file?._localPreviewUrl) return file._localPreviewUrl; |
| | | return normalizeFileUrl(file?.tempFilePath || file?.path || ""); |
| | | }; |
| | | |
| | | // 上传弹窗内的媒体预览(图片/视频) |
| | | const previewUploadedMedia = file => { |
| | | if (!file) return; |
| | | if (isImageFile(file)) { |
| | | const imageUrls = getCurrentFiles() |
| | | .filter(f => isImageFile(f)) |
| | | .map(f => getFileAccessUrl(f)) |
| | | .filter(Boolean); |
| | | const current = getFileAccessUrl(file); |
| | | if (!imageUrls.length || !current) return; |
| | | uni.previewImage({ |
| | | urls: imageUrls, |
| | | current, |
| | | }); |
| | | return; |
| | | } |
| | | if (isVideoFile(file)) { |
| | | playVideoFile(file); |
| | | } |
| | | }; |
| | | |
| | | // 预览附件 |
| | |
| | | current: getFileAccessUrl(file), |
| | | }); |
| | | } else { |
| | | // 预览视频 - 显示视频播放弹窗 |
| | | showVideoPreview(file); |
| | | // 预览视频 |
| | | playVideoFile(file); |
| | | } |
| | | }; |
| | | |
| | | const safeEncodeUrl = url => { |
| | | if (!url) return ""; |
| | | if (!/^https?:\/\//i.test(url)) return url; |
| | | try { |
| | | return encodeURI(url); |
| | | } catch (e) { |
| | | return url; |
| | | } |
| | | }; |
| | | |
| | | const getPlayableVideoUrl = file => { |
| | | return safeEncodeUrl(getFileAccessUrl(file)); |
| | | }; |
| | | |
| | | const getFallbackLocalVideoUrl = file => { |
| | | const local = file?._localPreviewUrl || file?.tempFilePath || file?.path || ""; |
| | | return safeEncodeUrl(local); |
| | | }; |
| | | |
| | | const playVideoFile = file => { |
| | | const remoteSrc = getPlayableVideoUrl(file); |
| | | const localSrc = getFallbackLocalVideoUrl(file); |
| | | // 弹窗播放器优先远程地址,失败再自动回退本地地址 |
| | | const src = remoteSrc || localSrc; |
| | | if (!src) { |
| | | uni.showToast({ |
| | | title: "视频地址无效", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 统一使用页面内 video 弹窗播放 |
| | | showVideoPreview({ |
| | | ...file, |
| | | _playUrl: src, |
| | | _playUrlLocal: localSrc, |
| | | _playUrlRemote: remoteSrc, |
| | | }); |
| | | }; |
| | | |
| | | // 显示视频预览 |
| | |
| | | }; |
| | | |
| | | // 视频播放错误处理 |
| | | const handleVideoError = error => { |
| | | const handleVideoError = () => { |
| | | const localUrl = currentVideoFile.value?._playUrlLocal; |
| | | const remoteUrl = currentVideoFile.value?._playUrlRemote; |
| | | const currentUrl = currentVideoFile.value?._playUrl; |
| | | if (remoteUrl && currentUrl !== remoteUrl) { |
| | | currentVideoFile.value = { |
| | | ...currentVideoFile.value, |
| | | _playUrl: remoteUrl, |
| | | }; |
| | | uni.showToast({ |
| | | title: "已切换远程视频重试播放", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | if (localUrl && currentUrl !== localUrl) { |
| | | currentVideoFile.value = { |
| | | ...currentVideoFile.value, |
| | | _playUrl: localUrl, |
| | | }; |
| | | uni.showToast({ |
| | | title: "已切换本地视频重试播放", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | uni.showToast({ |
| | | title: "视频播放失败", |
| | | icon: "error", |
| | |
| | | url: |
| | | uploadedFile.url || |
| | | uploadedFile.downloadUrl || |
| | | uploadedFile.tempPath || |
| | | file.tempFilePath || |
| | | file.path, |
| | | tempPath: uploadedFile.tempPath || file.tempPath || "", |
| | | _localPreviewUrl: file.tempFilePath || file.path || "", |
| | | bucketFilename: |
| | | uploadedFile.bucketFilename || uploadedFile.originalFilename || file.name, |
| | | uploadedFile.bucketFilename || |
| | | uploadedFile.originalFilename || |
| | | uploadedFile.originalName || |
| | | file.name, |
| | | downloadUrl: uploadedFile.downloadUrl || uploadedFile.url, |
| | | size: uploadedFile.size || uploadedFile.byteSize || file.size, |
| | | createTime: uploadedFile.createTime || new Date().getTime(), |
| | |
| | | |
| | | .video-player { |
| | | width: 100%; |
| | | height: auto; |
| | | height: 56vh; |
| | | min-height: 260px; |
| | | max-height: 60vh; |
| | | display: block; |
| | | object-fit: contain; |
| | | } |
| | | </style> |