vue3+vant 实现树状多选组件

news2024/11/17 9:43:05

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": []
                   }
                ]
            }
        ]
    }
]

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

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

相关文章

车间ERP管理系统都有哪些?能带给企业什么好处

不同规模的制造企业有不同的管理模式和经营策略&#xff0c;而生产和销售等业务是这类企业较为核心的部门&#xff0c;其中车间的管理是生产过程管理的重点环节之一。 车间的管理工作涉及物料、班组、设备、工时评估、生产现场数据采集、派工单、退补料等环节&#xff0c;如何…

ARM 版 Kylin V10 部署 KubeSphere 3.4.0 不完全指南

前言 知识点 定级&#xff1a;入门级KubeKey 安装部署 ARM 版 KubeSphere 和 KubernetesARM 版麒麟 V10 安装部署 KubeSphere 和 Kubernetes 常见问题 实战服务器配置 (个人云上测试服务器) 主机名IPCPU内存系统盘数据盘用途ksp-master-1172.16.33.1681650200KubeSphere/k8…

vite vue3 配置pinia

准备 https://blog.csdn.net/qq_36437991/article/details/134474050 安装pinia 官网 yarn add piniasrc下新建store文件夹&#xff0c;该文件夹下新建index.ts import { createPinia } from "pinia"; const store createPinia(); export default store;修改ma…

【C++11】多线程库 {thread线程库,mutex互斥锁库,condition_variable条件变量库,atomic原子操作库}

在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如windows和linux下各有自己的接口&#xff0c;这使得代码的可移植性比较差。 //在C98标准下&#xff0c;实现可移植的多线程程序 —— 条件编译 #ifdef _WIN32CreateThread(); //在windows系…

腾讯云S5服务器4核8G配置价格表S5.LARGE8性能测评

腾讯云服务器4核8G配置优惠价格表&#xff0c;轻量应用服务器和CVM云服务器均有活动&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;腾讯云百科txybk.com分…

【cpolar】Ubuntu本地快速搭建web小游戏网站,公网用户远程访问

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;cpolar&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 目录 前言 1. 本地环境服务搭建 2. 局域网测试访问 3. 内网穿透 3.1 ubuntu本地安装cpolar 3.2 创建隧道 3.3 测试公网访问 4. 配置…

二、程序员指南:数据平面开发套件

MEMPOOL库 内存池是固定大小对象的分配器。在DPDK中&#xff0c;它由名称标识&#xff0c;并使用环形结构来存储空闲对象。它提供一些其他可选服务&#xff0c;例如每个核心的对象缓存和一个对齐辅助工具&#xff0c;以确保对象填充以将它们均匀分布在所有DRAM或DDR3通道上。 …

三十分钟学会Hive

Hive的概念与运用 Hive 是一个构建在Hadoop 之上的数据分析工具&#xff08;Hive 没有存储数据的能力&#xff0c;只有使用数据的能力&#xff09;&#xff0c;底层由 HDFS 来提供数据存储&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并且提供类似 SQL …

全球地表水数据集JRC Global Surface Water Mapping Layers v1.4

简介&#xff1a; JRC Global Surface Water Mapping Layers产品&#xff0c;是利用1984至2020年获取的landsat5、landsat7和landsat8的卫星影像&#xff0c;生成分辨率为30米的一套全球地表水覆盖的地图集。用户可以在全球尺度上按地区回溯某个时间上地表水分的变化情况。产品…

VS 将 localhost访问改为ip访问

项目场景&#xff1a; 使用vs进行本地调试时需要多人访问界面,使用ip访问报错 问题描述 vs通过ip访问报错 虚拟机或其它电脑不能正常打开 原因分析&#xff1a; 原因是vs访问规则默认是iis,固定默认启动地址是localhost 解决方案&#xff1a; 1.vs项目启动之后会出现这个 右…

BUUCTF [GXYCTF2019]佛系青年 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 密文&#xff1a; 下载附件&#xff0c;解压得到ZIP压缩包。 解题思路&#xff1a; 1、压缩包内有一张png图片和一个txt文本&#xff0c;解压zip压缩包&#xff0c;解压出图片&#xff0c;但txt文本提示需要输入密…

Wireshark 截取指定端口海量包分析

有个应用要分析一下协议&#xff0c;但是8939&#xff0c;8940传输一下子几个G的数据&#xff0c;但是要分析的端口8939实际上只有几个MB最多&#xff0c;如果用wireshark有界面的程序一截取就会卡死&#xff0c;于是使用命令行方式&#xff0c;截取指定端口的 tshark -i &quo…

CentOS7 安装mysql8(离线安装)postgresql14(在线安装)

注&#xff1a;linux系统为vmware虚拟机&#xff0c;和真实工作环境可能有出入 引言 postgresql与mysql目前都是非常受人欢迎的两大数据库&#xff0c;其各有各的优势&#xff0c;初学者先使用简单一张图来说明两者区别 以上内容引用自https://zhuanlan.zhihu.com/p/64326848…

Java 类之 java.util.Properties

Java 类之 java.util.Properties 文章目录 Java 类之 java.util.Properties一、简介二、主要功能1、存储键值对2、读取文件与属性代码示例运行结果截图 3、设置属性并保存文件代码示例结果截图 4、遍历属性代码示例运行结果 关联博客&#xff1a;《基于 Java 列举和说明常用的外…

程序员如何把【知识体系化】

你好&#xff0c;我是田哥 最近有不少人找我聊如何准备面试&#xff0c;其中有个点是大家都无从下手的问题。 这个问题估计是困扰了很多人&#xff0c;最可怕的是都没有想到什么好点办法。 下面来说说个人的想法&#xff08;仅供参考&#xff09;。 我该怎么准备&#xff1f;这…

JDK1.8 新特性(二)【Stream 流】

前言 上节我们学了 lambda 表达式&#xff0c;很快我就在 Flink 的学习中用到了&#xff0c;我学的是 Java 版本的 Flink&#xff0c;一开始会以为代码会很复杂&#xff0c;但事实上 Flink 中很多地方都用到了 函数接口&#xff0c;这也让我们在编写 Flink 程序的时候可以使用 …

upload-labs关卡9(基于win特性data流绕过)通关思路

文章目录 前言一、靶场需要了解的知识1::$data是什么 二、靶场第九关通关思路1、看源码2、bp抓包修改后缀名3、检查是否成功上传 总结 前言 此文章只用于学习和反思巩固文件上传漏洞知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去尚未授…

大模型之十二十-中英双语开源大语言模型选型

从ChatGPT火爆出圈到现在纷纷开源的大语言模型&#xff0c;众多出入门的学习者以及跃跃欲试的公司不得不面临的是开源大语言模型的选型问题。 基于开源商业许可的开源大语言模型可以极大的节省成本和加速业务迭代。 当前&#xff08;2023年11月17日)开源的大语言模型如下&#…

Devart dotConnect ADO.NET Data Providers Crack

开发数据相关 .NET 应用程序的终极解决方案&#xff1a;快速、灵活、全面、功能丰富、支持 ORM 的 ADO.NET 提供程序 概述 实体框架 连接字符串 博客 高性能 ADO.NET 数据提供程序 dotConnect 是基于 ADO.NET 架构和采用多项创新技术的开发框架构建的增强型数据连接解决方​​…

Pandas 求平均值

Pandas是Python中最流行的数据分析库之一&#xff0c;它提供了许多强大的工具来处理和分析数据集。其中&#xff0c;求平均值是数据分析中最常见的操作之一。在本文中&#xff0c;我们将从多个角度分析Pandas中如何求平均值。 一、基础操作 Pandas中求平均值的基础操作是使用m…