|
|
|
@ -55,22 +55,19 @@ |
|
|
|
<span class="required-mark">*</span>云视频 |
|
|
|
</div> |
|
|
|
<div class="form-value"> |
|
|
|
<van-uploader |
|
|
|
v-model="videoFileList" |
|
|
|
:max-count="1" |
|
|
|
:max-size="VIDEO_MAX_SIZE" |
|
|
|
accept="video/*" |
|
|
|
:after-read="onAfterReadVideo" |
|
|
|
@oversize="onVideoOversize" |
|
|
|
@delete="onVideoDelete" |
|
|
|
> |
|
|
|
<template #default> |
|
|
|
<div class="uploader-btn"> |
|
|
|
<van-icon name="plus" size="24" color="#dcdee0" /> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</van-uploader> |
|
|
|
<div class="form-tip">(仅支持视频,200MB以下)</div> |
|
|
|
<div v-if="videoUploading" class="video-upload-state"> |
|
|
|
<van-loading type="spinner" color="#2b7de9" /> |
|
|
|
<span class="upload-text">上传中...</span> |
|
|
|
</div> |
|
|
|
<div v-else-if="cloudVideo" class="video-preview-item"> |
|
|
|
<video :src="cloudVideo" muted preload="metadata" class="video-thumb" @click="showVideoPreview = true" /> |
|
|
|
<van-icon name="play-circle-o" class="play-icon" @click="showVideoPreview = true" /> |
|
|
|
<van-icon name="cross" class="delete-icon" @click="onVideoDelete" /> |
|
|
|
</div> |
|
|
|
<div v-else class="uploader-btn" @click="onRecordVideo"> |
|
|
|
<van-icon name="plus" size="24" color="#dcdee0" /> |
|
|
|
</div> |
|
|
|
<div class="form-tip">(仅支持现场录制视频,200MB以下)</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
@ -127,11 +124,9 @@ |
|
|
|
import { showConfirmDialog, showSuccessToast, showToast } from 'vant'; |
|
|
|
import noDataImg from '@/assets/images/nos/no-data.png'; |
|
|
|
import { submitCloudRecordApi, cloudRecordDetailApi } from '@/api/nos/index'; |
|
|
|
import { uploadFile } from '@/util/fileUpload'; |
|
|
|
import { startVideoRecorder, uploadLocalFileToBackend } from '@/util/w6sMedia'; |
|
|
|
import { toFullFileUrl, toRelativeFilePath } from '@/util/fileUrl'; |
|
|
|
|
|
|
|
const VIDEO_MAX_SIZE = 200 * 1024 * 1024; |
|
|
|
|
|
|
|
const router = useRouter(); |
|
|
|
const route = useRoute(); |
|
|
|
|
|
|
|
@ -147,8 +142,8 @@ const cloudVideo = ref(''); |
|
|
|
const cloudText = ref(''); |
|
|
|
const suggestion = ref(''); |
|
|
|
const cloudVideoPath = ref(''); |
|
|
|
const videoFileList = ref([]); |
|
|
|
const showVideoPreview = ref(false); |
|
|
|
const videoUploading = ref(false); |
|
|
|
|
|
|
|
const applyDetailData = (data) => { |
|
|
|
cloudVideo.value = data.cloudVideo ? toFullFileUrl(data.cloudVideo) : ''; |
|
|
|
@ -191,56 +186,40 @@ const onRefresh = () => { |
|
|
|
fetchDetail(); |
|
|
|
}; |
|
|
|
|
|
|
|
const isVideo = (url) => { |
|
|
|
if (!url) return false; |
|
|
|
return /\.(mp4|webm|ogg|mov|avi|mkv)$/i.test(url); |
|
|
|
}; |
|
|
|
|
|
|
|
const onVideoOversize = () => { |
|
|
|
showToast('视频大小不能超过 200MB'); |
|
|
|
}; |
|
|
|
|
|
|
|
const onVideoDelete = () => { |
|
|
|
cloudVideoPath.value = ''; |
|
|
|
cloudVideo.value = ''; |
|
|
|
}; |
|
|
|
|
|
|
|
const onAfterReadVideo = (file) => { |
|
|
|
const files = Array.isArray(file) ? file : [file]; |
|
|
|
files.forEach((f) => { |
|
|
|
if (f.file && !f.file.type.startsWith('video/')) { |
|
|
|
showToast('请上传视频文件'); |
|
|
|
f.status = 'failed'; |
|
|
|
f.message = '格式错误'; |
|
|
|
videoFileList.value = []; |
|
|
|
cloudVideoPath.value = ''; |
|
|
|
const onRecordVideo = async () => { |
|
|
|
if (videoUploading.value) return; |
|
|
|
try { |
|
|
|
videoUploading.value = true; |
|
|
|
const res = await startVideoRecorder(); |
|
|
|
if (!res?.video_path) { |
|
|
|
showToast('录制失败'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
uploadFile(f, (res) => { |
|
|
|
const body = res?.data?.data ?? res?.data ?? res; |
|
|
|
const filePath = |
|
|
|
typeof body === 'string' |
|
|
|
? body |
|
|
|
: (body && (body.url || body.fileUrl || body.path)) || ''; |
|
|
|
|
|
|
|
if (filePath) { |
|
|
|
if (!isVideo(filePath) && f.file && !f.file.type.startsWith('video/')) { |
|
|
|
showToast('请上传视频文件'); |
|
|
|
videoFileList.value = []; |
|
|
|
cloudVideoPath.value = ''; |
|
|
|
return; |
|
|
|
} |
|
|
|
cloudVideoPath.value = filePath; |
|
|
|
f.url = toFullFileUrl(filePath); |
|
|
|
f.isImage = false; |
|
|
|
f.status = 'done'; |
|
|
|
} else { |
|
|
|
f.status = 'failed'; |
|
|
|
f.message = '上传失败'; |
|
|
|
cloudVideoPath.value = ''; |
|
|
|
} |
|
|
|
const uploadRes = await uploadLocalFileToBackend({ |
|
|
|
fileURL: res.video_path, |
|
|
|
fileName: res.name || `video-${Date.now()}.mp4`, |
|
|
|
mimeType: res.type || 'video/mp4', |
|
|
|
}); |
|
|
|
}); |
|
|
|
const filePath = uploadRes?.data || ''; |
|
|
|
if (filePath) { |
|
|
|
cloudVideoPath.value = filePath; |
|
|
|
cloudVideo.value = toFullFileUrl(filePath); |
|
|
|
} else { |
|
|
|
showToast('上传失败'); |
|
|
|
} |
|
|
|
} catch (err) { |
|
|
|
console.error('[nosCloudRecord] 视频录制/上传失败:', err); |
|
|
|
if (err?.message !== '已取消录制') { |
|
|
|
showToast(err?.message || '录制失败或已取消'); |
|
|
|
} |
|
|
|
} finally { |
|
|
|
videoUploading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const validateForm = () => { |
|
|
|
@ -495,6 +474,63 @@ onMounted(() => { |
|
|
|
justify-content: center; |
|
|
|
border-radius: 4px; |
|
|
|
border: 1px dashed #ebedf0; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
.video-upload-state { |
|
|
|
width: 80px; |
|
|
|
height: 80px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
gap: 4px; |
|
|
|
background-color: #f7f8fa; |
|
|
|
border-radius: 4px; |
|
|
|
border: 1px dashed #ebedf0; |
|
|
|
|
|
|
|
.upload-text { |
|
|
|
font-size: 12px; |
|
|
|
color: #969799; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.video-preview-item { |
|
|
|
position: relative; |
|
|
|
width: 80px; |
|
|
|
height: 80px; |
|
|
|
border-radius: 4px; |
|
|
|
overflow: hidden; |
|
|
|
background: #000; |
|
|
|
|
|
|
|
.video-thumb { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
object-fit: cover; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
.play-icon { |
|
|
|
position: absolute; |
|
|
|
top: 50%; |
|
|
|
left: 50%; |
|
|
|
transform: translate(-50%, -50%); |
|
|
|
font-size: 28px; |
|
|
|
color: rgba(255, 255, 255, 0.9); |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
.delete-icon { |
|
|
|
position: absolute; |
|
|
|
top: 2px; |
|
|
|
right: 2px; |
|
|
|
font-size: 16px; |
|
|
|
color: #fff; |
|
|
|
background: rgba(0, 0, 0, 0.5); |
|
|
|
border-radius: 50%; |
|
|
|
padding: 2px; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.form-tip { |
|
|
|
|