前言
本文使用Ant Design Vue 4进行自定义文件上传的操作,文件上传使用的是阿里OSS。
单图片上传
ImgUploadSingle.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| <template> <a-upload name="avatar" list-type="picture-card" class="avatar-uploader" :show-upload-list="false" :customRequest="customRequest" :before-upload="beforeUpload" @change="handleChange" > <img v-if="imageUrl" :src="imageUrl" alt="avatar" /> <div v-else> <loading-outlined v-if="loading"></loading-outlined> <plus-outlined v-else></plus-outlined> <div class="ant-upload-text">上传</div> </div> </a-upload> </template> <script lang="ts" setup> import { ref, watch } from 'vue' import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue' import { message } from 'ant-design-vue' const model = defineModel() import { ossUploadFile } from '@/assets/api/alioss_upload'
const loading = ref<boolean>(false) const imageUrl = ref<string>('')
watch(model, () => { if (model.value) { imageUrl.value = model.value.toString() } })
async function customRequest(options: any) { const formData = new FormData() formData.append('file', options.file) let uploadFile = options.file let result = await ossUploadFile(uploadFile) if (result) { options.onSuccess(result.url) } else { options.onError('上传失败') } }
const handleChange = (info: any) => { if (info.file.status === 'uploading') { console.info(info.event || 0) loading.value = true return } if (info.file.status === 'done') { model.value = info.file.response } if (info.file.status === 'error') { loading.value = false message.error('上传失败') } }
const beforeUpload = (file: any) => { console.info(file) console.info(file.type) const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' if (!isJpgOrPng) { message.error('只能上传JPG/PNG图片!') } const isLt2M = file.size / 1024 / 1024 < 2 if (!isLt2M) { message.error('图片必须小于 2MB!') } return isJpgOrPng && isLt2M } </script> <style scoped> .avatar-uploader > .ant-upload { width: 128px; height: 128px; } .ant-upload-select-picture-card i { font-size: 32px; color: #999; } .ant-upload-select-picture-card { overflow: hidden; } .ant-upload-select-picture-card img { width: 96px; border-radius: 8px; }
.ant-upload-select-picture-card .ant-upload-text { margin-top: 8px; color: #666; } </style>
|
其中
自定义上传要设置customRequest
上传状态的变化和change事件的状态是完全对应的
开始上传
开始上传的时候,返回的状态是info.file.status === 'uploading'
上传进度
每次调用options.onProgress(80)的时候,返回的状态依旧是uploading,同时可以获取进度
1
| console.info(info.event || 0)
|
上传成功
上传成功的话,返回的状态是done,options.onSuccess('https://www.psvmc.cn/head.jpg')传入的参数可以通过下面的方式获取
1
| console.info(info.file.response)
|
封面图我们可以获取文件的Base64,也可以直接使用成功传入的地址
1 2 3 4 5 6 7 8 9 10
| function getBase64(img: Blob, callback: (base64Url: string) => void) { const reader = new FileReader() reader.addEventListener('load', () => callback(reader.result as string)) reader.readAsDataURL(img) }
getBase64(info.file.originFileObj, (base64Url: string) => { imageUrl.value = base64Url loading.value = false })
|
注意
customRequest中上传中动画会在调用options.onSuccess(result.url)后取消,但是如果我们没有经过网络请求直接调用的话也还是会显示上传中,只要在setTimeout中调用即可。
上传失败
上传失败返回的状态是error,options.onError('上传失败')传入的参数可以通过下面的方式获取
1
| console.info(info.file.error)
|
多图片上传
ImgUploadMuti.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| <template> <a-flex gap="middle"> <div v-for="(imgUrl, index) in imageUrlArr" :key="index" class="img_outer"> <img class="zimg" :src="imgUrl" alt="avatar" /> <div class="del_outer"> <a-button size="small" @click="delItemClick(index)">删除</a-button> </div> </div>
<a-upload v-show="imageUrlArr.length < props.maxNum" name="avatar" list-type="picture-card" class="avatar-uploader" :show-upload-list="false" :customRequest="customRequest" :before-upload="beforeUpload" @change="handleChange" > <div> <loading-outlined v-if="loading"></loading-outlined> <plus-outlined v-else></plus-outlined> <div class="ant-upload-text">上传</div> </div> </a-upload> </a-flex> </template> <script lang="ts" setup> import { ref, watch } from 'vue' import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue' import { message } from 'ant-design-vue' const model = defineModel() import { ossUploadFile } from '@/assets/api/alioss_upload'
const props = defineProps({ maxNum: { type: Number, default: 3 } })
const loading = ref<boolean>(false) const imageUrlArr = ref<string[]>([])
watch(model, () => { if (model.value) { imageUrlArr.value = model.value.toString().split(',') } })
function delItemClick(index: number) { imageUrlArr.value.splice(index, 1) model.value = imageUrlArr.value.join(',') }
async function customRequest(options: any) { const formData = new FormData() formData.append('file', options.file) let uploadFile = options.file let result = await ossUploadFile(uploadFile) if (result) { options.onSuccess(result.url) } else { options.onError('上传失败') } }
const handleChange = (info: any) => { if (info.file.status === 'uploading') { console.info(info.event || 0) loading.value = true return } if (info.file.status === 'done') { loading.value = false imageUrlArr.value.push(info.file.response) model.value = imageUrlArr.value.join(',') } if (info.file.status === 'error') { loading.value = false message.error('上传失败') } }
const beforeUpload = (file: any) => { console.info(file) console.info(file.type) const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' if (!isJpgOrPng) { message.error('只能上传JPG/PNG图片!') } const isLt2M = file.size / 1024 / 1024 < 2 if (!isLt2M) { message.error('图片必须小于 2MB!') } return isJpgOrPng && isLt2M } </script> <style scoped> .img_outer { position: relative; }
.img_outer .del_outer { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: none; background: #00000033; align-items: flex-start; justify-content: flex-end; padding: 4px; }
.img_outer:hover .del_outer { display: flex; }
.zimg { height: 100px; } .avatar-uploader > .ant-upload { width: 128px; height: 128px; } .ant-upload-select-picture-card i { font-size: 32px; color: #999; } .ant-upload-select-picture-card { overflow: hidden; } .ant-upload-select-picture-card img { width: 96px; border-radius: 8px; }
.ant-upload-select-picture-card .ant-upload-text { margin-top: 8px; color: #666; } </style>
|
单视频上传
VideoUploadSingle.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| <template> <div class="video_upload_outer"> <div v-if="videoUrl" class="video_outer"> <video controls> <source :src="videoUrl" type="video/mp4" /> 您的浏览器不支持 video 标签。 </video> </div> <a-upload name="avatar" list-type="picture-card" class="avatar-uploader" :show-upload-list="false" :customRequest="customRequest" :before-upload="beforeUpload" @change="handleChange" > <div> <loading-outlined v-if="loading"></loading-outlined> <plus-outlined></plus-outlined> <div class="ant-upload-text">上传</div> </div> </a-upload> </div> </template> <script lang="ts" setup> import { ref, watch } from 'vue' import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue' import { message } from 'ant-design-vue' const model = defineModel() import { ossUploadFile } from '@/assets/api/alioss_upload'
const loading = ref<boolean>(false) const videoUrl = ref<string>('')
watch(model, () => { if (model.value) { videoUrl.value = model.value.toString() } })
async function customRequest(options: any) { const formData = new FormData() formData.append('file', options.file) let uploadFile = options.file let result = await ossUploadFile(uploadFile) if (result) { options.onSuccess(result.url) } else { options.onError('上传失败') } }
const handleChange = (info: any) => { if (info.file.status === 'uploading') { console.info(info.event || 0) loading.value = true return } if (info.file.status === 'done') { loading.value = false model.value = info.file.response } if (info.file.status === 'error') { loading.value = false message.error('上传失败') } }
const beforeUpload = (file: any) => { const isVideo = file.type === 'video/mp4' || file.name.endsWith('.mp4') || file.name.endsWith('.flv') if (!isVideo) { message.error('只能上传MP4/FLV格式的视频!') } const isLt200M = file.size / 1024 / 1024 < 200 if (!isLt200M) { message.error('图片必须小于 200MB!') } return isVideo && isLt200M } </script> <style scoped> .video_upload_outer { display: flex; gap: 10px; position: relative; height: 300px; width: 100%; }
.video_outer { position: relative; width: 400px; height: 100%; flex: none; }
video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } .avatar-uploader > .ant-upload { width: 300px; height: 200px; } .ant-upload-select-picture-card i { font-size: 32px; color: #999; } .ant-upload-select-picture-card { overflow: hidden; } .ant-upload-select-picture-card img { width: 96px; height: 96px; border-radius: 8px; }
.ant-upload-select-picture-card .ant-upload-text { margin-top: 8px; color: #666; } </style>
|
阿里OSS上传
npm安装
1 2
| npm install ali-oss --save npm install uuid
|
示例
这里通过临时凭证进行文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import { apiCustomGetSts } from '@/assets/api/common_api.js' import { v4 as uuidv4 } from 'uuid'
function getDateStr() { const now = new Date() const year = now.getFullYear() const month = now.getMonth() + 1 const day = now.getDate() const formattedMonth = month.toString().padStart(2, '0') const formattedDay = day.toString().padStart(2, '0') return `${year}-${formattedMonth}-${formattedDay}` }
import OSS from 'ali-oss' export const ossUploadFile = async (file) => { let fileName = file.name fileName = fileName.replace(/[^a-zA-Z0-9_.-]/g, '') let res = await apiCustomGetSts() if (res.code === 0) { let { accessKeyId, accessKeySecret, securityToken } = res.obj let client = new OSS({ region: 'oss-cn-qingdao', secure: true, accessKeyId: accessKeyId, accessKeySecret: accessKeySecret, stsToken: securityToken, bucket: 'cxzfile', refreshSTSToken: async () => { const result2 = await apiCustomGetSts() if (result2.code === 0) { let { accessKeyId, accessKeySecret, securityToken } = result2.obj return { accessKeyId: accessKeyId, accessKeySecret: accessKeySecret, stsToken: securityToken } } } }) try { let uuidStr = uuidv4() return await client.put(`admin_imgs/${getDateStr()}/${uuidStr}-${fileName}`, file) } catch (e) { console.log(e) } } return null }
|