内容字号: 默认 大号 超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到宋体切换到微软雅黑

重写element-ui的Transfer穿梭框,支持正常搜索、反向排除、可翻页

发布:2021-01-06 浏览: 评论(
有没有觉得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对象,结合完整的使用例子去理解,也可以在这个基础上再扩展一下

前端新手交流群
欢迎加入web前端新手交流qq群:734802480

更多文章

相关文章

评论

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。


Copyright © 2014-2021 seozhijia.net 版权所有-粤ICP备13087626号-4