vue3+vant 实现树状多选组件

vue3+vant 实现树状多选组件

  • 需求描述
  • 效果图
  • 代码
    • 父组件引用
    • selectTree组件
  • tree组件
    • 数据格式

需求描述

在这里插入图片描述

移动端需要复刻Pc端如上图的功能组件,但vant无组件可用,所以自己封装一个。

效果图

在这里插入图片描述

代码

父组件引用

import TreeSelect from "/selectTree.vue"

  <treeSelect
    ref="treeSelectRef"
    v-model:show="showAera"
    :modelValue="modelValue"
    :listData="options"
    :multiple='true'
    placeholder="请选择"
    @changeModelValue="changeModelValue"
  ></treeSelect>

selectTree组件

<template>
  <van-popup v-model:show="showPicker" round position="bottom"  @click-overlay="onClickOverlay" >
    <div class="tree-box">
      <div class="tree-container">
        <div class="tree-data">
          <TreeSelect
            ref="treeSelectRef"
            :list="data.list"
            :listObj="data.listObj"
            @confirm="onConfirm"
          ></TreeSelect>
        </div>
      </div>
    </div>

    <div class="tree-confirm">
      <van-button type="primary" block @click="handleConfirm">确定</van-button>
    </div>
  </van-popup>
</template>
 
<script setup>
import { reactive, watch, ref, nextTick, onMounted } from "vue";
import TreeSelect from "./tree.vue";
import { showLoadingToast, closeToast } from "vant";

const emits = defineEmits(["changeModelValue", "update:show", "confirm"]);
const props = defineProps({
  show: {
    type: Boolean,
    default: false,
  },
  // 绑定值
  modelValue: {
    type: Array,
     default() {
      return [];
    },
  },
  listData: {
    type: Array,
    default() {
      return [];
    },
  },
});

watch(
  () => props.show,
  () => {
    showPicker.value = props.show;
    initData(props.listData);
  }
);

const showPicker = ref(props.show);
const data = reactive({
  list: props.listData, // 树数组
  listObj: {}, // 数组对象
  selectList: [], // 选中的数据
  canCheckList: [], // 能够选择的数据集合
  canCheckListFixed: [], // 固定的能够选择的数据集合
});

const treeSelectRef = ref(null);

const init = (type) => {
  data.canCheckList = [];
  data.canCheckListFixed = [];
};
const initData = (options) => {
  if (options && options.length) {
    options[0].first=true
    data.list = options;
    init();
    data.listObj = setListObj(options);
  }
};

// 将树形数据转为扁平对象
const setListObj = (list) => {
  let listObj = {};
  list.forEach((itm) => {
    if(props.modelValue&&props.modelValue.indexOf(itm.id)!==-1){
       itm.checked=true
    }
    data.canCheckList.push(itm);
    data.canCheckListFixed.push(itm);
    listObj[itm.id] = itm;
    if (itm.children && itm.children.length) {
      listObj = {
        ...listObj,
        ...setListObj(itm.children),
      };
    }
  });

  return listObj;
};

const onClickOverlay = () => {
  emits("update:show", false);
};
// 确认
const handleConfirm = () => {
  emits("changeModelValue", data.selectList);
  emits("update:show", false);
};

const onConfirm = (e) => {
  const showSelectList = filterData(e);
  data.selectList = showSelectList.map((itm) => itm.id);
};

// 过滤数据
const filterData = (selectList) => {
  // 过滤出展示中,且打勾的数据
  const showSelectList = selectList.filter((itm) => {
    return !itm.isHide && !itm.isShowChildren;
  });
  return showSelectList;
};

const sendWordShow = ref(false);

defineExpose({
  init,
  setListObj,
});
</script>
 
<style lang="less" scoped>
.tree-box {
  --van-search-content-background-color: #eeeeee;
  --van-search-content-background: #eeeeee;
}

.tree-container {
  width: 100%;
  padding: 32px 32px 0;
}

.tree-data {
  height: 60vh;
  overflow-y: auto;
}

.tree-btns {
  width: 100%;
  margin-bottom: 24px;
  display: flex;
  align-items: center;
}

.tree-confirm {
  width: 100%;
  padding: 12px 32px;
}
</style>

tree组件

<template>
  <div class="list">
    <div class="item" v-for="item in props.list" :key="item.key" v-show="!item.isHide">
      <div class="title">
        <div class="checkbox-box">
          <van-checkbox  icon-size="16px" shape="square" @click.stop="checkChange(item)" v-model="item.checked"><span style="font-size: 15px;">{{ item.name}}</span></van-checkbox>
        </div>
        <div @click.stop="itemClick(item)" :class="item.first?'arrow':'arrowlast'">
            <van-icon v-if="item.children && item.children.length" :name="item.isShowChildren ? 'arrow-up' : 'arrow-down'" />
        </div>
        
      </div>
      <div class="tree" v-show="item.first||item.isShowChildren">
        <tree  
         :isLink="data.isLink"
          v-if="item.children && item.children.length" 
          :list="item.children" 
          :listObj="props.listObj"
          :isFirstFloor="false" 
          :multiple="data.multiple" 
          @confirm="onConfirm" 
          :defaultId="defaultId">
        </tree>
      </div>
    </div>
  </div>
</template>
<script setup>
import { reactive, watch } from 'vue'
import tree from './tree.vue'
const emits = defineEmits(["change","confirm"])
const props = defineProps({

  // 是否是第一层
  isFirstFloor: {
    type: Boolean,
    default() {
      return true;
    },
  },
  // 树形结构
  list: {
    type: Array,
    default() {
      return [];
    },
  },
  // 树形扁平化数据
  listObj: {
    type: Object,
    default() {
      return {};
    },
  },
  // 单选默认值
  defaultId : String
})

 
const data = reactive({
  firstLoad: true,
  checkboxValue1: [],
  showList: [],
  isLink:true,
  multiple:true,
  isOutData: true, // 需要将数据抛出
})
 
watch(() => props.list, () => {
  if (data.firstLoad) {
    outDataBuffer();
    data.firstLoad = false;
  }
  // 判断 是第一层树 且 不是进行显示隐藏操作时,进行数据的抛出
  if (props.isFirstFloor && data.isOutData) {
    if(data.multiple){
      outCheckedData();
    }
  }
}, { deep: true })
 
// 展开
const itemClick = (item) => {
  outDataBuffer();
  item.isShowChildren = !item.isShowChildren
  
}
// 数据抛出缓冲(在list数据变化时,不想抛出选择的数据时,调用该方法)
const outDataBuffer = () => {
  data.isOutData = false;
  setTimeout(() => {
    data.isOutData = true;
  }, 500);
}
// 获取选中对象
const getCheckData = (list) => {
  let deptList = [];
  list.forEach((itm) => {
    if (itm.checked) {
      deptList.push(itm);
    }
    if (itm.children && itm.children.length) {
      deptList = deptList.concat(getCheckData(itm.children));
    }
  });
  return deptList;
}
// 单项checked改变
const checkChange = (item) => {

  // 多选
  if (data.multiple) {
    // item.checked = !item.checked
    if (data.isLink) {
      // 展开所有可以展开的节点
      if (item.checked) {
          expandAll(item);
      }
      // 判断父级是否需要勾选
      checkParent(item);
      // 勾选子级
      if (item.children && item.children.length) {
        checkChidren(item.children, item.checked);
        outCheckedData();
      }
    }
    return
  }
 
  // 单选
  if(item.children && item.children.length) return
  toggleAllSelectData(props.list)
  outCheckedData();

}
 
// 获取全部可选择数据,进行全选/取消
const toggleAllSelectData = (list) => {
  list.forEach((itm) => {
    itm.checked = false
    if (itm.children && itm.children.length) {
      toggleAllSelectData(itm.children)
    }
  });
}
 
// 展开所有可以展开的节点
const expandAll = (item) => {
  if (item.children?.length) {
    item.isShowChildren = true
    item.children.forEach(itm => {
      expandAll(itm);
    })
  }
}
// 判断父级是否需要勾选
const checkParent = (item) => {
  // 父级不存在不再往下走
  if (!props.listObj[item[props.pidKey]]) {
    return;
  }
  let someDataCount = 0; // 同级的相同父级数据量
  let checkedDataCount = 0; // 同级已勾选的数据量
  for (const id in props.listObj) {
    const itm = props.listObj[id];
    if (itm[props.pidKey] === item[props.pidKey] && !itm.isHide) {
      someDataCount++;
      if (itm.checked) {
        checkedDataCount++;
      }
    }
  }
  const isEqual = someDataCount === checkedDataCount;
  if (props.listObj[item[props.pidKey]].checked != isEqual) {
    props.listObj[item[props.pidKey]].checked = isEqual
    checkParent(props.listObj[item[props.pidKey]]);
  }
}
// 根据父级统一取消勾选或勾选
const checkChidren = (list, isChecked) => {
  list.forEach((itm) => {
    itm.checked = isChecked
    if (itm.children && itm.children.length) {
      checkChidren(itm.children, isChecked);
    }
  });
}
// 抛出选中的数据
const outCheckedData = () => {
  const checkedList = getCheckData(props.list);
  emits("change", checkedList);
  onConfirm(checkedList)
}
 
const onConfirm = (e) => {
  emits("confirm", e);
}
 
defineExpose({
  itemClick,
  outDataBuffer,
  getCheckData,
  checkChange,
  expandAll,
  checkParent,
  checkChidren,
  outCheckedData,

})
</script>
 
<style lang="less" scoped>
 
.list {
  .item {
    margin-bottom: 10px;
 
    .title {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
 
      .checkbox-box {
        display: flex;
        align-items: center;
        cursor: pointer;
        padding: 10px 0;
      }
 
      .arrow{
        width: 80px;
        display: flex;
        justify-content: flex-end;
      }
    }
 
    .tree {
      margin-left: 50px;
    }
  }
    .arrow{
    display: none !important;
   }
}
</style>

数据格式

[
    {
        "name": "1",
        "key": 0,
        "children": [
            {
                "name": "2",
                "key": 1,
                "children": []
            },
            {
                "name": "3",
                "key": 1,
                "children": [
                   {
                     "name": "4",
                     "key": 3,
                     "children": []
                   }
                ]
            }
        ]
    }
]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小型内衣洗衣机什么牌子好?性价比高的迷你洗衣机推荐

现在洗内衣内裤也是一件较麻烦的事情了&#xff0c;在清洗过程中还要用热水杀菌&#xff0c;还要确保洗衣液是否有冲洗干净&#xff0c;还要防止细菌的滋生等等&#xff0c;所以入手一款小型的烘洗全套的内衣洗衣机是非常有必要的&#xff0c;专门的内衣洗衣机可以最大程度减少…

移交计划书、移交确认单

项目移交过程文件&#xff1a; 1、移交计划书 2、移交确认单 1、移交计划 2、移交确认单

2023年03月 Scratch(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题(共25题,每题2分,共50分) 第1题 下列说法不正确的是?( ) A:可以从声音库中随机导入声音 B:可以录制自己的声音上传 C:可以修改声音的大小 D:不能修改声音的速度 答案:D 针对声音可以进行导入,上传,修改声音大小和速度这些操作,故正确答案选择D。…

牛只识别 牛脸识别 个体识别 身份识别

融合YOLOv5s与通道剪枝算法的奶牛轻量化个体识别方法 Light-weight recognition network for dairy cows based on the fusion of YOLOv5s and channel pruning algorithm 论文链接 知网链接 点击进入正文 该文章讨论了奶牛花斑、光照条件、不同剪枝方法、不同剪枝率对准确率的…

Power Automate-当收到HTTP请求时触发流程

选择创建自动化云端流&#xff0c;点跳过 第一个操作搜索HTTP&#xff0c;点击当收到HTTP请求时 第一个下拉框选择Anyone&#xff0c;给任何人提供访问权限&#xff0c;当然也可以根据需求选择 点击使用示例有效负载生成架构 写入JSON&#xff0c;点击完成 正文JSON架构就自动生…

Tosca 2023x 新功能介绍

Tosca/Structure的功能增强 热优化功能增强 在优化过程中&#xff0c;支持将对流边界条件转移至新创建的表面&#xff0c;方便更加真实地模拟传热行为。支持恒定传导。多物理场优化能力增强。通过使用多个输入面板对热和结构荷载进行分别输入&#xff0c;以支持同时对热和结构…

校园跑腿小程序源码系统+多校园版+取快递+食堂超市跑腿+外卖 带完整的搭建教程

大家好啊&#xff0c;又到了罗峰给大家分享源码的时间啦。今天要给大家分享的是一款校园跑腿小程序源码系统。这款系统功能十分强大&#xff0c;开发了多校园版。众所周知&#xff0c;校园跑腿的发展是从外卖配送一点点演变过来的&#xff0c;而校园跑腿的基础性服务项目就是帮…

【YOLOv7/YOLOv5系列算法改进NO.47】改进激活函数为GELU

文章目录 前言一、解决问题二、基本原理三、​添加方法四、总结 前言 作为当前先进的深度学习目标检测算法YOLOv7&#xff0c;已经集合了大量的trick&#xff0c;但是还是有提高和改进的空间&#xff0c;针对具体应用场景下的检测难点&#xff0c;可以不同的改进方法。此后的系…

modbus转profinet网关连接PLC与变频器控制摆辊应用在涂布机案例

通过兴达易控modbus转profinet网关的应用&#xff0c;PLC能够直接与变频器进行通讯&#xff0c;并实现对摆辊的精确控制。兴达易控modbus转profinet网关&#xff08;XD-MDPN100&#xff09;作为一个高性能的转换设备&#xff0c;能够稳定可靠地完成modbus和profinet之间的数据转…

智慧城市安全监控的新利器

在传统的城市管理中&#xff0c;井盖的监控一直是一个难题&#xff0c;而井盖异动传感器的出现为这一问题提供了有效的解决方案。它具有体积小、重量轻、安装方便等特点&#xff0c;可以灵活地应用于各种类型的井盖&#xff0c;实现对城市基础设施的全方位监控。 智能井盖监测终…

《低代码指南》——国内首个向量数据库标准亮相,腾讯云联合50家企业共同编制

11月15日,在腾讯云向量数据库技术及产业峰会上,腾讯云全面升级向量数据库多项核心性能,最高支持千亿级向量规模和500万QPS峰值能力,同时和信通院一起联合50多家企业共同发布了国内首个向量数据库标准,推进向量数据库及大模型相关产业走向大规模应用。 腾讯集团高级执行副总…

[修改Linux下ssh端口号]解决无法修改sshd_config无法修改

前言&#xff1a;写本文的前因是本人的阿里云服务器经常被黑客暴力破解ssh的22端口号。再网络上搜索解决都是说使用root权限进行修改&#xff0c;但本人在root下也无法成功进行修改sshd_config文件。所以在大量搜索下终于找到了解决方案&#xff0c;现在分享出来给有需要的人使…

安卓用户当心: CERT-IN 发布高危漏洞警告

已发现的漏洞一旦被利用&#xff0c;将构成严重风险&#xff0c;可能导致未经授权访问敏感信息。 印度计算机应急响应小组&#xff08;CERT-IN&#xff09;在最近发布的一份公告中&#xff0c;就影响印度安卓用户的新安卓漏洞发出了重要警告。 该警告对使用安卓 11、12、12L、…

delphi电子处方流转 sm2 sm4(医院)

【delphi电子处方流转(医院) sm2 sm4】支持 就诊登记、电子处方上传预核验、处方处方医保电子签名、电子处方上传、电子处方撤销、电子处方信息查询、电子处方审核结果查询、电子处方取药结果查询、电子处方药品目录查询等功能。技术交流Q 648437169 下载链接&#xff1a;http…

RT-DETR算法优化改进:SCConv,空间和通道重建卷积 | CVPR2023 | 卷积变体大作战

💡💡💡本文改进:SCConv(空间和通道重建卷积),一个即插即用的架构单元,可以直接用来替代各种卷积神经网络中的标准卷积。 1)放入Neck RepC3后面; RT-DETR魔术师专栏介绍: https://blog.csdn.net/m0_63774211/category_12497375.html ✨✨✨魔改创新RT-DETR �…

【Ubuntu】Ubuntu20.04下安装视频播放器vlc和录屏软件ssr

【Ubuntu】Ubuntu20.04下安装视频播放器vlc和录屏软件ssr 文章目录 【Ubuntu】Ubuntu20.04下安装视频播放器vlc和录屏软件ssr1. 安装视频播放器vlc2. 安装录屏软件ssr 1. 安装视频播放器vlc sudo apt-get install vlcvlc是一款比较简洁的视频播放器&#xff0c;如下所示 2. 安…

生成对抗网络Generative Adversarial Network,GAN

Basic Idea of GAN Generation&#xff08;生成器&#xff09;  Generation是一个neural network&#xff0c;它的输入是一个vector&#xff0c;它的输出是一个更高维的vector&#xff0c;以图片生成为例&#xff0c;输出就是一张图片&#xff0c;其中每个维度的值代表生…

《Linux从练气到飞升》No.30 深入理解 POSIX 信号量与生产消费模型

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

渗透测试——1

1.计算机地址 计算机在网络中的地址有以下3类&#xff1a; &#xff08;1&#xff09;物理地址 物理地址相当于现实生活中的人脸&#xff0c;是不可改变的&#xff0c;独一无二的。每张网卡的物理地址是固定不变的。 物理地址由六个十六进制数组成&#xff0c;如&…

求臻医学:当MRD遇到肺癌 见招拆招,斩草除根

当MRD遇到肺癌→见招拆招&#xff0c;斩草除根 浅谈MRD在肺癌中的应用 新辅助治疗后&#xff0c;可以通过MRD来辅助评估预后&#xff0c;一般MRD阴性与新辅助治疗后的pCR (病理完全缓解)相关&#xff0c;达到MRD阴性或pCR的惠者预后更好. MRD在肺癌中可潜在解决的问题点1.术…