feat: 视频上传
This commit is contained in:
@ -62,3 +62,13 @@ 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);
|
||||||
|
};
|
||||||
|
|||||||
@ -110,3 +110,25 @@ 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user