效果图:
组件代码:
<template>
<div>
<div class="components-upload-box">
<draggable :list="imgList" v-bind="$attrs" :set-data="setData" class="item-box">
<div class="item" v-for="(item, index) in imgList">
<img :src="item" v-if="inspectionType(item) == 'img'">
<svg-icon v-else class="icon" :icon-class="inspectionType(item)" />
<div class="btn">
<div class="edit" @click="componentsUploadEdit(index)">替换</div>|<div class="del"
@click="componentsUploadDel(index)">删除</div>
</div>
</div>
</draggable>
<div class="item add" @click="addFileBtn">
<i class="el-icon-circle-plus-outline" />
添加文件
</div>
</div>
<!-- 《《《《选择/上传文件弹窗 -->
<el-dialog class="dialogUploadVisible" title="选择文件" :visible.sync="dialogUploadVisible" width="950px">
<div class="l-r">
<div class="left">
<div class="title">分组名称</div>
<div :class="fileType=='all'?'item on':'item'" @click="edit_fileType('all')">
<svg-icon class="icon" icon-class="all-file" />全部
</div>
<div :class="fileType=='image'?'item on':'item'" @click="edit_fileType('image')">
<svg-icon class="icon" icon-class="image" />图片
</div>
<div :class="fileType=='video'?'item on':'item'" @click="edit_fileType('video')">
<svg-icon class="icon" icon-class="video" />视频
</div>
<div :class="fileType=='audio'?'item on':'item'" @click="edit_fileType('audio')">
<svg-icon class="icon" icon-class="audio" />音频
</div>
<div :class="fileType=='file'?'item on':'item'" @click="edit_fileType('file')">
<svg-icon class="icon" icon-class="file" />文件
</div>
<div :class="fileType=='package'?'item on':'item'" @click="edit_fileType('package')">
<svg-icon class="icon" icon-class="package" />压缩包
</div>
</div>
<div class="right">
<div class="top">
<div>全部</div>
<el-button type="primary">上传<i class="el-icon-upload el-icon--right"></i></el-button>
</div>
<div class="file-list-box">
<div class="file-list">
<template v-for="(item, index) in testImg">
<a href="javascript:void(0)" title="图片名称"
:class="inspectCheckedFile(item.id)?'file-item on':'file-item'" @click="clickFile(item)">
<img v-if="item.file == 'img'" class="img" :src="item.url">
<svg-icon v-else-if="item.file == 'video'" class="icon" icon-class="shipin" />
<svg-icon v-else-if="item.file == 'audio'" class="icon" icon-class="yinpin" />
<svg-icon v-else-if="item.file == 'file'" class="icon" icon-class="wenjian" />
<svg-icon v-else-if="item.file == 'package'" class="icon" icon-class="yasuobao" />
<svg-icon v-else class="icon" icon-class="weizhi" />
<span>{{item.name}}</span>
<i class="el-icon-success" />
</a>
</template>
</div>
<el-pagination class="file-page" background :current-page="2" :page-size="10"
layout="total, prev, pager, next, jumper" :total="400">
</el-pagination>
</div>
</div>
</div>
<div class="checkedFile-box">
<div class="title">已选文件:</div>
<div class="itemList">
<a href="javascript:void(0)" :title="item.name" class="item" v-for="(item, index) in checkedFile">
<img v-if="item.file == 'img'" class="img" :src="item.url">
<svg-icon v-else-if="item.file == 'video'" class="icon" icon-class="shipin" />
<svg-icon v-else-if="item.file == 'audio'" class="icon" icon-class="yinpin" />
<svg-icon v-else-if="item.file == 'file'" class="icon" icon-class="wenjian" />
<svg-icon v-else-if="item.file == 'package'" class="icon" icon-class="yasuobao" />
<svg-icon v-else class="icon" icon-class="weizhi" />
<span>{{item.name}}</span>
<i class="el-icon-error" @click="clickFile(item)" />
</a>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogUploadVisible = false">取 消</el-button>
<el-button type="primary" @click="uploadFileOk">确 定</el-button>
</div>
</el-dialog>
<!-- 选择/上传文件弹窗 》》》》 -->
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'UploadFile',
components: {
draggable
},
props: {
only: { //允许选择的文件类型['img', 'video', 'audio', 'file', 'package']
type: Array,
default: () => {
return ['img', 'video', 'audio', 'file', 'package']
}
},
num: { //默认允许上传9个
type: Number,
default: 9
},
imgLists: { //已有的文件
type: Array,
default: () => {
return []
}
}
},
data() {
return {
editIndex: '-1', //替换第几个
dialogUploadVisible: false,
fileType: 'all',
testImg: [{
id: 1,
name: '图片名称',
file: 'img',
url: 'http://www.news.cn/photo/2021-11/23/1128089521_16375988814621n.jpg'
}, {
id: 2,
name: '图片名称',
file: 'img',
url: 'http://www.news.cn/photo/2021-11/23/1128089521_16375988814971n.jpg'
}, {
id: 3,
name: '图片名称',
file: 'img',
url: 'http://www.news.cn/photo/2021-11/23/1128089521_16375988815281n.jpg'
}, {
id: 4,
name: '视频名称',
file: 'video',
url: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4'
}, {
id: 5,
name: '音频名称',
file: 'audio',
url: 'http://em.sycdn.kuwo.cn/3e60a6c24cd470707a4095449165b22e/619c9cee/resource/n3/30/66/2205407643.mp3'
}, {
id: 6,
name: '文件名称',
file: 'file',
url: 'http://baidu.com/file/test.txt'
}, {
id: 7,
name: '压缩包名称',
file: 'package',
url: 'http://baidu.com/file/test.zip'
}],
checkedFile: [], //选中的文件
}
},
computed: {
inspectCheckedFile: function() { // 判断是否已选
return function(id) {
return this.checkedFile.some((item) => {
return item.id === id;
});
}
},
inspectionType() { //判断Url文件类型
return function(fileUrl) {
let arr = fileUrl.split('.');
// 文件类型
let typeList = [{
name: 'img',
list: ['bmp', 'jpg', 'png', 'tga', 'gif', 'svg', 'psd', 'pcd', 'ai', 'raw', 'wmf', 'webp', 'swf', 'tiff']
}, {
name: 'shipin',
list: ['avi', 'wmv', 'mpg', 'mpeg', 'vob', '3gb', 'mp4', 'mkv', 'rmvb', 'mov', 'flv', 'mvb']
}, {
name: 'wenjian',
list: ['txt', 'doc', 'docx', 'xlsx', 'xls', 'log', 'pdf', 'ppt', 'tmdx', 'tmd', 'pmd', 'pmdx']
}, {
name: 'yinpin',
list: ['cda', 'wav', 'mp3', 'wma', 'ra', 'midi', 'ogg', 'ape', 'flac', 'aac', 'rma', 'asf', 'mid', 'rmi', 'xmi', 'vqf', 'tvq', 'mod', 'ope', 'aiff', 'au']
}, {
name: 'yasuobao',
list: ['rar', 'zip', 'rar4']
}]
if (arr.length > 1) {
let type = arr[arr.length - 1];
let tina = typeList.filter((p) => {
return p.list.indexOf(type) > -1
});
if (tina.length) {
return tina[0].name
} else {
return 'weizhi'
}
} else {
return 'weizhi'
}
}
}
},
created() {
this.imgList = this.imgLists;
},
methods: {
uploadFileOk(){ //弹窗确认
const that = this;
if(!that.checkedFile.length){
that.$message({
message: '请选择文件',
type: 'warning'
});
return false;
}
if(that.editIndex > -1){
that.imgList.splice(that.editIndex, 1, that.checkedFile[0].url)
}else{
that.imgList = [...that.imgList, ...that.checkedFile.map(item => {return item.url})]
}
that.dialogUploadVisible = false;
that.$emit('getFileData', that.imgList); //数据传递给父级
},
addFileBtn(){ //弹窗
this.dialogUploadVisible = true;
this.checkedFile = [];
this.editIndex = -1;
},
componentsUploadDel(index) { //删除
this.imgList.splice(index, 1)
},
componentsUploadEdit(index) { //替换
this.editIndex = index;
this.dialogUploadVisible = true;
this.checkedFile = [];
},
setData(dataTransfer) { //官方文档给的,避免Firefox bug
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
},
edit_fileType(type) { //切换分组
this.fileType = type;
},
clickFile(file) { //点击选中/取消选中文件
const that = this;
let checkedFile = that.checkedFile
if(checkedFile.length >= that.num){
that.$message({
message: '最多选择'+that.num+'个',
type: 'warning'
});
return false;
}
let tina = checkedFile.filter((p) => {
return p.id == file.id;
});
let index = checkedFile.indexOf(tina[0]);
if (index > -1) {
that.checkedFile.splice(index, 1);
} else {
//判断是否允许上传该类型文件
if (that.only.indexOf(file.file) === -1) {
that.$message.error('不允许选择该类型文件');
return false;
}
// 替换用的,删除原先勾选
if(that.editIndex > -1){
that.checkedFile = [];
}
that.checkedFile.push(file)
}
}
}
}
</script>
<style lang="scss">
.components-upload-box {
width: 100%;
display: flex;
flex-wrap: wrap;
.item-box {
display: flex;
flex-wrap: wrap;
}
.item {
width: 100px;
height: 100px;
border-radius: 5px;
border: 1px solid #dcdfe6;
background-color: #fafafa;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
.icon {
font-size: 100px;
}
.btn {
position: absolute;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 25px;
text-align: center;
color: #ffffff;
border-radius: 5px;
font-size: 12px;
.edit {
margin-right: 10px;
}
.del {
margin-left: 10px;
}
}
&>img {
width: 100%;
height: 100%;
object-fit: cover;
}
.el-icon-circle-plus-outline {
font-size: 30px;
margin-bottom: 10px;
}
}
.add {
border: 1px dashed #dcdfe6;
font-size: 12px;
}
}
.dialogUploadVisible {
margin: 0;
padding: 0;
.el-dialog__body {
padding: 0 20px;
}
.l-r {
display: flex;
justify-content: space-between;
.left {
width: 200px;
border: 1px solid #e3e3e3;
border-radius: 5px;
height: 410px;
overflow: auto;
.title {
background-color: #d9dbdc;
height: 45px;
line-height: 45px;
padding: 0 20px;
}
.on {
background-color: #e7e9ea;
}
.item {
cursor: pointer;
height: 45px;
line-height: 45px;
padding: 0 20px;
display: flex;
align-items: center;
.icon {
font-size: 25px;
margin-right: 10px;
}
&:hover {
background-color: #e7e9ea;
}
}
}
.right {
width: 700px;
.top {
display: flex;
align-items: center;
justify-content: space-between;
color: #000000;
}
.file-list-box {
border: 1px solid #e3e3e3;
border-radius: 5px;
padding: 20px;
padding-right: 10px;
margin-top: 10px;
.file-list {
display: flex;
flex-wrap: wrap;
height: 270px;
.file-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 123px;
height: 123px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
position: relative;
.icon {
font-size: 70px;
}
.img {
width: 70px;
height: 70px;
object-fit: cover;
}
&>span {
margin-top: 10px;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100px;
text-align: center;
}
.el-icon-success {
position: absolute;
top: 5px;
left: 5px;
font-size: 20px;
color: #409eff;
display: none;
}
}
.on {
border: 1px solid #409eff;
border-radius: 5px;
.el-icon-success {
display: block;
}
}
}
}
}
.file-page {
margin-top: 20px;
}
}
.checkedFile-box {
min-height: 141px;
border: 1px solid #e3e3e3;
border-radius: 5px;
margin-top: 10px;
padding: 10px;
.title {
color: #000000;
}
.itemList {
display: flex;
flex-wrap: wrap;
align-items: center;
.item {
margin-top: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
margin-right: 10px;
position: relative;
.icon {
font-size: 70px;
}
.el-icon-error {
position: absolute;
top: -10px;
right: -10px;
font-size: 20px;
}
.img {
width: 70px;
height: 70px;
object-fit: cover;
}
&>span {
margin-top: 10px;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 80px;
text-align: center;
}
.el-icon-success {
position: absolute;
top: 5px;
left: 5px;
font-size: 20px;
color: #409eff;
display: none;
}
}
}
}
}
</style>
我在main.js引入并注册组件:
import UploadFile from "@/components/UploadFile/index.vue"; //文件上传
Vue.component('UploadFile', UploadFile); //注册组件
使用:
<template>
<UploadFile :imgLists='imgList' @getFileData="getFileData"></UploadFile>
</div>
</template>
<script>
export default {
data() {
return {
imgList: [ //默认已经有的图片
'http://www.news.cn/photo/2021-11/23/1128090597_16376363492121n.jpg',
'http://www.news.cn/photo/2021-11/23/1128090597_16376363492911n.mp4',
'http://www.news.cn/photo/2021-11/23/1128090597_16376363493681n.cs',
'http://www.news.cn/photo/2021-11/23/1128090597_16376363493681n.zip'
]
}
},
methods: {
getFileData(e){ //子组件传到父组件的图片数据
console.log(e)
}
}
}
</script>
<style>
</style>