vue 结合 element 实现多图上传组件
简介
多图上传在后台管理系统中的表单和富文本中应用较多。这里实现的有多图的上传,预览,单个的删除等常用功能
主要依赖说明 (先安装,步骤略)
1 | { |
2 | "element-ui": "2.11.1", |
3 | "vue": "^2.6.10", |
4 | "vue-router": "^3.0.1" |
5 | } |
正文
1.组件
src/components/MultipleUpload.vue
1 | <template> |
2 | <div class="upload-container"> |
3 | <el-tooltip |
4 | class="item" |
5 | effect="dark" |
6 | content="上传图片" |
7 | placement="bottom" |
8 | :hide-after="800" |
9 | > |
10 | <el-button |
11 | :style="buttonStyle" |
12 | icon="el-icon-upload" |
13 | size="mini" |
14 | type="primary" |
15 | @click="showDialog" |
16 | >上传图片</el-button |
17 | > |
18 | </el-tooltip> |
19 | |
20 | <el-dialog |
21 | title="上传图片" |
22 | append-to-body |
23 | width="700px" |
24 | :visible.sync="dialogVisible" |
25 | center |
26 | > |
27 | <div v-for="(item,index) in imgSrcList" :key="index" class="img-box"> |
28 | <span class="delete-image" title="点击删除"> |
29 | <i class="el-icon-delete-solid" @click="deleteImage(index)" /> |
30 | </span> |
31 | <img v-if="item" class="img" :src="item" alt /> |
32 | </div> |
33 | <div class="uploadImg-box"> |
34 | <input |
35 | ref="fileElem" |
36 | accept="image/*" |
37 | class="img-input" |
38 | type="file" |
39 | multiple="multiple" |
40 | @change="onchange" |
41 | /> |
42 | <el-button |
43 | class="btn" |
44 | size="small" |
45 | type="primary" |
46 | @click="handleOpenFile" |
47 | >点击上传</el-button |
48 | > |
49 | </div> |
50 | |
51 | <div class="btn-box"> |
52 | <el-button @click="dialogVisible = false">取 消</el-button> |
53 | <el-button :loading="loading" type="primary" @click="handleSubmit" |
54 | >确 定</el-button |
55 | > |
56 | </div> |
57 | </el-dialog> |
58 | </div> |
59 | </template> |
60 | |
61 | <script> |
62 | import { Loading } from "element-ui"; |
63 | import { readFile } from "@/utils/upload"; // 见下文 |
64 | import { Base64ToBlob } from "@/utils/cos"; // 见下文 |
65 | |
66 | // 定义的接口根据自己项目更换 |
67 | import { uploadImage } from "@/api/upload"; |
68 | |
69 | export default { |
70 | name: "MultipleUpload", |
71 | props: { |
72 | // 最大上传文件的大小 |
73 | maxFileSize: { |
74 | type: Number, |
75 | default: 5 |
76 | }, |
77 | buttonStyle: { |
78 | type: Object, |
79 | default: () => ({}) |
80 | } |
81 | }, |
82 | data() { |
83 | return { |
84 | dialogVisible: false, |
85 | loading: false, |
86 | imgSrcList: [] |
87 | }; |
88 | }, |
89 | methods: { |
90 | // 打开文件 |
91 | handleOpenFile() { |
92 | const input = this.$refs.fileElem; |
93 | // 解决同一个文件不能监听的问题 |
94 | input.addEventListener( |
95 | "click", |
96 | function() { |
97 | this.value = ""; |
98 | }, |
99 | false |
100 | ); |
101 | // 点击input |
102 | input.click(); |
103 | }, |
104 | |
105 | // 显示弹窗 |
106 | showDialog() { |
107 | this.dialogVisible = true; |
108 | this.imgSrcList = []; |
109 | }, |
110 | |
111 | // 监听input上传 |
112 | async onchange() { |
113 | try { |
114 | // 文件列表 |
115 | const files = this.$refs.fileElem.files; |
116 | // 文件所有尺寸 |
117 | const sizes = []; |
118 | // 所有文件的base64位地址 |
119 | const allReadFile = []; |
120 | for (let index = 0; index < files.length; index++) { |
121 | const item = files[index]; |
122 | sizes.push(item.size); |
123 | allReadFile.push(readFile(item)); |
124 | } |
125 | // 获取最大尺寸检验 |
126 | const maxSize = Math.max.apply(null, sizes); |
127 | if (maxSize > 1024 * 1024 * this.maxFileSize) { |
128 | this.$message({ |
129 | message: `图片不得大于${this.maxFileSize}M`, |
130 | type: "warning", |
131 | duration: 2000 |
132 | }); |
133 | return; |
134 | } |
135 | // 读取所有文件为base64数据 |
136 | const base64List = await Promise.all(allReadFile); |
137 | this.imgSrcList = [...this.imgSrcList, ...base64List]; |
138 | } catch (error) { |
139 | console.log(error); |
140 | } |
141 | }, |
142 | |
143 | // 确定上传 |
144 | async handleSubmit() { |
145 | if (!this.imgSrcList.length) { |
146 | this.$message({ |
147 | message: "请上传图片!", |
148 | type: "error" |
149 | }); |
150 | return; |
151 | } |
152 | // 添加页面loading |
153 | const loadingInstance = Loading.service({ |
154 | fullscreen: true, |
155 | text: "上传中..." |
156 | }); |
157 | // 添加按钮loading |
158 | this.loading = true; |
159 | try { |
160 | // 所有blob文件 |
161 | const blobFiles = []; |
162 | // 所有上传图片请求 |
163 | const allRequest = []; |
164 | // Base64 数据转成blob数据 |
165 | this.imgSrcList.forEach(item => { |
166 | const blobFile = Base64ToBlob(item); |
167 | blobFiles.push(blobFile); |
168 | }); |
169 | |
170 | // 添加请求 |
171 | blobFiles.forEach(item => { |
172 | allRequest.push(uploadImage(item)); |
173 | }); |
174 | // 执行请求拿到结果 |
175 | const urlList = await Promise.all(allRequest); |
176 | // 分发事件 |
177 | this.$emit("success", urlList); |
178 | } catch (error) { |
179 | console.log(error, error); |
180 | } |
181 | // 停止loading关闭弹窗 |
182 | this.loading = false; |
183 | this.dialogVisible = false; |
184 | loadingInstance.close(); |
185 | }, |
186 | |
187 | // 删除图片 |
188 | deleteImage(index) { |
189 | this.imgSrcList.splice(index, 1); |
190 | } |
191 | } |
192 | }; |
193 | </script> |
194 | |
195 | <style rel="stylesheet/scss" lang="scss" scoped> |
196 | .btn-box { |
197 | text-align: right !important; |
198 | } |
199 | |
200 | .img-box { |
201 | position: relative; |
202 | display: inline-block; |
203 | width: 120px; |
204 | margin-right: 10px; |
205 | margin-bottom: 10px; |
206 | text-align: center; |
207 | .img { |
208 | width: 100%; |
209 | } |
210 | .delete-image { |
211 | display: none; |
212 | .el-icon-delete-solid { |
213 | width: 40px; |
214 | height: 40px; |
215 | line-height: 40px; |
216 | position: absolute; |
217 | top: 50%; |
218 | left: 50%; |
219 | transform: translate(-50%, -50%); |
220 | font-size: 20px; |
221 | color: #000; |
222 | font-weight: 900; |
223 | } |
224 | } |
225 | |
226 | &:hover { |
227 | .delete-image { |
228 | cursor: pointer; |
229 | width: 100%; |
230 | height: 100%; |
231 | position: absolute; |
232 | background-color: rgba(255, 255, 255, 0.6); |
233 | display: inline-block; |
234 | } |
235 | } |
236 | } |
237 | .uploadImg-box { |
238 | width: 100%; |
239 | height: 40px; |
240 | margin: 0 auto; |
241 | border-radius: 6px; |
242 | position: relative; |
243 | margin: 20px 0; |
244 | .img-input { |
245 | display: none; |
246 | } |
247 | .btn { |
248 | position: absolute; |
249 | left: 50%; |
250 | top: 50%; |
251 | transform: translate(-50%, -50%); |
252 | } |
253 | } |
254 | </style> |
2.使用
1 | <template> |
2 | <div> |
3 | <multiple-upload @success="handleImageSuccess" /> |
4 | </div> |
5 | </template> |
6 | |
7 | <script> |
8 | import MultipleUpload from "@/components/MultipleUpload"; |
9 | |
10 | export default { |
11 | name: "AppForm", |
12 | components: { |
13 | MultipleUpload |
14 | }, |
15 | methods: { |
16 | handleImageSuccess(urlList) { |
17 | console.log(urlList); |
18 | } |
19 | } |
20 | }; |
21 | </script> |
3.补充 src/utils/upload.js 文件 readFile
方法
1 | /** |
2 | * |
3 | * @param {file} file 源文件 |
4 | * @desc 读取图片文件为base64文件格式 |
5 | * @retutn 返回base64文件 |
6 | */ |
7 | export const readFile = file => { |
8 | return new Promise((resolve, reject) => { |
9 | const reader = new FileReader(); |
10 | reader.onload = e => { |
11 | const data = e.target.result; |
12 | resolve(data); |
13 | }; |
14 | reader.onerror = () => { |
15 | const err = new Error("读取图片失败"); |
16 | reject(err.message); |
17 | }; |
18 | |
19 | reader.readAsDataURL(file); |
20 | }); |
21 | }; |
4.补充 src/utils/cos 文件 Base64ToBlob
方法
1 | // base64转换成file文件 |
2 | export function Base64ToBlob(urlData) { |
3 | // 去掉url的头,并转换为byte |
4 | const bytes = window.atob(urlData.split(",")[1]); |
5 | |
6 | // 处理异常,将ascii码小于0的转换为大于0 |
7 | const ab = new ArrayBuffer(bytes.length); |
8 | const ia = new Uint8Array(ab); |
9 | for (let i = 0; i < bytes.length; i++) { |
10 | ia[i] = bytes.charCodeAt(i); |
11 | } |
12 | return new Blob([ab], { |
13 | type: "image/png" |
14 | }); |
15 | } |
5.使用效果