移动端树形结构

该组件依据需求来做,当前包含三种选择状态,选中,未选中,半选。由于不需要做树形的收缩展开故没有写相关内容。树形展开与收缩与选中类似,只需要在节点上挂载相关字段即可实现。由于需求需要增加不限的功能,所以改组件内部实现不限的勾选。

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

组件代码

<template>
    <div class="tree-container">
        <div v-for="item in selfTreeData" :key="item.code + item.name">
            <div class="tree-node">
                <!-- 第一级不需要缩进 -->
                <span v-if="level > 0" :style="{ width: (level - 1) * 24 + 'rpx' }" class="space"></span>
                <span v-if="level > 0" class="relevance"></span>
                <span :class="item.checked == 1 ? 'is-active' : ''">{{ item.name }} </span>
                <span class="check-box" @click="changeChecked(item)">
                    <img v-show="!item.checked" src="@/static/images/check-normal.svg" />
                    <img v-show="item.checked == 0.5" src="@/static/images/check-indeterminate.svg" />
                    <img v-show="item.checked == 1" src="@/static/images/check-active.svg" />
                </span>
            </div>
            <my-tree v-if="item.children && item.children.length" :tree-data="item.children" :level="level + 1"
                :ref="`chTree${item.code}`" @changeParent="changeParent" @clearUnlimited="clearUnlimited"></my-tree>
        </div>
    </div>
</template>

<script>
import myTree from './myTree';

export default {
    name: 'myTree',
    data() {
        return {}
    },
    props: {
        treeData: {
            required: true,
            type: Array
        },
        level: {
            type: Number,
            default: 0
        }
    },
    data(){
        return {
            selfTreeData:[]
        }
    },
    watch:{
        treeData:{
            handler(val){
                this.selfTreeData = val;
            },
            immediate:true,
            deep:true
        }
    },
    created() {
    },
    methods: {
        changeChecked(treeNode) {
            if (!treeNode.code) {
                // 选中不限,需要清除下面的所有
                if(!treeNode.checked)
                    return this.clearChecked();
            } else if(this.level){
                // 清除code为null的
                this.$emit('clearUnlimited')
            }else{
                // 清除code为null的
                this.clearUnlimited()
            }
            // checked 被改变
            if (!treeNode.checked) return this.handleChangeNodeChecked(treeNode, 1);
            if (treeNode.checked == 0.5) return this.handleChangeNodeChecked(treeNode, 1);
            if (treeNode.checked == 1) return this.handleChangeNodeChecked(treeNode, 0);
        },
        handleChangeNodeChecked(treeNode, state , isClear = false) {
            this.$set(treeNode, 'checked', state);
            // 通知父级,改变父级选中状态。
            if(!isClear)
                this.$emit('changeParent', treeNode.pcode);
            // 将子级全部选中。
            if (treeNode.code && this.$refs[`chTree${treeNode.code}`] && this.$refs[`chTree${treeNode.code}`][0])
                this.$refs[`chTree${treeNode.code}`][0].changeChChecked(treeNode, state);
        },
        changeParent(pcode) {
            // 子级发生改变,更新父级选中状态。
            const pNode = this.selfTreeData.find(node => node.code === pcode);
            if (!pcode) return;
            // 父级肯定有children
            const len = pNode.children.length;
            let state = 0 , hasChecked = false , hasNoChecked = false;
            // 此处不统计个数是假设数组很长,我前两个就能判断的话减少性能消耗。
            pNode.children.some((node,index) => {
                if(node.checked == 0.5){
                    // 直接将当前设置为半选
                    state = 0.5;
                    return true;
                }
                // 只有存在又有选中,又有未选中则直接设置为0.5
                if(node.checked == 1){
                    hasChecked = true;
                }
                if(!node.checked){
                    hasNoChecked = true;
                }
                if(hasChecked && hasNoChecked){
                    state = 0.5;
                    return true;
                }
            })
            // 此处只要判断state是否还是0,如果还是0且hasChecked为true证明需要为1。
            state = state === 0 && hasChecked ? 1 : state;
            this.$set(pNode, 'checked', state);
            this.$emit('changeParent', pNode.pcode);
        },
        changeChChecked(treeNode, state) {
            // 改变子级的选中状态
            if (treeNode.children && treeNode.children.length) {
                treeNode.children.forEach(node => {
                    this.$set(node, 'checked', state);
                    if(this.$refs[`chTree${node.code}`] && this.$refs[`chTree${node.code}`][0])
                        this.$refs[`chTree${node.code}`][0].changeChChecked(node, state);
                })
            }
        },
        clearUnlimited() {
            // 清除不限
            if (this.level) {
                this.$emit('clearUnlimited')
            } else {
                const unlimitedNode = this.selfTreeData.find(node => !node.code);
                if (unlimitedNode && unlimitedNode.checked) {
                    unlimitedNode.checked = 0;
                }
            }
        },
        getChecked(){
            const checkedCodes = [];
            this.selfTreeData.forEach(node=>{
                const checked = node.checked;
                if(checked == 1){
                    checkedCodes.push(node.code);
                }
                if(checked && this.$refs[`chTree${node.code}`] && this.$refs[`chTree${node.code}`][0])
                    checkedCodes.push(...this.$refs[`chTree${node.code}`][0].getChecked());
            })
            return checkedCodes;
        },
        clearChecked(){
            // 业务需求,重置的时候勾选不限。
            this.selfTreeData.forEach(node => {
                if (node.code){
                   this.handleChangeNodeChecked(node,0,true)
                }
                else 
                    this.$set(node,'checked',1)
            })
        }
    }
}
</script>

<style lang="scss" scoped>
.tree-node {
    height: 96rpx;
    padding: 26rpx 24rpx 26rpx 57rpx;
    color: #262626;
    font-size: 28rpx;
    box-shadow: inset 0px -1px 0px 0px #E6E6E6;
    box-sizing: border-box;
    position: relative;

    .space {
        height: 1px;
        display: inline-block;
    }

    .relevance {
        height: 14rpx;
        width: 14rpx;
        display: inline-block;
        border-left: 1px solid #B2B2B2;
        border-bottom: 1px solid #B2B2B2;
        position: relative;
        top: -10rpx;
        margin-right: 14rpx;
    }

    .check-box {
        position: absolute;
        right: 24rpx;
        overflow: auto;

        img {
            height: 40rpx;
            width: 40rpx;
        }
    }

    .is-active {
        color: $primaryColor;
    }
}</style>

使用

<template>
    <div class="filter-panel-content">
        <div class="tab-conrainer">
            <div class="left-tabs">
                <div 
                v-for="tab in pageData" 
                :key="tab.code" 
                :class="tab.code === activeTab ? 'tab-item active' : 'tab-item'" 
                @click="changeTabActive(tab)"
                >{{ tab.name }}</div>
            </div>
            <div class="tree-container" v-for="tab in pageData" :key="tab.code" v-show="tab.code === activeTab">
                <my-tree :tree-data="tab.treeData" :ref="`tree${tab.code}`" />
            </div>
        </div>
        <div class="operate-container">
            <div class="btn" @click="reset">重置</div>
            <div class="btn primary" @click="search">确定</div>
        </div>
    </div>
</template>

<script>
import myTree from './components/myTree.vue';

export default {
    props:{
        showFilterPopup:{
            type:Boolean,
            default:true
        }
    },
    data(){
        return {
            pageData:[],
            activeTab:'',
            searchData:{}
        }
    },
    components:{
        myTree
    },
    created(){
        this.getData()
    },
    methods:{
        getData(){
          this.pageData = [
                { name:'湖南' , code:'01',treeData:[
                    {name:'不限',code:null,checked:1},
                    {name:'长沙',code:'011',children:[
                        {name:'天心区',code:'0111',pcode:'011'},
                        {name:'开福区',code:'0112',pcode:'011'},
                        {name:'芙蓉区',code:'0113',pcode:'011'},
                        {name:'岳麓区',code:'0114',pcode:'011'},
                        {name:'望城区',code:'0115',pcode:'011'},
                        {name:'长沙县',code:'0116',pcode:'011',children:[
                            {name:'星沙',code:'01161',pcode:'0116'},
                            {name:'安沙',code:'01162',pcode:'0116'},
                        ]},
                    ]},
                    {name:'株洲',code:'012'},
                    {name:'湘潭',code:'013'}
                ]},
                { name:'广东' , code:'02',treeData:[
                    {name:'不限',code:null,checked:1},
                    {name:'广州',code:'021'},
                    {name:'深圳',code:'022'},
                    {name:'东莞',code:'023'}
                ]},
                { name:'湖北' , code:'03',treeData:[
                    {name:'不限',code:null,checked:1},
                    {name:'武汉',code:'031'},
                    {name:'恩施',code:'032'},
                    {name:'孝感',code:'033'}
                ]},
                { name:'江西' , code:'04',treeData:[
                    {name:'不限',code:null,checked:1},
                    {name:'武昌',code:'041'},
                    {name:'赣州',code:'042'},
                    {name:'九江',code:'043'}
                ]},
            ];
            this.activeTab = this.pageData[0].code;
        },
        changeTabActive(tab){
            this.activeTab = tab.code;
        },
        reset(){
            this.pageData.forEach(tab=>{
                this.$refs[`tree${tab.code}`][0].clearChecked()
            })
        },
        search(){
            this.pageData.forEach(tab=>{
                this.searchData[tab.code] =  this.$refs[`tree${tab.code}`][0].getChecked().filter(item=>item)
            })
            this.$emit('search',this.searchData)
        }
    }
}
</script>

<style scoped lang="scss">
uni-view{
    height: 100%;
}
.filter-panel-content{
    background:#FFF;
    max-height: 100%;
    overflow: auto;
    .tab-conrainer{
        display: flex;
        .left-tabs{
            background: #F0F0F0;
            width: 160rpx;
            min-height: 804rpx;
            .tab-item{
                height: 96rpx;
                line-height: 96rpx;
                color: $text-normal-color;
                font-size: 28rpx;
                font-weight: 400;
                text-align: center;
            }
            .tab-item.active{
                color: $primaryColor;
                background: #FFF;
            }
        }
        .tree-container{
            flex: 1;
        }
    }
    .operate-container{
        padding: 22rpx 40rpx;
        background: #F9F9F9;
        box-shadow: inset 0px 1rpx 0px 0px #D9D9D9;
        display: flex;
        .btn{
            cursor: pointer;
            color: $text-normal-color;
            line-height: 96rpx;
            box-sizing: border-box;
            flex: 1;
            text-align: center;
            font-size: 36rpx;
            background: #FFF;
            border: 2rpx solid rgba(0,0,0,0.15);
            border-radius: 8rpx;
        }
        .btn.primary{
            color: #FFF;
            background: $primaryColor;
            border: 0px;
            margin-left: 30rpx;
        }
    }
}
</style>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值