有没有觉得element-ui的Transfer很不好用的同学?小编用了之后感觉好难用,搜索框不能自定义,而且也不支持正常的搜索,只能过滤当前显示的数据,这有啥用????除非是用滚动加载的形式,一页显示所有的数据,数据量多的时候你眼睛不花的啊?!还是翻页靠谱,翻页只需要解决反向排除的问题就可以了,匹配的数据量少,可是element-ui没有给搜索自定义的slot,用filter-method自定义搜索真的晕,一条数据执行一次,return true则显示,也还是没办法按照关键词搜索加载数据进行选择,于是一拍键盘重写了一个Transfer,支持各种定制自定义,可以自由发挥,上图:
上代码:
//template部分
<template>
<div class="transfer-boxes" v-loading="allLoading">
<div class="left-box common-box">
<p class="left-header common-header">
<el-checkbox :indeterminate="isIndeterminateSource" :disabled="sourceData.length==0" v-model="checkAllSource" @change="sourceAllCheckChange"><slot :row="sourceTitle" name="sourceTitle">{{sourceTitle}}</slot></el-checkbox><span class="total-count">{{sourceCheckedData.length}}/{{sourceData.length}}</span>
</p>
<div class="left-body common-body" v-loading="sourceLoading">
<div class="search-box" v-if="isSearch">
<slot name="search">
<el-input size="mini" v-model="searchFilter" :placeholder="placeholder">
<el-button slot="append" icon="el-icon-search" @click="onSearch(searchFilter)"></el-button>
</el-input>
</slot>
</div>
<div class="load-content">
<div v-if="noDataShow==0" class="no-data-text">暂无数据</div>
<el-checkbox-group v-model="sourceCheckedData" @change="sourceEachCheckedChange">
<el-checkbox v-for="(item , i) in sourceData" :key="i" :label="item" class="check-item" :disabled="item.disabled"><slot :row="item" name="leftLabel">{{item[propName.label]}} ........... {{item[propName.id]}}</slot></el-checkbox>
</el-checkbox-group>
</div>
<div class="common-footer left-foot">
<slot name="left-footer"></slot>
</div>
</div>
</div>
<div class="transfer-button-box">
<div class="move-out"><el-button type="primary" :disabled="targetCheckedData.length==0 || targetData.length==0" @click="targetToSource"><i class="el-icon-arrow-left"></i> {{outBtnText}}</el-button></div>
<div class="move-in"><el-button type="primary" :disabled="sourceCheckedData.length==0 || sourceData.length==0" @click="sourceToTarget">{{inBtnText}} <i class="el-icon-arrow-right"></i></el-button></div>
</div>
<div class="right-box common-box" v-loading="targetLoading">
<p class="right-header common-header">
<el-checkbox :indeterminate="isIndeterminateTarget" :disabled="targetData.length==0" v-model="checkAllTarget" @change="targetAllCheckChange"><slot :row="targetTitle" name="targetTitle">{{targetTitle}}</slot></el-checkbox><span class="total-count">{{targetCheckedData.length}}/{{targetData.length}}</span>
</p>
<div class="right-body common-body">
<div class="right-content">
<el-checkbox-group v-model="targetCheckedData" @change="targetEachCheckedChange">
<el-checkbox v-for="(item ,i) in targetData" :key="i" :label="item" class="check-item"><slot :row="item" name="rightLabel"></slot>{{item[propName.label]}} ........... {{item[propName.id]}}</el-checkbox>
</el-checkbox-group>
</div>
<div class="common-footer right-foot"><slot name="right-footer"></slot></div>
</div>
</div>
</div>
</template>
//js部分
export default {
name:"",
data(){
return {
targetData:[],
checkAllSource: false,
isIndeterminateSource: false,
sourceCheckedData:[],
searchFilter:"",
checkAllTarget: false,
isIndeterminateTarget: false,
targetCheckedData:[],
propName:{
label:'userName',
id:'userId',
disabled:false,
order:'order'
}
}
},
props:{
sourceData:{
type:Array,
default:()=>{
return []
}
},
noDataShow:{
type:Number,
default:1
},
onSearch:{
type:Function,
default:(filter)=>{
}
},
isSearch:{
type:Boolean,
default:true
},
allLoading:{
type:Boolean,
default:false
},
sourceLoading:{
type:Boolean,
default:false
},
targetLoading:{
type:Boolean,
default:false
},
sourceTitle:{
type:String,
default:"所有可选用户"
},
targetTitle:{
type:String,
default:"待绑定用户"
},
placeholder:{
type:String,
default:"请输入用户名或者工号搜索"
},
outBtnText:{
type:String,
default:"移出"
},
inBtnText:{
type:String,
default:"移入"
},
propNames:{
type:Object,
default:()=>{
return {
id:'userId',
label:'userName',
disabled:false,
order:'order'
}
}
},
targetOrderType:{
type:String,
default:'push'//origin/unshift,origin的时候需要在sourceData元素添加order顺序字段才能生效
},
sourceCheckAllChange:{
type:Function,
default:(val)=>{
}
},
sourceGroupCheckedChange:{
type:Function,
default:(vals)=>{
}
},
targetCheckAllChange:{
type:Function,
default:(val)=>{
}
},
targetGroupCheckedChange:{
type:Function,
default:(val)=>{
}
},
targetToSourceThen:{
type:Function,
default:(val)=>{
}
},
sourceToTargetThen:{
type:Function,
default:(val)=>{
}
}
},
components:{
},
mounted(){
this.dataDeal();
},
updated(){
},
methods:{
dataDeal(){
this.propName = Object.assign({},this.propName,this.propNames);
},
targetToSource(){
if(this.checkAllTarget){
this.targetData = [];
}else{
this.targetCheckedData.forEach((item,i)=>{
if(this.targetData.indexOf(item) >-1){
this.targetData.splice(this.targetData.indexOf(item),1)
}
})
}
this.targetCheckedData=[];
this.checkAllTarget = false;
this.isIndeterminateTarget = false;
this.targetToSourceThen(this.targetData);
},
sourceToTarget(){
switch(this.targetOrderType){
case 'origin':
this.targetData = [...this.targetData,...this.sourceCheckedData].sort((a,b)=>{
return a[this.propName.order] - b[this.propName.order];
});
break;
case 'unshift':
this.targetData = [...this.sourceCheckedData,...this.targetData];
break;
default://push/other
this.targetData = [...this.targetData,...this.sourceCheckedData];
}
if(!this.checkAllSource){
this.sourceCheckedData.forEach((checkedItem,i)=>{
if(this.sourceData.indexOf(checkedItem) > -1){
this.sourceData.splice(this.sourceData.indexOf(checkedItem),1);
}
})
}else{
this.sourceData.splice(0,this.sourceData.length);
}
this.sourceCheckedData=[];
this.checkAllSource = false;
this.isIndeterminateSource = false;
this.sourceToTargetThen(this.targetData);
},
sourceAllCheckChange(val) {
this.sourceCheckedData = val ? this.sourceData : [];
this.isIndeterminateSource = false;
this.$emit("sourceCheckAllChange",val);
},
sourceEachCheckedChange(value) {
console.log(value);
let checkedCountSource = value.length;
this.checkAllSource = checkedCountSource === this.sourceData.length;
this.isIndeterminateSource = checkedCountSource > 0 && checkedCountSource < this.sourceData.length;
this.$emit("sourceGroupCheckedChange",value);
},
targetAllCheckChange(val){
this.targetCheckedData = val ? this.targetData : [];
this.isIndeterminateTarget = false;
this.$emit("targetCheckAllChange",val,this.targetCheckedData);
},
targetEachCheckedChange(value){
let checkedCountTarget = value.length;
this.checkAllTarget = checkedCountTarget === this.targetData.length;
this.isIndeterminateTarget = checkedCountTarget > 0 && checkedCountTarget < this.targetData.length;
this.$emit("targetGroupCheckedChange",value)
},
getTargetData(){
return this.targetData;
}
}
}
//CSS
.dialog-container .page-title{
font-size:13px;
color:#409EFF;
}
.dialog-container /deep/ .el-dialog__body{
padding-top:0;
}
.dialog-container /deep/ .el-dialog__footer{
text-align: center;
}
.current-role-box{
padding:12px 0;
text-align: center;
}
.transfer-boxes{
}
.transfer-boxes .common-box{
border: 1px solid #EBEEF5;
border-radius: 4px;
overflow: hidden;
background: #FFF;
display: inline-block;
vertical-align: middle;
width: 350px;
max-height: 100%;
box-sizing: border-box;
position: relative;
}
.transfer-boxes .common-header{
height: 40px;
line-height: 40px;
background: #F5F7FA;
margin: 0;
padding-left: 15px;
border-bottom: 1px solid #EBEEF5;
box-sizing: border-box;
color: #000;
}
.transfer-boxes .total-count{
position: absolute;
right: 15px;
color: #909399;
font-size: 12px;
font-weight: 400;
}
.transfer-boxes .common-body{
padding-top:0.5rem;
height:400px;
}
.transfer-boxes .right-content{
height:344px;
margin: 0;
padding: 6px 0;
list-style: none;
overflow: auto;
box-sizing: border-box;
}
.transfer-boxes .common-footer{
height: 40px;
background: #FFF;
margin: 0;
padding: 6px;
border-top: 1px solid #EBEEF5;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
z-index: 1;
text-align: center;
}
.transfer-boxes .common-body .search-box{
padding:0 1rem 0.5rem 1rem;
}
.common-body .load-content{
height:316px;
margin: 0;
padding: 6px 0;
list-style: none;
overflow: auto;
box-sizing: border-box;
}
.transfer-boxes .check-item{
height: 30px;
line-height: 30px;
padding-left: 15px;
display: block!important;
}
.transfer-boxes .no-data-text{
text-align:center;
color:#C0C4CC;
}
.transfer-boxes .transfer-button-box{
display: inline-block;
vertical-align: middle;
padding: 0 25px;
}
.transfer-button-box .move-out{
text-align: left;
}
.transfer-button-box .move-out button{
margin-right:10px;
margin-bottom:5px;
}
.transfer-button-box .move-in{
text-align: right;
}
.transfer-button-box .move-in button{
margin-left:10px;
margin-top:5px;
}
完整的使用例子如下: 图:
//template部分
<template>
<div class="dialog-container">
<el-dialog
:visible.sync="dialogVisible"
width="900px"
top="5vh"
:modal-append-to-body="modalAppendToBody"
:close-on-click-modal="closeOnClickModal"
:close-on-press-escape="closeOnPressESC"
@close="dialogClose"
>
<div slot="title"><span class="page-title">角色管理/</span><span class="dialog-title">{{dialogData.typeName}}</span></div>
<div
v-loading.fullsreen="fullscreenLoading"
v-loading="dialogloading"
>
<div class="current-role-box">当前角色:【 <strong>{{dialogData.params.roleName}}</strong> 】</div>
<v-transfer
:sourceLoading="sourceLoading"
:sourceData="sourceData"
:noDataShow="totals"
:propsNames="{'id':'userId',label:'userName'}"
targetOrderType="origin"
:sourceToTargetThen="sourceToTargetThen"
:targetToSourceThen="targetToSourceThen"
:onSearch="onSearch"
>
<span slot="left-footer">
<el-pagination
small
:current-page="currentPage"
:page-size="pageSize"
layout="total,prev,pager,next"
:total="totals"
:hide-on-single-page="onlyOnePage"
@current-change="pageChange($event)"
@size-change="sizeChange($event)"
></el-pagination>
</span>
</v-transfer>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" :disabled="dialogloading" v-if="" @click="handleSubmit">确 定</el-button>
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
</template>
//js部分
import vTransfer from '@/components/transfer';
export default {
name:"",
data(){
return {
sourceData:[],
targetData:[],
searchFilter:"",
bindUserIds:[],
dialogloading:false,
sourceLoading:false,
fullscreenLoading:false,
dialogVisible:false,
modalAppendToBody:false,
closeOnClickModal:false,
closeOnPressESC:false,
onlyOnePage:false,
frequentClick:true,
currentPage:1,
pageSize:10,
totals:0
}
},
props:['dialogData'],
components:{
vTransfer
},
mounted(){
this.dialogVisible = true;
console.log(this.dialogData);
this.userLoad();
},
updated(){
},
methods:{
targetToSourceThen(target){
this.targetData = target;
this.userLoad();
},
sourceToTargetThen(target){
this.targetData = target;
},
compareSourceTargetData(source,target){
this.$nextTick(()=>{
target.forEach((item,i)=>{
source.forEach((sourceItem,j)=>{
if(item.userId == sourceItem.userId){
source.splice(j,1);
return ;
}
})
})
})
console.log(this.sourceData,source);
},
dialogClose(){
this.dialogVisible = false;
this.$parent.roleBindUser = false ;
},
onSearch(searchFilter){
if(this.frequentClick){
this.frequentClick = false ;
this.currentPage = 1;
this.searchFilter = searchFilter;
this.userLoad();
//防止频繁点击
let that = this;
let timer = setTimeout(function(){
that.frequentClick = true;
if(timer) clearTimeout(timer);
timer = '';
},2000)
}
},
userLoad(){
this.sourceLoading = true;
this.$axios.post('/system/role/usernotinrole', {
currentPage: this.currentPage,
pageSize: this.pageSize,
filter:this.searchFilter,
queryParams:{
roleId:this.dialogData.params.roleId
}
}).then(res => {
this.sourceLoading = false;
this.$common.thenFactory({
res:res,
t:this
}).then((res)=>{
this.getItems(res.data.content.data);
this.totals = res.data.content.total;
})
}).catch(err => {
this.$common.systemCatch(err,this)
})
},
getItems(data){
data.forEach((dataItem,i)=>{
dataItem.roles && dataItem.roles.forEach((item,j)=>{
if(item.roleId == this.dialogData.params.roleId){
data.splice(i,1);
}
})
})
data.forEach((item,i)=>{
item.order = (this.currentPage-1)*this.pageSize + i;
})
this.sourceData = data;
this.compareSourceTargetData(this.sourceData,this.targetData);
},
pageChange(val){
this.currentPage = val;
this.userLoad()
},
sizeChange(val){
this.pageSize = val;
this.currentPage = 1;
this.userLoad();
},
handleSubmit(){
this.bindUserIds=[];
this.targetData.forEach((item,i)=>{
if(item.userId){
this.bindUserIds.push(item.userId);
}
})
if(this.bindUserIds.length){
this.dialogloading = true;
this.$axios({
method:'post',
url:'/system/role/adduser',
headers:{'content-type':'application/x-www-form-urlencoded'},
data:'roleId='+this.dialogData.params.roleId+'&userIds='+this.bindUserIds.join(',')
}).then(res => {
this.$common.thenFactory({
res:res,
t:this
}).then((res)=>{
this.dialogloading = false;
this.$message({
type: 'success',
message: '绑定成功!'
});
this.dialogClose();
})
}).catch(err => {
this.$common.systemCatch(err,this)
})
}else{
this.$message({
type:"warning",
message:"请选择并移入要绑定的用户!"
})
}
}
},
watch:{
}
}
//CSS部分
.dialog-container .page-title{
font-size:13px;
color:#409EFF;
}
.dialog-container /deep/ .el-dialog__body{
padding-top:0;
}
.dialog-container /deep/ .el-dialog__footer{
text-align: center;
}
.current-role-box{
padding:12px 0;
text-align: center;
}
注意事项:
1、this.$common是小编自己的公共组件,替换成你们自己的代码即可;
2、具体参数请看props对象,结合完整的使用例子去理解,也可以在这个基础上再扩展一下;