import { NModal, NSpin, NUpload, NUploadDragger, 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.png'; import { NaturalTypeEnum, PageEnum } from '@/enums/pageEnum'; import { formatUrlType } from '.'; import { modalClickMask } from '/src/state'; /** * 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<number>, default: 1 }, multiple: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false }, bucketName: { type: String, default: 'gyt' }, directoryDnd: { type: Boolean as PropType<boolean>, default: false }, path: { type: String, default: '' }, fileName: { type: String, default: '' }, cropper: { // 是否裁切, 只有图片才支持 - 失效(不支持) type: Boolean as PropType<boolean>, 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<boolean>(false); const btnLoading = ref<boolean>(false); const tempFiileBuffer = ref(); const uploadRef = ref(); const state = reactive([ // { // policy: '', // signature: '', // key: '', // KSSAccessKeyId: '', // acl: 'public-read', // name: '' // } ]) as any; const fileListRef = ref<UploadFileInfo[]>([]); 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.push({ id: file.id, tempFiileBuffer: file.file, policy: data.policy, signature: data.signature, acl: 'public-read', key: fileName, KSSAccessKeyId: data.kssAccessKeyId, 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 = (options: any) => { console.log(options, 'onFinish'); onFinishAfter(options); }; const onFinishAfter = async (options: any) => { const item = state.find((c: any) => c.id == options.file.id); const url = ossUploadUrl + item.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') { // 获取视频封面图 coverImg = await getVideoCoverImg(item.tempFiileBuffer); } emit('update:fileList', url); emit('readFileInputEventAsArrayBuffer', item.tempFiileBuffer); console.log(url, 'url onFinishAfter'); emit('finished', { coverImg, content: url }); options.file.url = url; visiable.value = false; btnLoading.value = false; }; 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 console.log(canvas); canvas.toBlob((blob: any) => { // console.log(blob); resolve(blob); }); }); }); }; const getVideoCoverImg = async (file: any) => { try { btnLoading.value = true; const imgBlob: any = await getVideoMsg(file || tempFiileBuffer.value); const fileName = `${props.path}${Date.now() + '.png'}`; 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, acl: 'public-read', KSSAccessKeyId: data.kssAccessKeyId, name: fileName } as any; const formData = new FormData(); for (const key in fileParams) { formData.append(key, fileParams[key]); } formData.append('file', imgBlob); await axios.post(ossUploadUrl, formData); const url = ossUploadUrl + fileName; 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 () => ( <div class={styles.uploadFile}> <NSpin show={btnLoading.value} description="上传中..."> <NUpload ref={uploadRef} action={ossUploadUrl} data={(file: any) => { const item = state.find((c: any) => { return c.id == file.file.id; }); const { id, tempFiileBuffer, ...more } = item; return { ...more }; }} v-model:fileList={fileListRef.value} accept={props.accept} multiple={props.multiple} max={props.max} disabled={props.disabled} directoryDnd={props.directoryDnd} showFileList={props.showFileList} showPreviewButton onBeforeUpload={(options: any) => onBeforeUpload(options)} onFinish={(options: any) => { onFinish(options); }} onChange={(options: any) => { // console.log(options, 'change'); }} onRemove={() => onRemove()}> <NUploadDragger> {props.showType === 'default' && ( <div class={styles.uploadBtn}> <img src={iconUploadAdd} class={styles.iconUploadAdd} /> <p>上传</p> </div> )} {props.showType === 'custom' && slots.custom && slots.custom()} </NUploadDragger> </NUpload> </NSpin> <NModal maskClosable={modalClickMask} v-model:show={visiable.value} preset="dialog" showIcon={false} class={['modalTitle background']} title="上传图片" style={{ width: '800px' }}> {/* @cropper-no="error" @cropper-ok="success" */} <Copper ref={CropperModal} onClose={() => (visiable.value = false)} onCropperOk={cropperOk} /> </NModal> </div> ); } });