移动端树形结构

news2024/11/24 15:39:34

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

效果

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

组件代码

<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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Moonbeam操作指南 | 如何设置Moonbeam开发节点

Moonbeam开发节点是为本地构建和测试应用的个人开发环境。对以太坊开发者来说&#xff0c;可以和Ganache相媲美。可以使你快速上手&#xff0c;且无需中继链的支出即可轻松实现。 有2种方式可以开始运行节点&#xff1a;使用Docker运行一个预构建的二进制文件&#xff0c;或者…

Java面试宝典

JDK,JRE,JVM 三者关系&#xff1f; JDK 是 JAVA 程序开发时用的开发工具包&#xff0c;其内部也有 JRE 运行环境 JRE。JRE 是 JAVA 程序运行时需要的运行环境&#xff0c;就是说如果你光是运行 JAVA 程序而不是去搞开发的话&#xff0c;只安装 JRE 就能运行已经存在的 JAVA 程…

我们发送的信息数据是怎么处理的?

下图以用户 a 向用户 b 发送邮件为例子&#xff1a; 数据处理流程 1、应用程序处理 首先应用程序会进行编码处理&#xff0c;这些编码相当于 OSI 的表示层功能&#xff1b; 编码转化后&#xff0c;邮件不一定马上被发送出去&#xff0c;这种何时建立通信连接何时发送数据的管…

5G/V2X赛道「重启」

在提升高阶智能驾驶安全性和感知冗余能力的道路上&#xff0c;除了激光雷达、高精度地图及定位&#xff0c;还有一项技术可能即将掀起一场新的风暴。 就在今年3月&#xff0c;作为全球通信领域的年度风向标 — 2023世界移动通信大会&#xff08;MWC&#xff09;上&#xff0c;…

无需魔法打开即用的 AI 工具集锦

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;蚂蚁集团高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《EffectiveJava》独家解析》专栏作者。 热门文章推荐…

Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper

前文&#xff1a; 《Tomcat源码&#xff1a;启动类Bootstrap与Catalina的加载》 《Tomcat源码&#xff1a;容器的生命周期管理与事件监听》 《Tomcat源码&#xff1a;StandardServer与StandardService》 《Tomcat源码&#xff1a;Container接口》 写在开头&#xff1a;本文…

【C++STL精讲】vector的模拟实现

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;定义vector类&#x1f337;各成员函数的实现&#x1f33a;构造函数&#x1f33a;迭代器&#x1f33a;size与capacity——求大小与容量&#x1f33a;reserve——扩容关于reserve中的深浅拷贝问题&#x1f33a;r…

RK3399平台开发系列讲解(基础篇)ADC 使用方法

🚀返回专栏总目录 文章目录 一、ADC 的 DTS 配置二、ADC 驱动说明2.1、获取 AD 通道2.2、读取 AD 采集到的原始数据2.3、计算采集到的电压三、接口说明沉淀、分享、成长,让自己和他人都能有所收获!😄 📢内核采用工业 I/O 子系统来控制 ADC,该子系统主要为 AD 转换或者…

Day920.结构化日志业务审计日志 -SpringBoot与K8s云原生微服务实践

结构化日志&业务审计日志 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于结构化日志&业务审计日志的内容。 1、什么是结构化日志 结构化日志&#xff08;Structured Logging&#xff09;是一种将日志信息组织为结构化数据的技术。 传统的日志通常是一些文…

浏览器前进与后退的秘密——栈 (栈的理解与实现)

文章目录前言&#xff1a;浏览器与栈的纠缠如何理解“栈”&#xff1f;如何实现一个“栈”&#xff1f;基于数组的顺序栈基于链表的链式栈解答开篇&#x1f431;‍&#x1f409;作者简介&#xff1a;大家好&#xff0c;我是黑洞晓威&#xff0c;一名大二学生&#xff0c;希望和…

0010_C++语言_简单的C++程序

系列文章目录 0001_C语言_C入门 文章目录 系列文章目录 前言 一、写一个简单的C程序 二、来看看这个程序 1.返回类型 2.函数名 3.形参列表 4.函数体 总结 前言 期中考试完毕&#xff01;&#xff08;在此祝宫野艾莲娜小姐考得很好&#xff01;&#xff09; 今天来看一个简单…

AXI write data在Write data channel的排布

前几天帮一位同事分析了下write data在AXI write data channel上排布&#xff0c;想想还是记录一下&#xff0c;方便日后复习。我们先来看一张wdata排布图&#xff0c;灰色单元表示该Byte没有被传输。 第一次看这张图的时候&#xff0c;是否有感觉疑惑&#xff1a; address为0…

Mysql-锁机制

Mysql的锁机制 一、简介 锁是为了保证数据库中数据的一致性&#xff0c;使各种【共享资源】在被访问时变得【有序】而设计的一种规则。 tip MysQL中不同的存储引擎支持不同的锁机制&#xff1a; InoDB支持【行锁】&#xff0c;有时也会升级为表锁。MyIsam只支持表锁。 【表锁…

实验6 TensorFlow基础

1. 实验目的 掌握TensorFlow低阶API&#xff0c;能够运用TensorFlow处理数据以及对数据进行运算。 2.实验内容 ①实现张量维度变换&#xff0c;部分采样等&#xff1b; ②实现张量加减乘除、幂指对数运算&#xff1b; ③利用TensorFlow对数据集进行处理。 3.实验过程 题目…

UE4 几种常见的项目优化方式

1.灯光范围优化 当屏幕某一块像素被多盏灯光所影响&#xff0c;那么也会拖慢帧率&#xff0c;可以打开灯光复杂度视图进行查看&#xff0c;屏幕上越红的地方灯光复杂度越高&#xff0c;尝试降低灯光半径可以解决&#xff1a; 2.材质纹素优化 有时候我们并不知道目标模型的…

Vue:自定义组件事件

一、直接在组件标签上绑定事件 1、关于组件的自定义事件&#xff0c;实现步骤&#xff1a; ①提供事件源&#xff08;这个事件源是一个组件&#xff09;②给组件绑定事件&#xff1a;v-on:事件名 或者 事件名 ③编写回调函数&#xff0c;将回调函数和事件进行绑定。 ④等…

NeRF:今年最火的AI技术

转载&#xff1a; https://mp.weixin.qq.com/s?__bizMzIxOTczOTM4NA&mid2247549727&idx2&snc59e18cb2bd8dfe34eafdc6e7cf9f4fd&chksm97d4e688a0a36f9ea0ed4c5a988a3802b4e9265655c5c0a6f208a396860f3842abae9be3a9a3&scene27 什么是NeRF&#xff1f; Ne…

springboot解决ajax跨域问题

方式1:使用CrossOrigin注解方式 CrossOrigin(origins "*") //解决跨域的关键注解,该注解可以标注在类上也可以标注在方法上域A&#xff1a;http://localhost/across_test/test.html 域A下的test.html <!DOCTYPE html> <html lang"en"> <h…

Three——一、详解基础场景搭建(结尾含完整代码)

速成Three.js——一、详解基础场景搭建(结尾含源码) 给模型添加标签时需要准标签部分 从本章开始会从最初的搭建场景模型开始到插入精灵图部分结尾&#xff0c;便于刚入门three而不知如何去学起的前端工程师去学习&#xff0c;这里可以学到场景搭建的基础知识&#xff0c;引入…

【微服务笔记12】微服务组件之Hystrix和OpenFeign结合使用

这篇文章&#xff0c;主要介绍微服务组件之Hystrix和OpenFeign结合使用。 目录 一、Hystrix和OpenFeign结合使用 1.1、引入依赖 1.2、openfeign启用hystrix 1.3、编写FeignClient接口 1.4、编写FeignClient实现类 1.5、编写Controller控制器 1.6、启动类启动OpenFeign …