<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
之前對一些主流手機拍出的照片大小做過對比,華為P30拍出的照片3M左右,同事的小米9不知開啟了什麼模式拍出了10M以上的照片。照片太大了對伺服器端上傳檔案造成了不小的壓力,對此,後端對前端提出了圖片上傳前對圖片進行壓縮。我們目前所用的UI庫Upload元件並不支援對上傳的圖片進行壓縮,所以花了一點時間自己寫了上傳的元件。
Upload
元件的禁用,該有的基本功能。export default { props: { icon: { //上傳元件的佔點陣圖 type: String, default: "iconcamera" }, size: { //圖片超過指定大小不讓上傳 type: Number, default: 3072 }, disabled: { //禁止上傳 type: Boolean }, iconSize: { //佔位icon的大小 type: Number, default: 24 }, name: { //input的原生屬性 type: String, default: 'file' }, accept: { //接受上傳的檔案型別 type: Array, default() { return []; } }, acceptErrorMessage: { //檔案型別錯誤的提示內容 type: String, default: '檔案型別錯誤' }, compress: { //是否開啟圖片壓縮 type: Boolean, default: true, }, compressSize: { //超過大小的圖片壓縮 type: Number, default: 512, }, data: { //上傳附帶的內容 type: Object, default() { return {}; }, }, action: { //上傳地址 type: String, default: '', }, headers: { //設定上傳的請求頭部 type: Object, default() { return {}; }, }, imgWidth: { //圖片壓縮時指定壓縮的圖片寬度 type: [Number, Boolean], default: 800, }, quality: { //圖片壓縮的質量 type: Number, default: 1, }, beforeUpload: { //上傳檔案之前的勾點 type: Function }, onSuccess: { //上傳成功的勾點 type: Function }, onError: { //上傳失敗的勾點 type: Function }, onLoadend: { //檔案上傳成功或者失敗都會執行的勾點 type: Function }, onProgress: { //檔案上傳進度的勾點 type: Function }, onSuccessText: { //上傳成功的提示內容 type: String, default: '上傳成功' }, onErrorText: { //上傳失敗的提示內容 type: String, default: '上傳失敗' }, beforeRemove: { //刪除檔案的勾點 type: Function }, showRemove: { //是否展示刪除icon type: Boolean, default: true }, type: { //單檔案上傳還是多檔案上傳 type: String, default: 'single', validator: function (value) { return ["single", "multiple"].includes(value); } }, maxNumber: { //多檔案上傳最多上傳的個數 type: Number }, isImage: { //檔案是否為圖片 type: Boolean, default: true } } }
template
<template> <div class="g7-Upload-single"> <!-- 佔位內容,圖片展示,檔案展示的處理 --> <div class="g7-Upload-default-icon"> <template v-if="!value"> <slot> <Icon :size="iconSize" :icon="icon" /> </slot> </template> <template v-else> <template v-if="isImage"> <img class="g7-Upload-img" :src="value" /> </template> <template v-else> <Icon :size="34" icon="iconicon-" /> </template> <span @click.stop="onRemove" v-if="showRemove" class="g7-Upload-removeImg"> <Icon :size="14" icon="iconcuowu" color="#fff" /> </span> </template> </div> <input class="g7-Upload-input" @change="change" :disabled="computedDisabled" :name="name" type="file" ref="input" /> <!-- 圖片壓縮需要用到的canvas --> <canvas hidden="hidden" v-if="compress" ref="canvas"></canvas> <!-- 進度條 --> <div v-if="progress > 0" class="g7-Upload-progress"> <div :style="{width:`${progress}%`}" class="g7-Upload-progress-bar"></div> </div> </div> </template>
檔案壓縮實現:
canvasDataURL(base) { const img = new Image(); img.src = base; const that = this; function ImgOnload() { /** * 計算生成圖片的寬高 */ const scale = this.width / this.height; const width = that.imgWidth === false || this.width <= that.imgWidth ? this.width : that.imgWidth; const height = width / scale; const canvas = that.$refs.canvas; canvas.width = width; canvas.height = height; //利用canvas繪製壓縮的圖片並生成新的圖片 const context = canvas.getContext("2d"); context.drawImage(this, 0, 0, width, height); canvas.toBlob( blob => { that.file = blob; that.upload(blob); that.$emit("on-change", blob, that.options); }, "image/png", that.quality ); /** * 使用完的createObjectURL需要釋放記憶體 */ window.URL.revokeObjectURL(this.src); } img.onload = ImgOnload; }
上傳檔案的實現:
export function fetch(options, file) { if (typeof XMLHttpRequest === 'undefined') { return; } const xhr = new XMLHttpRequest(); const action = options.action; if (xhr.upload) { xhr.upload.onprogress = function progress(e) { if (e.total > 0) { e.percent = e.loaded / e.total * 100; } options.uploadProgress(e); }; } const formData = new FormData(); formData.append(options.name, file, options.fileName); for (const key in options.data) { formData.append(key, options.data[key]); } // 成功回撥 xhr.onload = (e) => { const response = e.target.response; if (xhr.status < 200 || xhr.status >= 300) { options.uploadError(response); return; } options.onload(response); }; // 出錯回撥 xhr.onerror = (e) => { const response = e.target.response; options.uploadError(response); }; // 請求結束 xhr.onloadend = (e) => { const response = e.target.response; options.uploadLoadend(response); }; xhr.open('post', action, true); const headers = options.headers; for (const key in headers) { if (headers[key] !== null) { xhr.setRequestHeader(key, headers[key]); } } xhr.send(formData); }
上傳元件:
<!-- components/upload.vue --> <template> <div class="g7-Upload-single"> <div class="g7-Upload-default-icon"> <template v-if="!value"> <slot> <Icon :size="iconSize" :icon="icon" /> </slot> </template> <template v-else> <template v-if="isImage"> <img class="g7-Upload-img" :src="value" /> </template> <template v-else> <Icon :size="34" icon="iconicon-" /> </template> <span @click.stop="onRemove" v-if="showRemove" class="g7-Upload-removeImg"> <Icon :size="14" icon="iconcuowu" color="#fff" /> </span> </template> </div> <input class="g7-Upload-input" @change="change" :disabled="computedDisabled" :name="name" type="file" ref="input" /> <!-- 圖片壓縮需要用到的canvas --> <canvas hidden="hidden" v-if="compress" ref="canvas"></canvas> <!-- 進度條 --> <div v-if="progress > 0" class="g7-Upload-progress"> <div :style="{width:`${progress}%`}" class="g7-Upload-progress-bar"></div> </div> </div> </template> <script> import Icon from "../../Icon"; //自定義元件 import mixins from "./mixins"; import { getType, fetch } from "./utils"; import Toast from "../../~Toast"; //自定義元件 const compressList = ["png", "PNG", "jpg", "JPG", "jpeg", "JPEG"]; export default { components: { Icon }, mixins: [mixins], props: { value: { type: String } }, data() { return { file: "", progress: 0, src: "" }; }, computed: { computedDisabled() { return this.disabled || this.progress !== 0; } }, methods: { change(e) { if (this.disabled) { return; } const file = e.target.files[0]; if (!file) { return; } const type = getType(file.name); if (this.accept.length) { if (!this.accept.includes(type)) { Toast.info(this.acceptErrorMessage); return; } } const size = Math.round((file.size / 1024) * 100) / 100; if (size > this.size) { Toast.info(`請上傳小於${this.size / 1024}M的檔案`); return; } if (this.isCompress(type, size)) { this.canvasDataURL(URL.createObjectURL(file)); return; } this.$emit("on-change"); this.file = file; this.upload(file); }, /** * 判斷是否滿足壓縮條件 */ isCompress(type, size) { return ( this.compress && compressList.includes(type) && size > this.compressSize ); }, canvasDataURL(base) { const img = new Image(); img.src = base; const that = this; function ImgOnload() { /** * 計算生成圖片的寬高 */ const scale = this.width / this.height; const width = that.imgWidth === false || this.width <= that.imgWidth ? this.width : that.imgWidth; const height = width / scale; const canvas = that.$refs.canvas; canvas.width = width; canvas.height = height; //利用canvas繪製壓縮的圖片並生成新的blob const context = canvas.getContext("2d"); context.drawImage(this, 0, 0, width, height); canvas.toBlob( blob => { that.file = blob; that.upload(blob); that.$emit("on-change", blob, that.options); }, "image/png", that.quality ); /** * 使用完的createObjectURL需要釋放記憶體 */ window.URL.revokeObjectURL(this.src); } img.onload = ImgOnload; }, /** * 上傳成功 */ onload(e) { this.progress = 0; this.$emit("input", e); if (this.onSuccess) { this.onSuccess(this.file, e); return; } Toast.info(this.onSuccessText); }, /** * 上傳進度 */ uploadProgress(e) { this.progress = e.percent; if (this.onProgress) { this.onProgress(this.file, e); } }, /** * 上傳失敗 */ uploadError(e) { this.progress = 0; if (this.onError) { this.onSuccess(this.file, e); return; } Toast.info(this.onErrorText); }, /** * 請求結束 */ uploadLoadend(e) { this.clearInput(); if (this.onloadend) { this.onloadend(this.file, e); } }, /** * 上傳 */ upload(file) { this.clearInput(); if (!this.beforeUpload) { fetch(this, file); return; } const before = this.beforeUpload(file); if (before && before.then) { before.then(res => { if (res !== false) { fetch(this, file); } }); return; } if (before !== false) { fetch(this, file); } }, /** * 刪除檔案 */ onRemove() { this.clearInput(); if (this.type === "single") { if (!this.beforeRemove) { this.$emit("input", ""); return; } const before = this.beforeRemove(this.file, this.value); if (before && before.then) { before.then(res => { if (res !== false) { this.$emit("input", ""); } }); return; } if (before !== false) { this.$emit("input", ""); } return; } this.$emit("on-remove"); }, clearInput() { this.$refs.input.value = null; } } }; </script>
utils.js:獲取檔案的字尾
/** * 獲取檔案的字尾 * @param {*} file */ export const getType = file => file.substr(file.lastIndexOf('.') + 1); /** * 請求封裝 * @param {*} options * @param {*} file */ export function fetch(options, file) { if (typeof XMLHttpRequest === 'undefined') { return; } const xhr = new XMLHttpRequest(); const action = options.action; if (xhr.upload) { xhr.upload.onprogress = function progress(e) { if (e.total > 0) { e.percent = e.loaded / e.total * 100; } options.uploadProgress(e); }; } const formData = new FormData(); formData.append(options.name, file, options.fileName); for (const key in options.data) { formData.append(key, options.data[key]); } // 成功回撥 xhr.onload = (e) => { const response = e.target.response; if (xhr.status < 200 || xhr.status >= 300) { options.uploadError(response); return; } options.onload(response); }; // 出錯回撥 xhr.onerror = (e) => { const response = e.target.response; options.uploadError(response); }; // 請求結束 xhr.onloadend = (e) => { const response = e.target.response; options.uploadLoadend(response); }; xhr.open('post', action, true); const headers = options.headers; for (const key in headers) { if (headers[key] !== null) { xhr.setRequestHeader(key, headers[key]); } } xhr.send(formData); }
整個Upload元件的對外暴露元件:
<template> <div class="g7-Upload"> <template v-if="type === 'single'"> <Upload :icon="icon" :size="size" :accept="accept" :name="name" :acceptErrorMessage="acceptErrorMessage" :compress="compress" :iconSize="iconSize" :compressSize="compressSize" :imgWidth="imgWidth" :response="response" :showLabel="showLabel" :headers="headers" :action="action" :data="data" :quality="quality" :beforeRemove="beforeRemove" :beforeUpload="beforeUpload" :onSuccess="onSuccess" :onSuccessText="onSuccessText" :onError="onError" :onProgress="onProgress" :onLoadend="onLoadend" :onErrorText="onErrorText" :disabled="disabled" :showRemove="showRemove" v-model="currentValue" @input="input" :type="type" :maxNumber="maxNumber" :isImage="isImage" > <slot></slot> </Upload> </template> </div> </template> <script> import Upload from "./components/upload"; import mixins from "./components/mixins"; export default { name: "G-Upload", components: { Upload }, mixins: [mixins], props: { value: { type: [String, Array] } }, data() { return { currentValue: this.value }; }, watch: { value(val) { this.currentValue = val; } }, methods: { input(val) { this.$emit("input", val); }, //接管檔案上傳時,自定義檔案上傳進度 onProgress(percent) { this.$refs.upload.uploadProgress(percent); } } }; </script>
因為圖片和檔案展示的佔點陣圖不一樣,所以用一個isImage
的引數來判斷傳入的檔案是否為圖片的方式總感覺很傻。但是當網路資源的url沒有檔案字尾時沒法分辨出來是圖片還是檔案,各位大佬有木有好的解決方式。
到此這篇關於實現一個Vue版Upload元件的文章就介紹到這了,更多相關Vue Upload元件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45