这是原篇教程,本篇为升级版,旧版已废弃。对你们不友好。
【Vue】vue2移动端 ,vant2使用van-tree-select分类选择实现【全选】和【取消全选】、【搜索过滤当前children】,只影响当前显示children,并且去重
由于项目又多了新功能,我发现之前写的van-tree-select全选反选组件,不友好。得改进一下。
效果如下。
van-tree-select实现全选反选组件效果实战案例
全部代码在总结,需要适当修改一点东西。就是获取列表那。不一定你的字段跟我的一样
我直接把代码全部贴出来。你们其实只需要改接口方法就好了。
后面我会一步步讲解
<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
代码如下(示例):
<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>
代码如下(示例):
export default {
name: "vinit",
components: {},
props: {},
data() {
return {
activeIndex: 0, // 左侧《下标
activeId: [], //右侧》列表选中项 ids数组
searchName: "", // 搜索过滤
list: [], // ----------------待选列表, 部门+人员子级 children嵌套
userListAll: [], // 所有子级项数组,用来筛选
};
},
由于,我有一个搜索输入框,我需要过滤的。
并且页面中渲染,也不能直接用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;
}
},
},
不是有选中项数组吗?
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里面。作为一个变量,有利于后面的全选反选方法。
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;
}
},
},
全选、反选、过滤、取消和确定,都不要动它。
<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就是绑定的对象数组,具体看下面疑惑解释
为什么getDeptList方法里面要写一个forEach循环?
因为我的res返回值,与vant所需要的对应不上,我的返回值label,是对应了element的tree组件,所以用text重新赋值。
computed中的filterUserList可以不封装吗?
它本质就是把一个方法的return数组,封装为变量,在全选和反选里面有用到,看起来更简洁点。
userListAll的作用
由于activeId是选中项id,我的父页面,需要的是对象数组。当我选中后,呈现的页面效果如下。
取消和确定的方法里的 $emit 是啥,没见过。
左边是方法名,右边是传递的参数。
这个是给父页面调用的,看父页面用法参数
这个v-model是什么原理?
当我的子页面调用this.$emit("input", this.activeList);
这段代码就是把值赋给input, 然后父页面只管v-model接收就好了
但是要记住,这个子页面的this.$emit("input", this.activeList);
得你自己触发调用。我的确定按钮是在子页面中的,所以我是点击了确定手动触发。
console.log("打印选择结果", logText(this.activeList));
由于数组或者对象打印,会显示小数点省略号
我封装了一个全局方法,专门用于打印,把它丢到main.js中就好了,随便你放哪一行
// 在main.js中定义全局方法
window.logText = function (value) {
return JSON.parse(JSON.stringify(value));
};
提示:这里对文章进行总结:
样式可以自由修改,我这里并没有适配的很完善。高度之类的。但是肯定,如果你也用vant组件,也是vue2项目,你可以尝试下我的这个二级选人组件。van-tree-select组件二次封装,有全选和反选。
我这里没有做主页面删除、然后子组件也跟着取消勾选。我觉得没必要、