利用Vue3和element-plus實(shí)現(xiàn)圖片上傳組件
前言
element-plus 提供了 uploader 組件,但是不好定制化,所以自己又造了個(gè)輪子,實(shí)現(xiàn)了一個(gè)圖片上傳的組件,它的預(yù)期行為是:
1.還沒(méi)上傳圖片時(shí),顯示上傳卡片
2.上傳圖片時(shí)顯示進(jìn)度條,隱藏上傳卡片
3.上傳成功時(shí)顯示圖片縮略圖,上傳失敗則顯示失敗提示
4.支持上傳圖片的預(yù)覽和刪除
具體如下圖所示:
具體代碼
圖片上傳
這里使用的圖床是牛圖網(wǎng),無(wú)需注冊(cè),貌似也沒(méi)有圖片大小的限制,但是請(qǐng)不要上傳違規(guī)圖像。
<code>import axios from "axios" import { ElMessage } from 'element-plus' const service = axios.create({ baseURL: "/image" }) service.interceptors.response.use(response => { const code = response.data.code || 200 if (code === 200) { return response.data.data } let msg = response.data.code + " " + response.data.msg ElMessage.error(msg) return Promise.reject('上傳圖片失?。? + msg) }) /** * 上傳圖片 * @param {File} file 圖片文件 * @param {RefImpl} progress 上傳進(jìn)度 * @returns promise */ function uploadImage(file, progress) { let formData = new FormData(); formData.append("file", file) return service({ url: "/upload", method: "post", data: formData, onUploadProgress(event) { let v = Math.round(event.loaded / event.total * 100) progress.value = v == 100 ? 80 : v }, }) } export { uploadImage }
這里使用 onUploadProgress
來(lái)監(jiān)視上傳進(jìn)度,但是實(shí)際上直接使用計(jì)算出來(lái)的進(jìn)度往往會(huì)和實(shí)際的存在很大的偏差,也就是說(shuō):即使你還在上傳,axios
也會(huì)告訴你已經(jīng)上傳完了,所以這里把 100
的進(jìn)度換成了 80
,真正的 100
進(jìn)度應(yīng)該在服務(wù)器返回 url
時(shí)設(shè)置。
受到同源策略的限制,我們需要在 vue.config.js 中配置一下代理服務(wù)器:
<code>module.exports = { devServer: { proxy: { "/image": { target: "https://niupic.com/api", pathRewrite: { "^/image": "" }, }, } } }
上傳組件
圖片預(yù)覽功能用的是 vue-easy-light-box
,如果沒(méi)有安裝的話(huà)可以 npm install --save vue-easy-lightbox@next
安裝一下。下面是具體代碼:
<code><template> <div class="uploader"> <input type="file" id="file-input" style="display: none" accept="image/*" @change="onImageAdded" /> <div class="card upload-card" @click="openFileDialog" v-if="!isThumbnailVisible" > <svg class="icon" width="28" height="28" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" > <path fill="#8c939d" d="M480 480V128a32 32 0 0164 0v352h352a32 32 0 110 64H544v352a32 32 0 11-64 0V544H128a32 32 0 010-64h352z" ></path> </svg> </div> <div class="card thumbnail-card" v-show="isThumbnailVisible"> <img src="" alt="縮略圖" id="thumbnail" /> <label class="success-label" v-show="isSuccessLabelVisible" ><i class="success-icon" ><svg class="icon" width="12" height="12" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" > <path fill="white" d="M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z" ></path></svg ></i> </label> <!-- 圖標(biāo) --> <div class="thumbnail-actions"> <span class="thumbnail-preview" @click="handleThumbnailPreview"> <svg class="icon" width="20" height="20" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" > <path fill="white" d="M795.904 750.72l124.992 124.928a32 32 0 01-45.248 45.248L750.656 795.904a416 416 0 1145.248-45.248zM480 832a352 352 0 100-704 352 352 0 000 704zm-32-384v-96a32 32 0 0164 0v96h96a32 32 0 010 64h-96v96a32 32 0 01-64 0v-96h-96a32 32 0 010-64h96z" ></path> </svg> </span> <span class="thumbnail-delete" @click="handleThumbnailRemove"> <svg class="icon" width="20" height="20" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" > <path fill="white" d="M160 256H96a32 32 0 010-64h256V95.936a32 32 0 0132-32h256a32 32 0 0132 32V192h256a32 32 0 110 64h-64v672a32 32 0 01-32 32H192a32 32 0 01-32-32V256zm448-64v-64H416v64h192zM224 896h576V256H224v640zm192-128a32 32 0 01-32-32V416a32 32 0 0164 0v320a32 32 0 01-32 32zm192 0a32 32 0 01-32-32V416a32 32 0 0164 0v320a32 32 0 01-32 32z" ></path> </svg> </span> </div> <!-- 進(jìn)度條 --> <el-progress type="circle" :percentage="progress" v-show="isProgressVisible" :width="110" id="progress" /> </div> <vue-easy-lightbox moveDisabled :visible="isLightBoxVisible" :imgs="localImageUrl" :index="index" @hide="handleLightboxHide" /> </div> </template> <script> import { ref, computed } from "vue"; import { uploadImage } from "../api/image"; import { Plus } from "@element-plus/icons-vue"; import VueEasyLightbox from "vue-easy-lightbox"; import { ElMessage } from 'element-plus/lib/components'; export default { name: "KilaKilaUploader", emits: ["uploaded", "aboutToUpload", "removed"], components: { Plus, VueEasyLightbox }, setup(props, context) { let progress = ref(0); let isLightBoxVisible = ref(false); let isProgressVisible = ref(false); let isSuccessLabelVisible = ref(false); let imageUrl = ref(""); let localImageUrl = ref(""); let index = ref(0); let isThumbnailVisible = computed(() => localImageUrl.value.length > 0); function openFileDialog() { document.getElementById("file-input").click(); } function onImageAdded() { let fileInput = document.getElementById("file-input"); if (fileInput.files.length == 0) { return; } context.emit("aboutToUpload"); let file = fileInput.files[0]; setImageUrl(URL.createObjectURL(file)); upload(file); } function setImageUrl(url) { let thumbnailEl = document.getElementById("thumbnail"); thumbnailEl.src = localImageUrl.value = url; } function handleThumbnailRemove(file) { imageUrl.value = ""; localImageUrl.value = ""; context.emit("removed", file); } function handleThumbnailPreview() { isLightBoxVisible.value = true; } function handleLightboxHide() { isLightBoxVisible.value = false; } function upload(file) { progress.value = 0; isProgressVisible.value = true; isSuccessLabelVisible.value = false; uploadImage(file, progress).then( (url) => { progress.value = 100; imageUrl.value = url; document.getElementById("thumbnail").src = url; context.emit("uploaded", url); setTimeout(() => { isProgressVisible.value = false; isSuccessLabelVisible.value = true; }, 200); }, () => { isProgressVisible.value = false; localImageUrl.value = ""; context.emit("uploaded", ""); ElMessage.error("哎呀,圖片上傳出錯(cuò)啦~") } ); } return { progress, imageUrl, localImageUrl, index, isLightBoxVisible, isThumbnailVisible, isProgressVisible, isSuccessLabelVisible, handleThumbnailRemove, handleThumbnailPreview, handleLightboxHide, openFileDialog, onImageAdded, setImageUrl, }; }, }; </script> <style lang="less" scoped> .uploader { display: flex; } .card { background-color: #fbfdff; border: 1px dashed #c0ccda; border-radius: 6px; width: 148px; height: 148px; overflow: hidden; } .upload-card { display: flex; justify-content: center; align-items: center; transition: all 0.3s; cursor: pointer; &:hover { border-color: #409eff; color: #409eff; } } .thumbnail-card { border: 1px solid #c0ccda; position: relative; #thumbnail { width: 100%; height: 100%; object-fit: contain; display: inline; } .success-label { position: absolute; right: -15px; top: -6px; width: 40px; height: 24px; background: #67c23a; text-align: center; transform: rotate(45deg); box-shadow: 0 0 1pc 1px #0003; .success-icon { position: absolute; left: 13px; top: 1px; transform: rotate(-45deg); } } #progress { width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: rgba(255, 255, 255, 0.7); :deep(.el-progress-circle) { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } } .thumbnail-actions { width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); opacity: 0; transition: all 0.4s ease; display: flex; justify-content: center; align-items: center; position: absolute; top: 0; left: 0; border-radius: 6px; .thumbnail-preview, .thumbnail-delete { cursor: pointer; margin: 0 8px; display: inline-block; } &:hover { opacity: 1; } } } :deep(.vel-img) { box-shadow: 0 5px 20px 2px rgba(0, 0, 0, 0.35); } </style>
在圖片上傳之前、上傳完成和移除圖片的時(shí)候都會(huì)觸發(fā)相應(yīng)的自定義事件,父級(jí)組件可以處理這些事件來(lái)設(shè)置圖片 url。
以上就是利用Vue3和element-plus實(shí)現(xiàn)圖片上傳組件的詳細(xì)內(nèi)容,更多關(guān)于Vue3 element-plus圖片上傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vant 中的Toast設(shè)置全局的延遲時(shí)間操作
這篇文章主要介紹了Vant 中的Toast設(shè)置全局的延遲時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11淺析vue-router實(shí)現(xiàn)原理及兩種模式
這篇文章主要介紹了vue-router實(shí)現(xiàn)原理及兩種模式分析,給大家介紹了vue-router hash模式與history模式不同模式下處理邏輯,需要的朋友可以參考下2020-02-02vue預(yù)覽 pdf、word、xls、ppt、txt文件的實(shí)現(xiàn)方法
這篇文章主要介紹了vue預(yù)覽 pdf、word、xls、ppt、txt文件的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Vue實(shí)現(xiàn)手機(jī)橫屏效果的代碼示例
有時(shí)候一些頁(yè)面希望在手機(jī)上可以實(shí)現(xiàn)橫屏的效果,比如播放頁(yè)面,所以本文我們講給大家介紹Vue如何實(shí)現(xiàn)手機(jī)橫屏效果,文章通過(guò)代碼示例介紹的非常詳細(xì),感興趣的同學(xué)跟著小編一起來(lái)看看吧2023-08-08Vue3中使用qrcode庫(kù)實(shí)現(xiàn)二維碼生成
Vue3中實(shí)現(xiàn)二維碼生成需要使用第三方庫(kù)來(lái)處理生成二維碼的邏輯,常用的庫(kù)有?qrcode和?vue-qrcode,本文主要介紹了Vue3中使用qrcode庫(kù)實(shí)現(xiàn)二維碼生成,感興趣的可以了解一下2023-12-12解決Vue中mounted鉤子函數(shù)獲取節(jié)點(diǎn)高度出錯(cuò)問(wèn)題
本篇文章給大家分享了如何解決Vue中mounted鉤子函數(shù)獲取節(jié)點(diǎn)高度出錯(cuò)問(wèn)題,對(duì)此有興趣的朋友可以參考學(xué)習(xí)下。2018-05-05