首页 > Vue >

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

时间: 作者:admin 浏览:

有没有觉得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对象,结合完整的使用例子去理解,也可以在这个基础上再扩展一下;

微信公众号
微信公众号:
  • 前端全栈之路(微信群)
前端QQ交流群
前端QQ交流群:
  • 794324979
  • 734802480(已满)

更多文章

栏目文章


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