首页 前端知识 【Vue】vant2使用van-tree-select实现【全选、反选、搜索】,自定义组件,拿去即用。2.0版本保姆级教程

【Vue】vant2使用van-tree-select实现【全选、反选、搜索】,自定义组件,拿去即用。2.0版本保姆级教程

2024-07-27 22:07:14 前端知识 前端哥 973 718 我要收藏

系列文章目录

这是原篇教程,本篇为升级版,旧版已废弃。对你们不友好。
【Vue】vue2移动端 ,vant2使用van-tree-select分类选择实现【全选】和【取消全选】、【搜索过滤当前children】,只影响当前显示children,并且去重


文章目录

  • 系列文章目录
  • 前言
  • 一、写在前面
  • 二、使用步骤
    • 1.html代码
    • 2.data变量
    • 3. filters过滤器
    • 4.compted计算属性
    • 5. methods方法
    • **`全选、反选、过滤、取消和确定,都不要动它。`**
    • 6.父页面用法引入
  • 解答疑惑
  • 总结


前言

由于项目又多了新功能,我发现之前写的van-tree-select全选反选组件,不友好。得改进一下。
效果如下。

van-tree-select实现全选反选组件效果实战案例


全部代码在总结,需要适当修改一点东西。就是获取列表那。不一定你的字段跟我的一样

一、写在前面

我直接把代码全部贴出来。你们其实只需要改接口方法就好了。

后面我会一步步讲解

<!--
 * @Author: guo-bomin 2974463764@qq.com
 * @Date: 2023-11-07 16:38:51
 * @LastEditors: guo-bomin 2974463764@qq.com
 * @LastEditTime: 2023-11-07 17:51:03
 * @FilePath: \wuzhihua-app\src\components\ChoseDept\index.vue
 * @Description: 郭博民16670506200
 * 有问题微信同号询问,急事call我
 * Copyright (c) 2023 by 湖南習羽科技网络有限公司, All Rights Reserved. 
-->

<template>
  <div class="showUser">
    <h4 @click="getDeptList">请选择部门</h4>
    <van-search v-model="searchName" :placeholder="`请输入`" />
    <van-loading size="24px" v-if="list.length == 0">加载中...</van-loading>
    <div class="submit">
      <van-button type="info" plain size="small" @click="onSelectAll"
        >全选</van-button
      >
      <van-button
        type="warning"
        plain
        size="small"
        color="#999"
        @click="onClearAll"
        >取消全选</van-button
      >
    </div>
    <van-tree-select
      :items="list | newUserList(searchName)"
      :active-id.sync="activeId"
      :main-active-index.sync="activeIndex"
    />
    <div class="submit">
      <van-button type="default" @click="onCancel">取消</van-button>
      <van-button type="info" @click="onConfirm">确定</van-button>
    </div>
  </div>
</template>

<script>
import { mainDepts } from "@/api/flow/common.js";

export default {
  name: "vinit",
  components: {},
  props: {},
  data() {
    return {
      activeIndex: 0, // 左侧《下标
      activeId: [], //右侧》列表选中项 ids数组
      searchName: "", // 搜索过滤
      list: [], // ----------------待选列表, 部门+人员子级 children嵌套
      flowList: [], // 正在处理人员,用于禁选

      userListAll: [], // 所有子级项数组,用来筛选
      mainDept: null, // 当前用户部门信息
    };
  },
  computed: {
    // 用来返回到父页面
    activeList() {
      let selectedData = [];
      if (Array.isArray(this.activeId)) {
        selectedData = this.activeId.map((id) => {
          // 通过 id 查找对应的数据
          return this.userListAll.find((data) => data.id == id);
        });
      }
      return selectedData;
    },
    // 过滤后的右侧人员列表
    filterUserList() {
      return this.filterNewUserList(this.list, this.searchName);
    },
  },
  filters: {
    // 过滤选择人员
    newUserList(list, searchName) {
      let arr = [];
      if (searchName != "") {
        list.forEach((item1, index1) => {
          arr.push({
            text: item1.text,
            children: [],
          });
          item1.children.forEach((item2) => {
            if (item2.text.toLowerCase().includes(searchName.toLowerCase())) {
              arr[index1].children.push({
                id: item2.id,
                disabled: item2.disabled,
                text: item2.text,
              });
            }
          });
        });
        return arr;
      } else {
        return list;
      }
    },
  },
  watch: {},
  created() {},
  mounted() {
    this.init();
  },
  methods: {
    init() {
      this.getDeptList(); // 获取部门列表
    },
    // 全选
    onSelectAll() {
      const currentChildren =
        this.filterUserList[this.activeIndex]?.children || [];
      const selectedIdsSet = new Set(this.activeId);

      currentChildren.forEach((item) => {
        if (!item.disabled) {
          selectedIdsSet.add(item.id);
        }
      });
      this.activeId = Array.from(selectedIdsSet);
    },
    // 清空当前页全选
    onClearAll() {
      const currentChildren =
        this.filterUserList[this.activeIndex]?.children || [];
      const selectedIdsSet = new Set(this.activeId);
      currentChildren.forEach((item) => {
        selectedIdsSet.delete(item.id);
      });
      this.activeId = Array.from(selectedIdsSet);
    },
    // 取消
    onCancel() {
      this.$emit("onCancel");
    },
    // 确定
    onConfirm() {
      this.$emit("input", this.activeList);
      this.$emit("onConfirm", this.activeList);
    },
    // 获取部门列表
    getDeptList() {
      mainDepts().then((res) => {
        console.log(`res -->`, logText(res));
        let allData = {
          id: "-1",
          text: "全部",
          children: [],
        };
        let data = res.data;
        // 将label赋值给text
        data.forEach((item) => {
          item.text = item.label;
          if (item.children) {
            item.children.forEach((child) => {
              child.text = child.label;
              allData.children.push(child);
              this.userListAll.push(child);
            });
          }
        });
        data.unshift(allData);
        this.list = data;
      });
    },
    // 搭配过滤使用
    filterNewUserList(list, searchName) {
      let arr = [];
      if (searchName !== "") {
        list.forEach((item1, index1) => {
          arr.push({
            text: item1.text,
            children: [],
          });
          item1.children.forEach((item2) => {
            if (item2.text.toLowerCase().includes(searchName.toLowerCase())) {
              arr[index1].children.push({
                id: item2.id,
                disabled: item2.disabled,
                text: item2.text,
              });
            }
          });
        });
        return arr;
      } else {
        return list;
      }
    },
  },
};
</script>

<style scoped lang="less">
.showUser {
  height: 100vh;
  box-sizing: border-box;
  padding: 30px 0;

  h4 {
    margin-bottom: 30px;
    padding-left: 30px;
    font-size: 30px;
  }

  // 选择器
  .van-tree-select {
    margin-top: 20px;
    height: 70% !important;

    .van-sidebar-item--select::before {
      background-color: #418af1;
    }

    .van-tree-select__item--active {
      color: #418af1;
    }
  }

  .submit {
    width: 100%;
    display: flex;
    justify-content: space-around;
    margin: 20px 0;

    .van-button {
      width: 150px;
    }
  }
}
</style>

注意,我的接口方法是对原来的返回数组进行了修改

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

van-tree-select是需要text、id、children

二、使用步骤

1.html代码

代码如下(示例):

<template>
  <div class="showUser">
    <h4 @click="getDeptList">请选择部门</h4>
    <van-search v-model="searchName" :placeholder="`请输入`" />
    <van-loading size="24px" v-if="list.length == 0">加载中...</van-loading>
    <div class="submit">
      <van-button type="info" plain size="small" @click="onSelectAll"
        >全选</van-button
      >
      <van-button
        type="warning"
        plain
        size="small"
        color="#999"
        @click="onClearAll"
        >取消全选</van-button
      >
    </div>
    <van-tree-select
      :items="list | newUserList(searchName)"
      :active-id.sync="activeId"
      :main-active-index.sync="activeIndex"
    />
    <div class="submit">
      <van-button type="default" @click="onCancel">取消</van-button>
      <van-button type="info" @click="onConfirm">确定</van-button>
    </div>
  </div>
</template>

2.data变量

代码如下(示例):

export default {
  name: "vinit",
  components: {},
  props: {},
  data() {
    return {
      activeIndex: 0, // 左侧《下标
      activeId: [], //右侧》列表选中项 ids数组
      searchName: "", // 搜索过滤
      list: [], // ----------------待选列表, 部门+人员子级 children嵌套
      userListAll: [], // 所有子级项数组,用来筛选
    };
  },

3. filters过滤器

由于,我有一个搜索输入框,我需要过滤的。
在这里插入图片描述
并且页面中渲染,也不能直接用list,不然起不到过滤的作用。
过滤器时传了一个参数,我不知道为啥,在filters中不能用this.searchName,反正我就写到过滤器里面了。其实拿出来,丢到methods里面效果一样。

避免methods复杂,我这里就归类到filters中了

filters: {
  // 过滤选择人员
  newUserList(list, searchName) {
    let arr = [];
    if (searchName != "") {
      list.forEach((item1, index1) => {
        arr.push({
          text: item1.text,
          children: [],
        });
        item1.children.forEach((item2) => {
          if (item2.text.toLowerCase().includes(searchName.toLowerCase())) {
            arr[index1].children.push({
              id: item2.id,
              disabled: item2.disabled,
              text: item2.text,
            });
          }
        });
      });
      return arr;
    } else {
      return list;
    }
  },
},

4.compted计算属性

  • 我需要计算,选中的人员列表,当然你会说,不是有选中项数组吗?
  • 可那个是id数组,我需要的是对象数组,我除了id,我还需要它的text值。用作回显。
  computed: {
    // 用来返回到父页面
    activeList() {
      let selectedData = [];
      if (Array.isArray(this.activeId)) {
        selectedData = this.activeId.map((id) => {
          // 通过 id 查找对应的数据
          return this.userListAll.find((data) => data.id == id);
        });
      }
      return selectedData;
    },
    // 过滤后的右侧人员列表
    filterUserList() {
      return this.filterNewUserList(this.list, this.searchName);
    },
  },

filterUserList 这个是列表中右侧的人员列表
在这里插入图片描述
它搭配了一个filterNewUserList使用,它的作用是啥呢?

过滤右侧、通过搜索。

假设右侧有10个数据,我搜索输入之后,只有5个,当我点击全选,我需要选中的是这5个,不是10个,所以它就是这个作用,但是它是个方法,为了方便,把它丢到computed里面。作为一个变量,有利于后面的全选反选方法。

5. methods方法

methods: {
    init() {
      this.getDeptList(); // 获取部门列表
    },
    // 全选
    onSelectAll() {
      const currentChildren =
        this.filterUserList[this.activeIndex]?.children || [];
      const selectedIdsSet = new Set(this.activeId);

      currentChildren.forEach((item) => {
        if (!item.disabled) {
          selectedIdsSet.add(item.id);
        }
      });
      this.activeId = Array.from(selectedIdsSet);
    },
    // 清空当前页全选
    onClearAll() {
      const currentChildren =
        this.filterUserList[this.activeIndex]?.children || [];
      const selectedIdsSet = new Set(this.activeId);
      currentChildren.forEach((item) => {
        selectedIdsSet.delete(item.id);
      });
      this.activeId = Array.from(selectedIdsSet);
    },
    // 取消
    onCancel() {
      this.$emit("onCancel");
    },
    // 确定
    onConfirm() {
      this.$emit("input", this.activeList);
      this.$emit("onConfirm", this.activeList);
    },
    // 获取部门列表
    getDeptList() {
      mainDepts().then((res) => {
        console.log(`res -->`, logText(res));
        let allData = {
          id: "-1",
          text: "全部",
          children: [],
        };
        let data = res.data;
        // 将label赋值给text
        data.forEach((item) => {
          item.text = item.label;
          if (item.children) {
            item.children.forEach((child) => {
              child.text = child.label;
              allData.children.push(child);
              this.userListAll.push(child);
            });
          }
        });
        data.unshift(allData);
        this.list = data;
      });
    },
    // 搭配过滤使用
    filterNewUserList(list, searchName) {
      let arr = [];
      if (searchName !== "") {
        list.forEach((item1, index1) => {
          arr.push({
            text: item1.text,
            children: [],
          });
          item1.children.forEach((item2) => {
            if (item2.text.toLowerCase().includes(searchName.toLowerCase())) {
              arr[index1].children.push({
                id: item2.id,
                disabled: item2.disabled,
                text: item2.text,
              });
            }
          });
        });
        return arr;
      } else {
        return list;
      }
    },
  },

全选、反选、过滤、取消和确定,都不要动它。

6.父页面用法引入

    <!-- 选择人员弹出框 -->
    <van-popup
      v-model="showChose"
      closeable
      close-icon="close"
      position="right"
      :style="{ height: '100%', width: '100%' }"
    >
      <ChoseDept
        v-model="activeList"
        @onCancel="showChose = false"
        @onConfirm="onConfirmChose"
      ></ChoseDept>
    </van-popup>

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

activeList就是绑定的对象数组,具体看下面疑惑解释

解答疑惑

  1. 为什么getDeptList方法里面要写一个forEach循环?
    因为我的res返回值,与vant所需要的对应不上,我的返回值label,是对应了element的tree组件,所以用text重新赋值。

  2. computed中的filterUserList可以不封装吗?
    它本质就是把一个方法的return数组,封装为变量,在全选和反选里面有用到,看起来更简洁点。

  3. userListAll的作用
    由于activeId是选中项id,我的父页面,需要的是对象数组。当我选中后,呈现的页面效果如下。
    在这里插入图片描述

  4. 取消和确定的方法里的 $emit 是啥,没见过。
    在这里插入图片描述
    左边是方法名,右边是传递的参数。
    这个是给父页面调用的,看父页面用法参数
    在这里插入图片描述

这个v-model是什么原理?
当我的子页面调用 this.$emit("input", this.activeList);
这段代码就是把值赋给input, 然后父页面只管v-model接收就好了

但是要记住,这个子页面的this.$emit("input", this.activeList); 得你自己触发调用。我的确定按钮是在子页面中的,所以我是点击了确定手动触发。

  1. 为啥我有了v-model还需要一个onConfirm方法呢?
    你需要在父页面,关闭van-popup弹窗
  2. logText方法是什么?为什么它打印出来的数组,没有显示…省略号
console.log("打印选择结果", logText(this.activeList));

由于数组或者对象打印,会显示小数点省略号
我封装了一个全局方法,专门用于打印,把它丢到main.js中就好了,随便你放哪一行

// 在main.js中定义全局方法
window.logText = function (value) {
  return JSON.parse(JSON.stringify(value));
};

总结

提示:这里对文章进行总结:

样式可以自由修改,我这里并没有适配的很完善。高度之类的。但是肯定,如果你也用vant组件,也是vue2项目,你可以尝试下我的这个二级选人组件。van-tree-select组件二次封装,有全选和反选。

我这里没有做主页面删除、然后子组件也跟着取消勾选。我觉得没必要、

转载请注明出处或者链接地址:https://www.qianduange.cn//article/14425.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!