import { NModal, NUpload, UploadFileInfo, useMessage } from 'naive-ui'; import { defineComponent, watch, PropType, reactive, ref } from 'vue'; import { policy } from '@/components/upload-file/api'; import Copper from '@/components/upload-file/copper'; import axios from 'axios'; import styles from './index.module.less'; import iconUploadAdd from '../../../images/icon-upload-add.svg'; import { NaturalTypeEnum, PageEnum } from '@/enums/pageEnum'; import { formatUrlType } from '.'; /** * 1. 图片上传可以进行裁剪 * 2. 视频上传可以选择某一帧做为封面 * 3. 音频只用限制某一种格式 * 4. 只支持单个上传,因为多个上传没有办法去处理,即有视频,图片等 */ export default defineComponent({ name: 'upload-file', props: { fileList: { type: String, default: '' }, imageList: { type: Array, default: () => [] }, accept: { // 支持类型 type: String, default: '.jpg,.png,.jpeg,.gif' }, showType: { type: String as PropType<'default' | 'custom'>, default: 'default' }, showFileList: { type: Boolean, default: true }, max: { type: Number as PropType, default: 1 }, multiple: { type: Boolean as PropType, default: false }, disabled: { type: Boolean as PropType, default: false }, bucketName: { type: String, default: 'gyt' }, path: { type: String, default: '' }, fileName: { type: String, default: '' }, cropper: { // 是否裁切, 只有图片才支持 type: Boolean as PropType, default: false }, options: { type: Object, default: () => { return { viewMode: 0, autoCrop: true, //是否默认生成截图框 enlarge: 1, // 图片放大倍数 autoCropWidth: 200, //默认生成截图框宽度 autoCropHeight: 200, //默认生成截图框高度 fixedBox: false, //是否固定截图框大小 不允许改变 previewsCircle: true, //预览图是否是原图形 title: '上传图片' }; } } }, emits: [ 'update:fileList', 'close', 'readFileInputEventAsArrayBuffer', 'remove', 'finished' ], setup(props, { emit, expose, slots }) { const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`; const message = useMessage(); const visiable = ref(false); const btnLoading = ref(false); const tempFiileBuffer = ref(); const uploadRef = ref(); const state = reactive({ policy: '', signature: '', key: '', KSSAccessKeyId: '', acl: 'public-read', name: '' }) as any; const fileListRef = ref([]); const initFileList = () => { if (props.fileList) { const splitName = props.fileList.split('/'); fileListRef.value = [ { id: new Date().getTime().toString(), name: splitName[splitName.length - 1], status: 'finished', url: props.fileList } ]; } else { fileListRef.value = []; } }; initFileList(); watch( () => props.imageList, () => { initFileList(); } ); watch( () => props.fileList, () => { initFileList(); } ); const handleClearFile = () => { uploadRef.value?.clear(); }; expose({ handleClearFile }); const CropperModal = ref(); const onBeforeUpload = async (options: any) => { const file = options.file; // 文件大小 let isLt2M = true; const type = file.type.includes('image') ? NaturalTypeEnum.IMG : file.type.includes('audio') ? NaturalTypeEnum.SONG : NaturalTypeEnum.VIDEO; const size = type === 'IMG' ? 2 : type === 'SONG' ? 20 : 500; if (size) { isLt2M = file.file.size / 1024 / 1024 < size; if (!isLt2M) { message.error(`文件大小不能超过${size}M`); return false; } } if (!isLt2M) { return isLt2M; } // 是否裁切 if (props.cropper && type === 'IMG') { getBase64(file.file, (imageUrl: any) => { const target = Object.assign({}, props.options, { img: imageUrl, name: file.file.name // 上传文件名 }); visiable.value = true; setTimeout(() => { CropperModal.value?.edit(target); }, 100); }); return false; } try { btnLoading.value = true; const name = file.file.name; const suffix = name.slice(name.lastIndexOf('.')); const fileName = `${props.path}${ props.fileName || Date.now() + suffix }`; const obj = { filename: fileName, bucketName: props.bucketName, postData: { filename: fileName, acl: 'public-read', key: fileName, unknowValueField: [] } }; const { data } = await policy(obj); state.policy = data.policy; state.signature = data.signature; state.key = fileName; state.KSSAccessKeyId = data.kssAccessKeyId; state.name = fileName; tempFiileBuffer.value = file.file; } catch { // // message.error('上传失败') btnLoading.value = false; return false; } return true; }; const getBase64 = async (img: any, callback: any) => { const reader = new FileReader(); reader.addEventListener('load', () => callback(reader.result)); reader.readAsDataURL(img); }; const onFinish = async (options: any) => { const url = ossUploadUrl + state.key; const type = formatUrlType(url); let coverImg = ''; if (type === 'IMG') { coverImg = url; } else if (type === 'SONG') { coverImg = PageEnum.SONG_DEFAULT_COVER; } else if (type === 'VIDEO') { // 获取视频封面图 await getVideoCoverImg(); // coverImg = 'https://gyt.ks3-cn-beijing.ksyuncs.com/1688997532875.png'; } emit('update:fileList', url); emit('readFileInputEventAsArrayBuffer', tempFiileBuffer.value); emit('finished', { coverImg, content: url }); options.file.url = url; visiable.value = false; btnLoading.value = false; }; // 获取文件流 const base64ImgtoFile = (dataurl: string, filename = 'file') => { const arr: any = dataurl.split(','); const mime = arr[0].match(/:(.*?);/)[1]; const suffix = mime.split('/')[1]; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], `${filename}.${suffix}`, { type: mime }); }; const getVideoMsg = (file: any) => { return new Promise(resolve => { let dataURL = ''; const videoElement = document.createElement('video'); videoElement.currentTime = 1; videoElement.src = URL.createObjectURL(file); videoElement.addEventListener('loadeddata', function () { const canvas: any = document.createElement('canvas'), width = videoElement.videoWidth, //canvas的尺寸和图片一样 height = videoElement.videoHeight; canvas.width = width; canvas.height = height; canvas.getContext('2d').drawImage(videoElement, 0, 0, width, height); //绘制canvas dataURL = canvas.toDataURL('image/jpeg'); //转换为base64 resolve(dataURL); }); }); }; const getVideoCoverImg = async () => { try { btnLoading.value = true; const imgBlob: any = await getVideoMsg(tempFiileBuffer.value); console.log(imgBlob, 'downloading'); const fileName = `${props.path}${Date.now() + '.jpeg'}`; const obj = { filename: fileName, bucketName: props.bucketName, postData: { filename: fileName, acl: 'public-read', key: fileName, unknowValueField: [] } }; const { data } = await policy(obj); const fileParams = { policy: data.policy, signature: data.signature, key: fileName, KSSAccessKeyId: data.kssAccessKeyId, name: fileName } as any; const formData = new FormData(); for (const key in fileParams) { formData.append(key, fileParams[key]); } console.log(base64ImgtoFile(imgBlob), 'base64ImgtoFile(imgBlob)'); formData.append('file', base64ImgtoFile(imgBlob), fileName); await axios.post(ossUploadUrl, formData); const url = ossUploadUrl + fileName; console.log(url, '12312312'); return url; } finally { btnLoading.value = false; } }; const onRemove = async () => { emit('update:fileList', ''); emit('remove'); btnLoading.value = false; }; // 裁切失败 // const cropperNo = () => {} // 裁切成功 const cropperOk = async (blob: any) => { try { const fileName = `${props.path}${ props.fileName || new Date().getTime() + '.png' }`; const obj = { filename: fileName, bucketName: props.bucketName, postData: { filename: fileName, acl: 'public-read', key: fileName, unknowValueField: [] } }; const { data } = await policy(obj); state.policy = data.policy; state.signature = data.signature; state.key = fileName; state.KSSAccessKeyId = data.kssAccessKeyId; state.name = fileName; const formData = new FormData(); for (const key in state) { formData.append(key, state[key]); } formData.append('file', blob); await axios.post(ossUploadUrl, formData).then(() => { const url = ossUploadUrl + state.key; const splitName = url.split('/'); fileListRef.value = [ { id: new Date().getTime().toString(), name: splitName[splitName.length - 1], status: 'finished', url: url } ]; emit('update:fileList', url); emit('finished', { coverImg: url, content: url }); visiable.value = false; }); } catch { return false; } }; return () => (
onBeforeUpload(options)} onFinish={(options: any) => onFinish(options)} onRemove={() => onRemove()}> {props.showType === 'default' && (

上传

)} {props.showType === 'custom' && slots.custom && slots.custom()}
{/* @cropper-no="error" @cropper-ok="success" */} (visiable.value = false)} onCropperOk={cropperOk} />
); } });