前言

本专栏将记录 Element UI 以及 Element Plus 的使用,以便提高开发速度

ElementUi官网

ElementPlus官网

引用 Element

Vue2版用的是ElementUi,一般都是在main.js中直接引入

1
2
3
4
5
6
npm i element-ui -S

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)

Vue3版用的是ElementPlus,不过一般都配合自动导入

1
2
3
4
5
6
7
8
9
10
11
12
 npm install element-plus --save

// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

快速开始

在前面已经知道如何去安装了,接下来搭建环境,采用下面2套模板去做实验,vue3模板已经实现了自动按需导入

创建一个 Vue 应用

环境:Node16 和 Node18 (nvm 来管理)

开发工具: VSCode

代码: 直接从本人的 Github 上拉取

::: info
Vue3 模板

1
git clone -b test https://github.com/luckyNwa6/luckyVue3Template.git

Vue2 模板

1
git clone -b test https://github.com/luckyNwa6/luckyVue2Template.git

弹框组件封装

vue2项目,封装的第一版弹框组件使用基于element-ui

在vue同级创建components/dialog文件夹

里面放paramDialog.vue

父组件引入 dialogVisible要是改变量名,这个也记得改

弹框组件

代码如下:

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
<ParamDialog :title="title" :dialogVisible.sync="dialogVisible" @dialog-closed="handleDialogClosed" :dataRe="dataFa" />

import ParamDialog from './components/dialog/paramDialog.vue';

components: {ParamDialog},
data() {
return {
// 弹出框
dialogVisible: false,
//要传给弹框的数据下面3个
dataFa: null,
title: '参数模板'
}
},
methods: {
handleView(row) { //可以是修改也可以是新增
console.log('打开dialog');
this.dialogVisible = true;
//下面看着传
this.dataFa = row;
this.dataFa = { ...this.dataFa, time: this.queryParams.time, };
},
//关闭弹框,子传父的自定义事件监听
handleDialogClosed(closed) {
this.dialogVisible = false;
// 在这里处理关闭状态
console.log('子组件弹框关闭状态:', closed);
},
}

子组件

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
<!--参数模板弹框-->
<template>
<el-dialog :title="title" :data="list" width="50%" :visible.sync="dialogVisible" :before-close="handleClose">
<div>1</div>
</el-dialog>
</template>

<script>
export default {
props: {
dialogVisible: {
type: Boolean,
default: false
},
dataRe: {
type: Object,
default: {}
},

title: {
type: String
}
},
data() {
return {
queryParams: {
pageNo: 1,
pageSize: 15,
time: '',
deviceCode: '',
stationId: null,
type: null
},
// 遮罩层
loading: true,
// 表格
list: []
};
},
watch: {
dialogVisible(newValue) {
if (newValue) {
//说明是打开的,调用方法
this.init();
}
}
},
computed: {},
methods: {
//关闭弹框
handleClose() {
this.$emit('dialog-closed', this.dialogVisible);
},
init() {
this.loading = true;
//处理父组件传过来的对象
this.queryParams.time = this.dataRe.time;
this.queryParams.stationId = this.dataRe.stationId;
this.queryParams.deviceCode = this.dataRe.deviceCode;
this.queryParams.type = this.dataRe.type;
var stationNameC = this.dataRe.stationName;
var timeC = this.dataRe.time;
this.loading = true;
console.log('🚀 ~ getList ~ this.queryParams:', this.queryParams);

this.loading = false;
}
}
};
</script>
<style lang="scss" >
</style>

上传功能实现

本章节将实现一个上传功能,结合项目的真正需求

原生上传

由于需要校验压缩包中的内容

1
npm  i  jszip

简单上传页

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
<template>
<div>
<input ref="fileInput" type="file" multiple @change="handleFileChange" style="display: none" />
<button @click="openFileInput">选择文件</button>
<button @click="resetFileInput">重置</button>
</div>
</template>

<script setup>
import { ref } from 'vue'
import JSZip from 'jszip'

const fileInput = ref(null) // 创建一个 ref 引用

const openFileInput = () => {
fileInput.value.click() // 打开文件选择对话框
}

const handleFileChange = (event) => {
const files = event.target.files // 获取选中的文件列表
console.log(files)

// 遍历文件列表
for (let i = 0; i < files.length; i++) {
const file = files[i]
const reader = new FileReader()
reader.onload = (e) => {
const zipData = e.target.result // 获取ZIP文件的内容

// 使用JSZip解压缩ZIP文件
JSZip.loadAsync(zipData).then((zip) => {
// 处理解压缩后的内容
zip.forEach((relativePath, zipEntry) => {
if (!zipEntry.dir) {
// 如果是文件而不是目录
zipEntry.async('string').then((fileContent) => {
// 处理文件内容
console.log(`文件名:${zipEntry.name}`)
// console.log(`文件内容:${fileContent}`)
})
}
})
})
}

reader.readAsArrayBuffer(file) // 以ArrayBuffer格式读取文件内容
}
}

const resetFileInput = () => {
fileInput.value.value = '' // 通过 .value 访问和修改引用的值
}
</script>

<style scoped></style>

在上传的过程中,如果第一次上传a文件,第二次再上传会没有效果,因为浏览器的缓存,需要去引用上传实例里的输入框设为空才能继续,由于业务要求,需要点击菜单下拉框里的按钮触发选择文件夹

1
2
uploadRef.value.$el.querySelector("input").value = ""; //可上传同名
uploadRef.value.$el.querySelector("input").click(); //触发点击选择文件框

业务过程:

原是列表上有个上传按钮—>点击弹框显示上传弹框—>在这里上传,由于一旦关闭弹框就要重新传而且影响用户其他操作—>采用新页面给它异步上传就不影响用户操作别的东西

新上传页面–>文件有它的格式要求–>用了部分的elementplus的上传组件(正常它的上传按钮都是在这个组件插槽里的,我这自定义按钮放外面所以都用不了该组件的方法如提交)–>验证zip里的格式–>进度条3种状态

采用组合式写法

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
<template>
<div class="upload-container">
<div style="text-align: center; width: 100%; padding: 20px 0"><h2>导入模型</h2></div>
<el-card style="width: 90%; margin: 0 auto; padding: 10px 25px; height: 780px; overflow: auto">
<p class="font-bold text-[14px]">上传目标路径: 全部文件</p>
<p class="text-[14px] mt-4 mb-4">支持以下类型和格式的模型导入。</p>

<el-table :data="tableData" border style="width: 35%">
<el-table-column prop="typeL" label="类型" align="center">
<template #default="scope"> <span style="font-weight: 600">格式</span> </template></el-table-column
>
<el-table-column prop="2DL" align="center">
<template #header>
<span>
二维地图
<el-tooltip placement="right" effect="light" popper-class="image-tooltip">
<template #content>
<img :src="image2d" />
</template>
<i-ep-info-filled />
</el-tooltip>
</span>
</template>
<template #default="scope"> <span style="font-weight: 600">png</span></template>
</el-table-column>
<el-table-column prop="DYL" align="center">
<template #header>
<span>
点云
<el-tooltip placement="right" effect="light" popper-class="image-tooltip">
<template #content>
<img :src="imageDY" />
</template>
<i-ep-info-filled />
</el-tooltip>
</span>
</template>
<template #default="scope"> <span style="font-weight: 600">pnts</span> </template>
</el-table-column>
<el-table-column prop="QXL" align="center">
<template #header>
<span>
倾斜模型
<el-tooltip placement="right" effect="light" popper-class="image-tooltip">
<template #content>
<img :src="imageQX" />
</template>
<i-ep-info-filled />
</el-tooltip>
</span>
</template>
<template #default="scope">
<span style="font-weight: 600">b3dm</span>
</template>
</el-table-column>

<el-table-column prop="QJL" align="center">
<template #header>
<span> 全景照片 </span>
</template>
<template #default="scope"> <span style="font-weight: 600">jpeg &nbsp jpg</span> </template>
</el-table-column>
</el-table>

<div class="text-[14px] h-20">
<p class="mt-4">除了全景照片可以直接上传文件(格式:jpg, jpeg)以外,其余类型的文件请将单个模型按照对应格式</p>
<p class="mt-3">的文件结构压缩成为zip包(小于 30GB)后,再进行上传。</p>
</div>
<el-dropdown class="mb-4">
<el-button type="primary" :disabled="btDisable"
><i-ep-upload /> 导入<i-ep-arrow-down style="margin-left: 5px" />
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openEditDialog('QX')" :disabled="btDisable">倾斜模型</el-dropdown-item>
<el-dropdown-item @click="openEditDialog('DY')" :disabled="btDisable">点云</el-dropdown-item>
<el-dropdown-item @click="openEditDialog('2D')" :disabled="btDisable">二维地图</el-dropdown-item>
<el-dropdown-item @click="openEditDialog('QJ')" :disabled="btDisable">全景照片</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-upload
ref="uploadRef"
:action="addApi"
:headers="headers"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:show-file-list="false"
:on-change="handleChange"
:auto-upload="false"
v-show="false"
multiple
>
</el-upload>
<div class="mb-4">
<i-ep-warning-filled style="color: #ff9900; margin-left: 5px" />
<span style="color: #ff9900; margin-left: 5px">上传过程中请勿关闭浏览器或退出当前页面!</span>
</div>
<el-table
:data="tableFileData"
border
class="mb-4"
highlight-current-row
v-loading="loading"
height="290px"
ref="tableRef"
>
<el-table-column prop="file_name" label="文件名称" width="380" align="center" show-overflow-tooltip />
<el-table-column label="上传进度" align="center" show-overflow-tooltip>
<template #default="scope">
<div v-if="scope.row.showProgress">
<el-progress
:percentage="scope.row.uploadProgress"
:color="customColorMethod(scope.row.uploadProgress, scope.row.state)"
/>
</div>
<div v-else-if="scope.row.showProgress === 0 && scope.row.file_tip === ''">
<span></span>
</div>
<div v-else>
<span>{{ scope.row.file_tip }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="state" label="上传状态" width="180" align="center" show-overflow-tooltip>
<template #default="scope">
<span>{{ fileUploadOption.find(item => item.value == scope.row.state)?.label }}</span>
</template>
</el-table-column>
<el-table-column prop="state" label="模型类型" width="180" align="center" show-overflow-tooltip>
<template #default="scope">
<span>{{ optionData.modelTypeOption.find(item => item.value == scope.row.model_type)?.label }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" width="180">
<template #default="scope">
<el-button type="danger" v-if="!scope.row.showDel" link @click.stop="handleDelete(scope.row)"
>取消</el-button
>
</template>
</el-table-column>
</el-table>
<div>
<el-button type="primary" @click="btDis" :disabled="btDisable">{{ uploadBtnName }}</el-button>
</div>
</el-card>
</div>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import _ from 'lodash';
import { ElMessage } from 'element-plus';
import image2d from '@/assets/model-import/image2d.png';
import imageDY from '@/assets/model-import/imageDY.png';
import imageQX from '@/assets/model-import/imageQX.png';
import { useUserStoreHook } from '@/store/modules/user';
import JSZip from 'jszip';
import axios from 'axios';
import optionData from '@/utils/option-data';
//文件上传状态
const fileUploadOption = [
{
value: 3,
label: '待上传'
},
{
value: 2,
label: '正在上传'
},
{
value: 1,
label: '上传成功'
},
{
value: 0,
label: '上传失败'
},
{
value: 4,
label: ''
}
];
//占用一行,不用改
const tableData = [
{
typeL: '格式',
QXL: 'zip',
DYL: 'zip',
'2DL': 'zip',
QJL: 'jpeg,jpg'
}
];
//上传的文件列表展示
const tableFileData = ref([]);
const loading = ref(false);
const uploadRef = ref(null);
const tableRef = ref('tableRef');
const uploadBtnName = ref('上传');
const addApi = ref(null);
const userStore = useUserStoreHook();
const headers = reactive({ 'X-Auth-Token': userStore.token });
const typeModel = ref(null);
const btDisable = ref(false); //上传和导入按钮是否禁用
const fileList = ref([]);
const fileType3 = ['zip']; //倾斜、点云、二维都是zip格式
const fileTypeQJ = ['jpeg', 'jpg']; //全景照片

const openEditDialog = type => {
console.log('当前要上传的类型是:', type);
typeModel.value = type; //这里必须赋值,下面才知道类型
console.log('打开了选中文件框');
uploadRef.value.$el.querySelector('input').value = '';
uploadRef.value.$el.querySelector('input').click(); //触发点击选择文件框
};

//下面是文件上传生命周期-----------------------------------------------------------------------------
const fileSize = 30;
const isTypeOk = ref();
const handleChange = async (file, updatedFileList) => {
console.log('=====================触发了handleChange的生命周期开始!!!');

file.modelType = typeModel.value;
if (file.name.length > 23) {
ElMessage.warning(`文件名过长,请限制20字内!`);
return;
}
const isLt = file.size / 1024 / 1024 / 1024 < fileSize;
if (!isLt) {
ElMessage.warning(`上传文件大小不能超过 ${fileSize} GB!`);
return false;
}

const ob1 = fileList.value.find(obj => obj.name === file.name);
const ob2 = tableFileData.value.find(obj => obj.file_name === file.name);
if (ob1 || ob2) {
ElMessage.warning(`文件已在列表中!`);
return;
}
let fileExtension = '';
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1);
console.log('🚀 ~文件扩展后缀是:', fileExtension);
}
isTypeOk.value = (typeModel.value === 'QJ' ? fileTypeQJ : fileType3).some(type => {
if (fileExtension && fileExtension.toUpperCase().indexOf(type.toUpperCase()) > -1) return true;
return false;
});
console.log('🚀 ~ handleChange ~ file:', file);
if (typeModel.value !== 'QJ') {
console.log('继续验证zip');
try {
const res = await unzipAndReadFiles(file);
console.log('🚀 ~ handleChange ~ 解压回调:', res);
isTypeOk.value = res;
} catch (error) {
isTypeOk.value = false; //捕获里面的异常
}
}

if (!isTypeOk.value) {
//格式有问题,加入列表中,不加入上传列表
console.log('格式有问题,加入列表中,不加入上传列表');
console.log(file.name);
tableFileData.value.push({
file_name: file.name,
showProgress: 0,
file_tip: '不支持的文件结构,请按照上传模型的结构要求上传文件。',
state: 4,
model_type: typeModel.value
});
} else {
fileList.value.push(file);
tableFileData.value.push({
file_name: file.name,
showProgress: 0,
state: 3,
uploadProgress: 0,
model_type: typeModel.value
});
console.log('通过格式验证');
console.log('🚀 ~ handleChange ~ updatedFileList:', updatedFileList);
console.log('🚀 ~ handleChange ~ 要上传的文件列表:', fileList.value);
}

console.log('=====================触发了handleChange的生命周期结束!!!');
};

// 上传前校检格式和大小
const handleBeforeUpload = file => {
console.log('触发了上传前的校验handleBeforeUpload');
return true;
};
const config = {
headers: {
'X-Auth-Token': userStore.token
}
};
//手动上传
const uploadFile = async () => {
console.log('🚀 ~ 手动上传时候uploadFile ~ fileList.value:', fileList.value);
if (!fileList.value.length) {
ElMessage.warning('请先导入模型!');
return;
}

btDisable.value = true;
uploadBtnName.value = '正在上传';
let arr = _.cloneDeep(fileList.value);
let flagPro = false; //防止进度条上传成功还99
for (let i = 0; i < arr.length; i++) {
const file = arr[i];
console.log('🚀 ~ uploadFile ~ file:', file);

// 使用FormData上传
const formData = new FormData();
formData.append('file', file.raw, file.name);

const index = tableFileData.value.findIndex(obj => obj.file_name === file.name); //在列表中删除提示那种没上传到服务器
tableFileData.value[index].state = 2;
tableFileData.value[index].showProgress = 1;
tableFileData.value[index].uploadProgress = 0; //进度条
try {
const response = await axios.post(
import.meta.env.VITE_APP_BASE_API +
`/achievement/api/v1/model/${userStore.userData.workspace_id}/uploadFile/${file.modelType}`,
formData,
{
...config,
onUploadProgress: progressEvent => {
// 计算进度百分比
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
if (percentCompleted === 100 && !flagPro) {
console.log('这是前端的100进度条改99等后端返回0才100');
tableFileData.value[index].uploadProgress = 99;
} else if (flagPro) {
tableFileData.value[index].uploadProgress = 100;
} else {
tableFileData.value[index].uploadProgress = percentCompleted; //更新实时进度条
}
}
}
);

if (response.data.code === 0) {
console.log('文件上传成功', response.data);
flagPro = true;
tableFileData.value[index].state = 1; //状态改成功
tableFileData.value[index].showDel = 1; //禁用删除
const index2 = fileList.value.findIndex(obj => obj.name === file.name); //文件列表删除,这样下次上传就没有它了
if (index2 !== -1) {
fileList.value.splice(index2, 1);
}
console.log('后端返回0了,进度条该100了');
tableFileData.value[index].uploadProgress = 100; //进度条拉满
} else if (response.data.code === -1) {
flagPro = false;
tableFileData.value[index].uploadProgress = 99;
console.log('上传失败了,后端说的');
tableFileData.value[index].state = 0; //状态改失败
}
} catch (error) {
tableFileData.value[index].state = 0; //状态改失败
tableFileData.value[index].uploadProgress = 99;
flagPro = false;
ElMessage.warning(`网络异常,文件上传失败!`);
console.error('文件上传失败', error);
}
}
};

const btDis = async () => {
try {
const res = await uploadFile();
console.log('🚀 ~上传全部完毕,失败还能继续上传的列表:', fileList.value);
// 所有文件上传完成
btDisable.value = false;
uploadBtnName.value = '上传';
} catch (error) {
btDisable.value = false;
uploadBtnName.value = '上传';
}
};

const unzipAndReadFiles = file => {
return new Promise((resolve, reject) => {
console.log(file);
let modelType = file.modelType;
file = file.raw;
const reader = new FileReader();
let tempBoolean = false; //用来存是否满足条件
let tempSum = 0; //临时长度
try {
reader.onload = e => {
const zipData = e.target.result; // 获取ZIP文件的内容
// 使用JSZip解压缩ZIP文件
JSZip.loadAsync(zipData)
.then(zip => {
console.log(zip);
// 处理解压缩后的内容
zip.forEach((relativePath, zipEntry) => {
if (modelType === 'DY' && pathMatchesRuleDY(relativePath)) {
tempSum++;
if (Object.keys(zip.files).length == tempSum) {
//同时满足正则
tempBoolean = true;
}
} else if (modelType === 'QX' && pathMatchesRuleQX(relativePath)) {
tempSum++;
console.log('QX满足压缩格式!!!');
if (Object.keys(zip.files).length == tempSum) {
//同时满足正则
tempBoolean = true;
}
} else if (modelType === '2D' && pathMatchesRule2D(relativePath)) {
console.log('有xyz.png格式!!!');
tempBoolean = true;
}
});
console.log('当前', tempBoolean);
resolve(tempBoolean); // 解压缩完成,将Promise标记为成功,并传递isTypeOk的值
})
.catch(error => {
console.log('这是解压时候遇到的异常');
reject(error); // 将Promise标记为失败,并传递错误信息
});
};

reader.readAsArrayBuffer(file); // 以ArrayBuffer格式读取文件内容
} catch (error) {
console.log('这里是解压外层捕获的异常');
reject(error); // 将Promise标记为失败,并传递错误信息
}
});
};

const pathMatchesRuleDY = path => {
// 根目录下第一个文件名为 "tileset.json"
const pathRegex = /^[^/]+\/tileset\.json/;
// 其他文件必须是 .json 或 .pnts 后缀
const fileRegex = /\.json$|\.pnts$/i;
// 路径以 / 结尾
const trailingSlashRegex = /\/$/;
return pathRegex.test(path) || fileRegex.test(path) || trailingSlashRegex.test(path);
};
const pathMatchesRuleQX = path => {
// 根目录下第一个文件名为 "tileset.json"
const pathRegex = /^[^/]+\/tileset\.json/;
// 其他文件必须是 .json 或 .b3dm后缀
const fileRegex = /\.json$|\.b3dm$/i;
// 路径以 / 结尾
const trailingSlashRegex = /\/$/;
return pathRegex.test(path) || fileRegex.test(path) || trailingSlashRegex.test(path);
};
const pathMatchesRule2D = path => {
//2D规则
const regex = /^[^/]+\/\d+\/\d+\/\d+\.png$/;
return regex.test(path);
};

/**
* 删除
*/
function handleDelete(row) {
ElMessageBox.confirm(`确认后将删除该条记录,且无法进行恢复`, '确认取消?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
console.log('🚀 ~当前行的数据:', row);
const index = tableFileData.value.findIndex(obj => obj.file_name === row.file_name); //在列表中删除提示那种没上传到服务器
console.log('🚀 ~ handleDelete ~ tableFileData.value:', tableFileData.value);
console.log('🚀 ~ tableFileData ~ index:', index);
const index2 = fileList.value.findIndex(obj => obj.name === row.file_name); //文件列表删除
console.log('🚀 ~ handleDelete ~ fileList.value:', fileList.value);
console.log('🚀 ~ fileList ~ index2:', index2);
if (index !== -1) {
tableFileData.value.splice(index, 1);
}
if (index2 !== -1) {
fileList.value.splice(index2, 1);
}

uploadRef.value.$el.querySelector('input').value = '';

ElMessage.success('删除成功');
});
}

//根据进度条百分比获取颜色
const customColorMethod = (percentage, state) => {
if (percentage < 100 && state) {
return '#409eff';
} else if (percentage <= 100 && !state) {
return '#ff3b30';
} else if (percentage == 100) {
return '#4bd863';
}
};
</script>

<style scoped>
.upload-container {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #f9f9f9;
}

.upload-demo {
width: 100%;
margin-bottom: 20px;
border: 1px dashed #d3d3d3;
border-radius: 4px;
padding: 20px;
text-align: center;
}

.el-upload__text em {
color: #409eff;
cursor: pointer;
}
</style>

组件上传

放在弹框中的

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
<template>
<el-dialog
:title="title"
:model-value="visible"
width="600px"
align-center
:close-on-click-modal="false"
@close="closeDialog"
>
<div class="upload-container">
<el-upload
ref="uploadImgRef"
:before-upload="handleBeforeUpload"
:on-change="handleChange"
:on-error="handleError"
:on-remove="handRemove"
:file-list="fileList"
:on-success="handleSuccess"
:action="addApi"
:headers="headers"
drag
multiple
>
<template #default>
<div class="upload-tip-box">
<i-ep-uploadFilled class="el-icon-upload" />
<div class="el-upload__text">点击或拖动文件上传</div>
</div>
<div class="tipB">支持图片格式:JPEG</div>
<div class="tipB">支持视频格式:MP4</div>
</template>
</el-upload>
</div>

<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="closeDialog" v-loading="loading">确 定</el-button>
<el-button @click="closeDialog">取 消</el-button>
</div>
</template>
</el-dialog>
</template>

<script setup>
import { reactive, defineProps, defineEmits, ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { useUserStoreHook } from '@/store/modules/user';

const uploadImgRef = ref('uploadImgRef');

const props = defineProps({
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '标题'
}
});

const emit = defineEmits(['update:visible', 'submit']);
const loading = ref(false);
// 关闭弹窗
function closeDialog() {
fileList.value = [];
uploadImgRef.value.clearFiles();
console.log('关闭弹框的文件列表:', fileList.value);
emit('update:visible', false);
emit('submit');
}
//保存
function handleSubmit() {}
//下面是上传相关
const fileList = ref([]);
const userStore = useUserStoreHook();

const addApi = import.meta.env.VITE_APP_BASE_API + `/media/api/v1/files/${userStore.userData.workspace_id}/uploadFile`;
const headers = reactive({ 'X-Auth-Token': userStore.token });

const existingFileIndex = ref(null);

const handleChange = (file, updatedFileList) => {
console.log('=====================触发了handleChange的生命周期开始!!!');
console.log('🚀 ~ handleChange ~ updatedFileList:', updatedFileList);
console.log('🚀 ~ handleChange ~ file:', file);
// 查找目标元素的全部索引
let indices = [];
for (let i = updatedFileList.length - 1; i >= 0; i--) {
if (updatedFileList[i].name === file.name) {
console.log('name相等!!');
indices.push(i);
}
}
console.log('🚀 ~ handleChange ~ indices:', indices);
// 如果有多个索引,删除第一个索引对应的元素
if (indices.length > 1) {
updatedFileList.splice(indices[0], 1);
}

console.log('🚀 ~ handleChange ~ updatedFileList:', updatedFileList);
fileList.value = updatedFileList;
console.log('=====================触发了handleChange的生命周期结束!!!');
};
//上传失败
const handleError = (file, updatedFileList) => {
ElMessage.error(updatedFileList.name + '上传失败');
console.log('handleError的生命周期!!!');
};
//文件列表移除文件时的钩子
const handRemove = (file, updatedFileList) => {
fileList.value = updatedFileList;
console.log('handRemove的生命周期!!!');
console.log('🚀 ~ 移除后的文件列表:', fileList.value);
};
//上传成功
const handleSuccess = (file, updatedFileList) => {
console.log('🚀 ~ handleSuccess的生命周期!!!', updatedFileList);
console.log(updatedFileList.name + '上传成功');
console.log('🚀 ~ 上传成功后 ~ fileList.value:', fileList.value);
console.log('-----------------------------------------------------');
};
const fileType = ['MP4', 'JPEG'];
const fileSize = 5;

// 上传前校检格式和大小
const handleBeforeUpload = file => {
// 校检文件类型
let fileExtension = '';
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1);
}
const isTypeOk = fileType.some(type => {
if (fileExtension && fileExtension.toUpperCase().indexOf(type.toUpperCase()) > -1) return true;
return false;
});

if (!isTypeOk) {
ElMessage.warning(`文件格式不正确, 请上传${fileType.join('/')}格式文件!`);
return false;
}

console.log('上传检查全部通过!正在上传文件,请稍候...');
return true;
};
</script>
<style scoped lang="scss">
.app-form {
::v-deep {
.el-input-number,
.el-select {
width: 100%;
}
.el-input-number .el-input__inner {
text-align: left;
}
.el-input-number.is-controls-right .el-input__wrapper {
padding-left: 11px;
}
.el-date-editor.el-input,
.el-date-editor.el-input__wrapper {
width: 100%;
}
}
}
.upload-container {
display: flex;
flex-direction: column;
width: 100%;
height: 300px;
}

:deep(.el-upload--picture-card) {
height: 100%;
width: 100%;
}
:deep(.el-upload__text) {
font-size: 13px;
margin-bottom: 13px;
margin-top: -5px;
}

:deep(.el-upload-dragger) {
width: 565px;
background-color: #f9f9f9;
}
:deep(.el-upload-dragger:hover) {
background-color: white;
}
.el-icon-upload {
color: #b7d9fd;
font-size: 70px;
margin-top: -34px;
}
.tipB {
font-size: 13px;
color: #cccccc;
margin-top: 10px;
}
</style>