feat: 视频上传

This commit is contained in:
rd
2025-07-31 18:18:50 +08:00
parent 372f673119
commit 1cb33bd3ad
3 changed files with 157 additions and 33 deletions

View File

@ -61,4 +61,14 @@ export const postBatchDownload = (params = {}) => {
// 任务中心-批量查询任务状态 // 任务中心-批量查询任务状态
export const batchQueryTaskStatus = (params = {}) => { export const batchQueryTaskStatus = (params = {}) => {
return Http.get(`/v1/tasks/batch-query-status`, params); return Http.get(`/v1/tasks/batch-query-status`, params);
}; };
// 获取图片上传地址
export const getImagePreSignedUrl = (params = {}) => {
return Http.get('/v1/oss/image-pre-signed-url', params);
};
// 获取视频上传地址
export const getVideoPreSignedUrl = (params = {}) => {
return Http.get('/v1/oss/video-pre-signed-url', params);
};

View File

@ -109,4 +109,26 @@ export function downloadByUrl(url: string, filename?: string) {
export function genRandomId() { export function genRandomId() {
return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`; return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
}
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
export function getVideoDuration(file: any) {
return new Promise((resolve) => {
const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = function() {
window.URL.revokeObjectURL(video.src);
resolve(video.duration);
};
video.src = URL.createObjectURL(file);
});
} }

View File

@ -1,10 +1,13 @@
<script lang="jsx"> <script lang="jsx">
import axios from 'axios';
import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button } from '@arco-design/web-vue'; import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button } from '@arco-design/web-vue';
import CommonSelect from '@/components/common-select'; import CommonSelect from '@/components/common-select';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import { formatFileSize, getVideoDuration } from '@/utils/tools';
import { getProjectList } from '@/api/all/propertyMarketing'; import { getProjectList } from '@/api/all/propertyMarketing';
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png'; import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
@ -13,6 +16,12 @@ const FORM_RULES = {
title: [{ required: true, message: '请输入标题' }], title: [{ required: true, message: '请输入标题' }],
}; };
const ENUM_UPLOAD_STATUS = {
DEFAULT: 'default',
UPLOADING: 'uploading',
END: 'end',
};
export default { export default {
name: 'ManuscriptForm', name: 'ManuscriptForm',
props: { props: {
@ -30,11 +39,63 @@ export default {
const formRef = ref(null); const formRef = ref(null);
const uploadRef = ref(null); const uploadRef = ref(null);
const projects = ref([]); const projects = ref([]);
const uploadStatus = ref(ENUM_UPLOAD_STATUS.DEFAULT);
const isVideo = computed(() => false); const videoInfo = ref({
name: '',
size: '',
percent: 0,
poster: '',
time: '',
});
function getFileExtension(filename) {
const match = filename.match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : '';
}
const isVideo = computed(() => true);
const replaceVideo = () => { function formatDuration(seconds) {
console.log('replaceVideo'); const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
const milliseconds = Math.floor((seconds % 1) * 100);
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}.${milliseconds
.toString()
.padStart(2, '0')}`;
}
const replaceVideo = async (option) => {
try {
const {
fileItem: { file },
} = option;
uploadStatus.value = ENUM_UPLOAD_STATUS.UPLOADING;
// 重置进度为0
videoInfo.value.percent = 0;
videoInfo.value.name = file.name;
videoInfo.value.size = formatFileSize(file.size);
// const duration = getVideoDuration(file);
// videoInfo.value.time = formatDuration(duration);
const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) });
const { file_name, upload_url } = response?.data;
if (!upload_url) {
throw new Error('未能获取有效的预签名上传地址');
}
const blob = new Blob([file], { type: file.type });
const res = await axios.put(upload_url, blob, {
headers: { 'Content-Type': file.type },
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(progressEvent.progress * 100);
videoInfo.value.percent = percentCompleted;
},
});
props.modelValue.files.push(file_name);
} finally {
uploadStatus.value = ENUM_UPLOAD_STATUS.END;
}
}; };
// 文件上传处理 // 文件上传处理
const handleUpload = (option) => { const handleUpload = (option) => {
@ -103,7 +164,36 @@ export default {
formRef.value?.clearValidate?.(); formRef.value?.clearValidate?.();
}; };
const renderImage = () => { const renderVideoUpload = () => {
return (
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={replaceVideo}
accept=".mp4,.webm,.ogg,.mov,.avi,.flv,.wmv,.mkv"
show-file-list={false}
>
{{
'upload-button': () => {
if (uploadStatus.value === ENUM_UPLOAD_STATUS.DEFAULT) {
return (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传视频</span>
</div>
);
} else {
return <Button type="text">替换视频</Button>;
}
},
}}
</Upload>
);
};
const renderVideo = () => {
const isUploading = uploadStatus.value === ENUM_UPLOAD_STATUS.UPLOADING;
const isEnd = uploadStatus.value === ENUM_UPLOAD_STATUS.END;
return ( return (
<FormItem <FormItem
field="files" field="files"
@ -116,40 +206,42 @@ export default {
), ),
}} }}
> >
<div class="flex items-center justify-between p-12px rounded-8px bg-#F7F8FA w-784px"> {uploadStatus.value === ENUM_UPLOAD_STATUS.DEFAULT ? (
<div class="flex items-center mr-12px"> renderVideoUpload()
<img src={''} class="w-80 h-80 object-cover mr-16px rounded-8px" /> ) : (
<div class="flex flex-col"> <div class="flex items-center justify-between p-12px rounded-8px bg-#F7F8FA w-784px">
<TextOverTips <div class="flex items-center mr-12px">
context={`视频素材标题视频素材标题.mp4`} {isUploading ? (
class="mb-4px cts !text-14px !lh-22px color-#211F24" <div class="w-80px h-80px flex items-center justify-center bg-#fff rounded-8px mr-16px">
/> <icon-loading size="24" class="color-#B1B2B5" />
<p> </div>
<span class="cts color-#939499 mr-24px">视频大小483.29kb</span> ) : (
<span class="cts color-#939499">视频时长2.73s</span> <img src={''} class="w-80 h-80 object-cover mr-16px rounded-8px" />
</p> )}
<div class="flex flex-col">
<TextOverTips context={videoInfo.value.name} class="mb-4px cts !text-14px !lh-22px color-#211F24" />
{isEnd ? (
<p>
<span class="cts color-#939499 mr-24px">视频大小{videoInfo.value.size}</span>
<span class="cts color-#939499">视频时长2.73s</span>
</p>
) : (
<div class="flex items-center">
<icon-loading size="16" class="color-#6D4CFE mr-8px" />
<span class="cts !color-#6D4CFE mr-4px">上传中</span>
<span class="cts !color-#6D4CFE mr-4px">{videoInfo.value.percent}%</span>
</div>
)}
</div>
</div> </div>
<div>{renderVideoUpload()}</div>
</div> </div>
<div> )}
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={replaceVideo}
accept=".mp4,.webm,.ogg,.mov,.avi,.flv,.wmv,.mkv"
show-file-list={false}
>
{{
'upload-button': () => <Button type="text">替换视频</Button>,
}}
</Upload>
</div>
</div>
</FormItem> </FormItem>
); );
}; };
const renderVideo = () => { const renderImage = () => {
return ( return (
<FormItem <FormItem
field="files" field="files"