ElementUI修改實現(xiàn)更好用圖片上傳預(yù)覽組件
前言
嗯,,,跟之前封裝“全局 Loading”的出發(fā)點基本一樣,因為產(chǎn)品覺得 ElementUI 提供的默認(rèn)上傳組件,使用“照片墻”或者“縮略圖”模式都需要去改動原本的組件樣式,并且縮略圖尺寸也不能調(diào)整,預(yù)覽模式也會對原始圖片進行縮放和處理,不符合系統(tǒng)本身的樣式規(guī)范。
最離譜的是,預(yù)覽模式居然有背景色,但是背景色又沒有填滿整個 model 的背景區(qū)域,,,甚至還出現(xiàn)了滾動條?。。?/p>
所以,為了更好的配合產(chǎn)品和UI,特地重新編寫了一個圖片上傳組件。
1. 功能設(shè)計
嗯,既然是圖片上傳,那么肯定只支持圖片文件了。因為是內(nèi)部項目,所以也保留了 http 上傳部分,大家可以參照 ElementUI 適當(dāng)修改。
修改后的上傳組件支持以下功能:
- 上傳(基礎(chǔ)中的基礎(chǔ))
- 實現(xiàn) v-model 語法糖綁定上傳數(shù)據(jù)列表(嗯,,,也很基礎(chǔ))
- 需要支持大圖預(yù)覽
- 不能換行,超出寬度顯示滾動條且支持鼠標(biāo)控制(不用 shift 的那種)
功能設(shè)計完成之后,大致的頁面樣式就是這樣的:
2. 實現(xiàn)
為了能夠適應(yīng)更多的場景,我決定把預(yù)覽部分也直接提取出來。
2.1 圖片預(yù)覽 PicturePreviewer
這里的圖片預(yù)覽是基于 ElDialog 開發(fā)的,支持翻頁、循環(huán)等。
本身不依賴外部的圖片元素,可以和 ElDialog 一樣直接使用 visible 屬性來控制顯示和隱藏。
<template> <el-dialog :title="title" :visible="visible" :close-on-click-modal="false" width="1000px" destroy-on-close append-to-body @close="closeDialog" > <div class="q-picture__img-box"> <div class="q-picture__prev-btn" @mouseover="leftBtnStatus = true" @mouseleave="leftBtnStatus = false" > <transition name="btn-fade"> <el-button v-show="leftBtnStatus && isNeeding" circle icon="el-icon-arrow-left" @click="lastImage()" /> </transition> </div> <img v-show="visible" :src="currentSrc" alt="" v-loading="loading" @load="loading = false" /> <div class="q-picture__next-btn" @mouseover="rightBtnStatus = true" @mouseleave="rightBtnStatus = false" > <transition name="btn-fade"> <el-button v-show="rightBtnStatus && isNeeding" circle icon="el-icon-arrow-right" @click="nextImage()" /> </transition> </div> </div> </el-dialog> </template> <script> export default { name: "PicturePreviewer", props: { visible: { type: Boolean, default: false }, pageable: { type: Boolean, default: true }, recyclable: { type: Boolean, default: true }, src: { type: [String, Array], required: true }, title: { type: String, default: "圖片預(yù)覽" }, current: { type: Number, default: 0 } }, data() { return { currentKey: -1, leftBtnStatus: false, rightBtnStatus: false, loading: false }; }, computed: { isNeeding: function () { return typeof this.src === "object" && this.pageable && this.src && this.src.length > 1; }, currentSrc: function () { if (typeof this.src === "string") return this.src; if (this.src && this.src.length) { return this.src[this.currentKey] || ""; } return ""; } }, methods: { closeDialog() { this.$emit("update:visible", false); }, lastImage() { if (this.currentKey - 1 === -1) { if (this.recyclable) this.currentKey = this.src.length - 1; else this.$message.info("當(dāng)前已經(jīng)是第一張圖片"); } else { this.currentKey = this.currentKey - 1; } }, nextImage() { if (this.currentKey + 1 === this.src.length) { if (this.recyclable) this.currentKey = 0; else this.$message.info("當(dāng)前已經(jīng)是最后一張圖片"); } else { this.currentKey = this.currentKey + 1; } } }, watch: { current: { handler: function (val) { if (val) this.currentKey = val; else this.currentKey = 0; }, immediate: true } } }; </script>
2.2 圖片上傳 ImageUpload
圖片預(yù)覽處理完成夠,就可以處理圖片上傳了。
<template> <div class="q-upload__preview" ref="pictures" :title="messageInfo" @mouseenter="horizontalRolling" > <slot name="preSlot"></slot> <input class="q-upload__file-input" type="file" ref="fileInput" name="fileInput" @change="fileChange" :accept="accept" /> <div class="q-upload__file-label" v-loading="fileLoading" @click="selectFile" v-show="fileLists.length < limitNum && !disabled" > <i class="el-icon-plus"></i> </div> <slot name="middle"></slot> <div class="q-upload__pre-img" v-for="(i, k) in fileLists" :key="i.smallUrl"> <img class="q-upload__img" :src="i.smallUrl ? i.smallUrl : i.url" /> <div class="q-upload__mask"> <i v-if="prev" class="el-icon-zoom-in" @click="imgPreview(i, k)"></i> <i class="el-icon-delete" v-if="!disabled" @click="imgRemove(k)"></i> </div> </div> <slot name="endSlot"></slot> <picture-previewer :visible.sync="dialogImageVisible" :src="imageUrls" :current="currentImage" /> </div> </template> <script> import Utils from "../../src/utils/commonUtils"; export default { name: "ImageUpload", props: { active: { type: String, default: "/api/file/upload" }, accept: { type: String, default: "" }, limitNum: { type: Number, default: 9 }, size: { type: Number, default: 10 }, prev: { type: Boolean, default: true }, disabled: { type: Boolean, default: false }, value: { type: Array, default: () => [] } }, data() { return { fileLoading: false, dialogImageVisible: false, dialogImageUrl: "", currentImage: 0, fileLists: [], messageInfo: "" }; }, computed: { imageUrls: function () { return this.fileLists.map(o => o.url); } }, methods: { async validateImage(file) { const isJPEG = file.type === "image/jpeg"; const isJPG = file.type === "image/jpg"; const isPNG = file.type === "image/png"; const isBMP = file.type === "image/bmp"; const isLtSize = file.size / 1024 / 1024 < this.size; if (!(isJPEG || isJPG || isPNG || isBMP)) { return { status: false, message: `上傳圖片必須是${this.accept}格式!` }; } if (!isLtSize) { return { status: false, message: "上傳圖片大小不能超過 " + this.size + " MB!" }; } return { status: true, message: "" }; }, // 選擇文件 selectFile() { this.$refs.fileInput.value = null; // 置空,防止刪除后無法再次選擇 this.$refs.fileInput.click(); return true; }, // 文件選取之后· async fileChange(el) { const file = [...el.target.files][0]; let { status, message } = await this.validateImage(file); if (status) { this.fileLoading = true; await this.customHttpRequest(file); } else { this.$message.error(message); return false; } }, // 上傳 async customHttpRequest(file) { try { let fData = Utils.createUploadForm(file); let { data: { data, status, message } } = await this.$http.post(this.active, fData.formData, fData.config); if (status) { this.fileLists.unshift(data); this.$emit("success", data); } else { this.$message.error(message); this.$emit("error"); } } finally { this.fileLoading = false; } }, imgPreview(file, k) { this.dialogImageUrl = file.url; this.dialogImageVisible = true; this.currentImage = k; }, imgRemove(index) { this.fileLists.splice(index, 1); this.$emit("input", this.fileLists); this.$emit("change", this.fileLists); this.$emit("blur", this.fileLists); }, horizontalRolling() { if (this.$refs["pictures"].clientWidth < this.$refs["pictures"].scrollWidth) { this.messageInfo = "滾動滾輪查看所有信息"; } else { this.messageInfo = ""; } this.$refs["pictures"].addEventListener("mousewheel", this.$_scrollEvent); this.$once("hook:beforeDestroy", () => { this.$refs["pictures"].removeEventListener("mousewheel", this.$_scrollEvent); }); }, $_scrollEvent(e) { let left = this.$refs["pictures"].scrollLeft; this.$refs["pictures"].scrollLeft = e.deltaY > 0 ? left + 40 : left - 40; } }, watch: { value: { deep: true, immediate: true, handler: function () { this.fileLists = typeof this.value === "string" ? JSON.parse(this.value) : this.value; if (!this.fileLists) this.fileLists = []; } }, fileLists: { deep: true, immediate: false, handler: function () { if (this.value && this.value.length > this.limitNum) { this.$message.warning(`最多可以上傳【 ${this.limitNum} 】張圖片!!`); } this.$emit("input", this.fileLists); this.$emit("change", this.fileLists); } } } }; </script>
因為是內(nèi)部項目,所以上傳方法還是使用的實例上的 axios 方法來發(fā)送上傳請求的;在獨立組件庫中依然應(yīng)該通過 props 的方式傳遞項目中定義的 http 請求方法。
組件接收一個最大張數(shù)限制 limitNum 和文件大小限制 size,以及預(yù)覽控制 prev 和禁用狀態(tài) disabled。
在選擇文件之后會立即上傳、點擊已上傳文件則是預(yù)覽當(dāng)前文件;當(dāng)前內(nèi)部也依賴了 ElementUI 的 Message 組件,用來顯示提示信息。
在預(yù)覽區(qū)域前后也增加了一個插槽,用來插入開發(fā)者需要的其他信息。
在整個組件的 Dom 節(jié)點上,會添加一個鼠標(biāo)的 mouseenter 事件,當(dāng)鼠標(biāo)在組件內(nèi)部的時候,則計算內(nèi)部的縮略圖區(qū)域與外層節(jié)點的大小進行比較,如果大于外層父節(jié)點的寬度的話,則提示用戶通過鼠標(biāo)滾輪來控制縮略圖區(qū)域的滾動。
3. 后記
整個組件雖然可以滿足當(dāng)時的系統(tǒng)的一個需求,但是仔細(xì)研究代碼的話會發(fā)現(xiàn)依然有很多細(xì)節(jié)的地方需要修復(fù)。例如:
- 組件的 mouseenter 事件,每次被觸發(fā)時都會給 dom 添加一個鼠標(biāo)監(jiān)聽事件,而沒有在鼠標(biāo)移出時及時銷毀監(jiān)聽
- 沒有增加自定義 http 配置
- 沒有控制預(yù)覽組件的配置項
- 縮略圖區(qū)域沒有尺寸控制
等等一系列的問題,所以我們在抽離組件、公共邏輯的時候,還是需要盡可能的保留以后擴展的可能性,減少與外界邏輯或者業(yè)務(wù)的關(guān)聯(lián)。
以上就是ElementUI修改實現(xiàn)更好用圖片上傳預(yù)覽組件的詳細(xì)內(nèi)容,更多關(guān)于ElementUI圖片上傳預(yù)覽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VUE 直接通過JS 修改html對象的值導(dǎo)致沒有更新到數(shù)據(jù)中解決方法分析
這篇文章主要介紹了VUE 直接通過JS 修改html對象的值導(dǎo)致沒有更新到數(shù)據(jù)中解決方法,結(jié)合實例形式詳細(xì)分析了VUE使用JS修改html對象的值導(dǎo)致沒有更新到數(shù)據(jù)的原因與解決方法,需要的朋友可以參考下2019-12-12vue3+echarts+折線投影(陰影)效果的實現(xiàn)
這篇文章主要介紹了vue3+echarts+折線投影(陰影)效果的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10vue項目webpack中Npm傳遞參數(shù)配置不同域名接口
這篇文章主要介紹了vue項目webpack中Npm傳遞參數(shù)配置不同域名接口,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06Vue使用echarts散點圖在區(qū)域內(nèi)標(biāo)點
這篇文章主要為大家詳細(xì)介紹了Vue使用echarts散點圖在區(qū)域內(nèi)標(biāo)點,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03vue中defineProperty和Proxy的區(qū)別詳解
這篇文章主要介紹了vue中defineProperty和Proxy的區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11