feat: 视频上传
This commit is contained in:
@ -62,3 +62,13 @@ export const postBatchDownload = (params = {}) => {
|
||||
export const batchQueryTaskStatus = (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);
|
||||
};
|
||||
|
||||
@ -110,3 +110,25 @@ export function downloadByUrl(url: string, filename?: string) {
|
||||
export function genRandomId() {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@ -1,10 +1,13 @@
|
||||
<script lang="jsx">
|
||||
import axios from 'axios';
|
||||
import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button } from '@arco-design/web-vue';
|
||||
import CommonSelect from '@/components/common-select';
|
||||
import { VueDraggable } from 'vue-draggable-plus';
|
||||
import TextOverTips from '@/components/text-over-tips';
|
||||
|
||||
import { formatFileSize, getVideoDuration } from '@/utils/tools';
|
||||
import { getProjectList } from '@/api/all/propertyMarketing';
|
||||
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
|
||||
|
||||
import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
|
||||
|
||||
@ -13,6 +16,12 @@ const FORM_RULES = {
|
||||
title: [{ required: true, message: '请输入标题' }],
|
||||
};
|
||||
|
||||
const ENUM_UPLOAD_STATUS = {
|
||||
DEFAULT: 'default',
|
||||
UPLOADING: 'uploading',
|
||||
END: 'end',
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ManuscriptForm',
|
||||
props: {
|
||||
@ -30,11 +39,63 @@ export default {
|
||||
const formRef = ref(null);
|
||||
const uploadRef = ref(null);
|
||||
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 = () => {
|
||||
console.log('replaceVideo');
|
||||
function formatDuration(seconds) {
|
||||
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) => {
|
||||
@ -103,7 +164,36 @@ export default {
|
||||
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 (
|
||||
<FormItem
|
||||
field="files"
|
||||
@ -116,40 +206,42 @@ export default {
|
||||
),
|
||||
}}
|
||||
>
|
||||
{uploadStatus.value === ENUM_UPLOAD_STATUS.DEFAULT ? (
|
||||
renderVideoUpload()
|
||||
) : (
|
||||
<div class="flex items-center justify-between p-12px rounded-8px bg-#F7F8FA w-784px">
|
||||
<div class="flex items-center mr-12px">
|
||||
{isUploading ? (
|
||||
<div class="w-80px h-80px flex items-center justify-center bg-#fff rounded-8px mr-16px">
|
||||
<icon-loading size="24" class="color-#B1B2B5" />
|
||||
</div>
|
||||
) : (
|
||||
<img src={''} class="w-80 h-80 object-cover mr-16px rounded-8px" />
|
||||
)}
|
||||
<div class="flex flex-col">
|
||||
<TextOverTips
|
||||
context={`视频素材标题视频素材标题.mp4`}
|
||||
class="mb-4px cts !text-14px !lh-22px color-#211F24"
|
||||
/>
|
||||
<TextOverTips context={videoInfo.value.name} class="mb-4px cts !text-14px !lh-22px color-#211F24" />
|
||||
{isEnd ? (
|
||||
<p>
|
||||
<span class="cts color-#939499 mr-24px">视频大小:483.29kb</span>
|
||||
<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>
|
||||
<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>{renderVideoUpload()}</div>
|
||||
</div>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVideo = () => {
|
||||
const renderImage = () => {
|
||||
return (
|
||||
<FormItem
|
||||
field="files"
|
||||
|
||||
Reference in New Issue
Block a user