事情是这样的产品经理需要实现一个类似于tree结构的人员选择功能,由于数据量过大,需要一级一级加载数据。效果如下:第一层加载完成以后,点击再加载它的子集,以此类推,直到人员
既然找不到合适的那就撸起袖子自己搞一个,毕竟咱的人生信条只有一个,那就是
第一:中国人办事向来是谋定而后动第一步当然是实现思路喽
首先是列表,少不了循环,其次是层级不定,异步加载数据,递归组件肯定是跑不了......。
额,编不下去了,废话少说,还是直接上代码比较实在
存放文件目录如下:
preRecursive.vue文件内容如下
<template>
<div style="height: 100%;">
<div class="opearte-box">
<van-button type="default" size="small" @click="closePop">取消</van-button>
<van-button type="primary" color="#1B82D1" size="small" @click="businessUserQd">确定</van-button>
</div>
<div class="user-select-area">
<div class="search-box1">
<i></i>
<input type="text" v-model="searchPersonText" placeholder="搜索人员">
<button type="button" @click="searchPerson">查询</button>
</div>
<div class="result-box">
<Recursion :list="personList" :multiple="multiple" @childEvent="getNextLevelData" />
</div>
</div>
</div>
</template>
<script>
/* eslint-disable */
import Recursion from './recursive.vue'
export default {
name: 'pre-recursive',
props: {
multiple: {
type: Boolean,
default: false
},
getUserList: {
type: Function
},
getSearchUserList: {
type: Function
}
},
data () {
return {
searchPersonText: '',
personList: [],
curItem: null,
curItemList: []
}
},
components: {
Recursion
},
mounted () {
const _this = this
_this.getUserList({}).then(res => {
_this.personList = res
})
},
methods: {
businessUserQd () {
const _this = this
let vals = _this.multiple ? _this.curItemList : _this.curItem
_this.$emit('childEvent', vals)
_this.closePop()
},
closePop () {
this.$emit('closePop')
},
// 将搜索返回的所有层级都展开
showAllLevel (arr) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].type === 1) {
arr[i].isDown = true
} else if (arr[i].type === 2) {
arr[i].isCheck = false
}
if (arr[i].children && arr[i].children.length > 0) {
this.showAllLevel(arr[i].children)
}
}
},
getNextLevelData (item) {
const _this = this
if (item.type === 1) {
// 箭头转换的样式
_this.closeArrow(item, _this.personList)
// 首次点击加载数据
if (item.children.length === 0) {
_this.getUserList(item).then(res => {
_this.recursiveData(item.id, _this.personList, res)
})
}
} else if (item.type === 2) {
// 区分单选和多选
if (_this.multiple) {
if (!item.isCheck) {
_this.curItemList.push(item)
} else {
_this.curItemList = _this.curItemList.filter( item1 => {
return item.id !== item1.id
})
}
_this.changeSelect1(item, _this.personList)
} else {
_this.curItem = item.isCheck ? null : item
_this.changeSelect(item, _this.personList)
}
}
},
// 收起当前箭头
closeArrow (item, arr) {
const _this = this
if (arr && arr.length > 0) {
let ids = []
let curIndex = 0
for (let i = 0; i < arr.length; i++) {
ids.push(arr[i].id)
if (item.id === arr[i].id) {
curIndex = i
}
}
if (ids.includes(item.id)) {
arr[curIndex].isDown = !item.isDown
} else {
for (let i = 0; i < arr.length; i++) {
_this.closeArrow(item, arr[i].children)
}
}
}
},
// 修改选中状态(单选)
changeSelect (item, arr) {
const _this = this
if (arr && arr.length > 0) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === item.id) {
arr[i].isCheck = !item.isCheck
} else {
arr[i].isCheck = false
}
if (arr[i].children && arr[i].children.length > 0) {
_this.changeSelect(item, arr[i].children)
}
}
}
},
// 修改选中状态(多选)
changeSelect1 (item, arr) {
const _this = this
if (arr && arr.length > 0) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === item.id) {
arr[i].isCheck = !item.isCheck
}
if (arr[i].children && arr[i].children.length > 0) {
_this.changeSelect1(item, arr[i].children)
}
}
}
},
// 递归返回的列表重新整合数据
recursiveData (id, arr, arr1) {
const _this = this
if (arr && arr.length > 0) {
let ids = []
let curIndex = 0
for (let i = 0; i < arr.length; i++) {
ids.push(arr[i].id)
if (id === arr[i].id) {
curIndex = i
}
}
// 可替代用数组的some()方法检测当前数组是否有想要的id
/*
let isHaveId = arr.some((item, index, array) => item.id === id)
*/
if (ids.includes(id)) {
arr[curIndex].children = arr1
} else {
for (let i = 0; i < arr.length; i++) {
_this.recursiveData(id, arr[i].children, arr1)
}
}
}
}
}
}
</script>
<style lang="less" scope>
.user-select-area {
padding: 0 16px;
height: calc(100% - 40px);
}
.opearte-box {
display: flex;
justify-content: space-between;
padding: 4px 16px;
}
.result-box {
margin-top: 10px;
height: calc(100% - 46px);
overflow: auto;
}
.search-box1 {
flex: 1;
display: flex;
align-items: center;
background: #F7F7F7 ;
border-radius: 18px;
padding-left: 10px;
height: 36px;
i {
display: inline-block;
width: 16px;
height: 16px;
background: url("./img/icon_baseSearch@2x.png") 0 0 no-repeat;
background-size: contain;
}
input {
width: calc(100% - 60px);
background: #F7F7F7;
margin-left: 5px;
border: none;
}
button {
width: 50px;
height: 30px;
border-radius: 18px;
color: #fff;
background: #1B82D1;
border: none;
}
}
</style>
递归组件recursive.vue内容如下:
<template>
<div>
<div class="item">
<ul>
<li v-for="(item, index) in list" :key="index">
<div class="title-style-ro" @click="getNextLevel(item)">
<i v-if="item.type === 1" class="arrow-icon" :class="item.isDown ? 'arrow-down-icon' : 'arrow-right-icon'"></i>
<template v-if="!multiple">
<i v-if="item.type === 2" class="radio-icon" :class="item.isCheck ? 'radio-yes-icon' : 'radio-no-icon'"></i>
</template>
<template v-else>
<i v-if="item.type === 2" class="radio-icon" :class="item.isCheck ? 'multiple-yes-icon' : 'multiple-no-icon'"></i>
</template>
<span>{{ item.type === 1 ? item.name : item.nameMobile }}</span>
</div>
<ul style="padding-left: 10px" v-show="item.children && item.type === 1 && item.isDown">
<li>
<index-chird :multiple="multiple" :list="item.children" @childEvent="getNextLevel"></index-chird>
</li>
</ul>
</li>
</ul>
</div>
</div>
</template>
<script>
/* eslint-disable */
export default {
name: 'index-chird',
props: {
list: Array,
multiple: Boolean
},
data () {
return {
// list: []
}
},
methods: {
getNextLevel (item) {
this.$emit('childEvent', item)
}
},
watch: {
list (newData) {
this.list = newData
}
}
}
</script>
<style lang="less" scope>
.title-style-ro {
height: 40px;
background: #FFFFFF;
box-shadow: inset 0px -1px 0px 0px #EEEEEE;
display: flex;
align-items: center;
i {
display: inline-block;
margin-right: 7px;
}
.arrow-icon {
width: 16px;
height: 16px;
}
.arrow-right-icon {
background: url("./img/caret-right@2x.png") 0 0 no-repeat;
background-size: contain;
}
.arrow-down-icon {
background: url("./img/caret-down-grey@2x.png") 0 0 no-repeat;
background-size: contain;
}
.radio-icon {
width: 12px;
height: 12px;
}
.radio-yes-icon {
background: url("./img/icon_round_checked@2x.png") 0 0 no-repeat;
background-size: contain;
}
.radio-no-icon {
background: url("./img/icon_round_normal@2x.png") 0 0 no-repeat;
background-size: contain;
}
.multiple-yes-icon {
background: url("./img/icon-checks-on.png") 0 0 no-repeat;
background-size: contain;
}
.multiple-no-icon {
background: url("./img/icon-checks.png") 0 0 no-repeat;
background-size: contain;
}
}
</style>
接下来就是如何调用该组件
<template>
<div class="example-area">
<div class="title title12">
<div>树状人员选择(默认单选)</div>
</div>
<div>
<van-button type="primary" color="#1B82D1" size="small" @click="showUserTree">人员选择</van-button>
</div>
<div class="show-list">
选中名单:<span>{{ personListStr }}</span>
</div>
<van-popup v-model="businessUserShow" position="bottom" :style="{ height: '70%' }">
<template v-if="businessUserShow">
<pre-recursive :multiple="multiple" :getUserList="getUserList" :getSearchUserList="getSearchUserList" @closePop="businessUserShow = false" @childEvent="getUserInfo"></pre-recursive>
</template>
</van-popup>
</div>
</template>
<script>
import preRecursive from './recursive/preRecursive.vue'
export default {
name: 'example-area',
data () {
return {
businessUserShow: false,
// 人员列表参数
multiple: false, // 人员列表参数,单选还是多选false为单选
personListStr: '',
// 人员选择数据要求
personListVal: [
{
children: [],
id: 112,
loginName: '',
lvl: 3,
mobile: '',
name: '杭州西湖分局',
nameMobile: '',
type: 1 // 是否是最后可选一级(1有子集可下拽)
},
{
children: [],
id: 33445043,
loginName: '18888888888',
lvl: 2,
mobile: '18888888888',
name: '萧峰',
nameMobile: '萧峰(18888888888)',
type: 2 // 是否是最后可选一级(2无子集不可下拽)
},
{
children: [],
id: 111,
loginName: '',
lvl: 3,
mobile: '',
name: '杭州上城一分局',
nameMobile: '',
type: 1
},
{
children: [],
id: 448,
loginName: '',
lvl: 3,
mobile: '',
name: '杭州钱塘分局',
nameMobile: '',
type: 1
},
{
children: [],
id: 3813808,
loginName: '17777777777',
lvl: 2,
mobile: '17777777777',
name: '虚竹',
nameMobile: '虚竹(17777777777)',
type: 2
},
{
children: [],
id: 3813809,
loginName: '16666666666',
lvl: 2,
mobile: '16666666666',
name: '段誉',
nameMobile: '段誉(16666666666)',
type: 2
}
],
// 人员搜索模拟数据
searchPersonListVal: [
{
children:[
{
children:[
{
children: [
{
children: [],
id: 3971617,
loginName: '',
lvl: 4,
mobile: "15555555555",
name: '周晨',
nameMobile: '周晨(15555555555)',
type: 2
}
],
id: 133875,
loginName: '',
lvl: 4,
mobile: '',
name: '余杭未来科技城支局',
nameMobile: '',
type: 1
}
],
id: 13,
loginName: '',
lvl: 3,
mobile: '',
name: '杭州余杭分公司',
nameMobile: '',
type: 1
},
{
children: [
{
children: [
{
children: [],
id: 32992687,
loginName: '',
lvl: 4,
mobile: '13355555555',
name: '周晨霞',
nameMobile: '周晨霞(13355555555)',
type: 2
}
],
id: 656,
loginName: '',
lvl: 4,
mobile: '',
name: '淳安汾口支局',
nameMobile: '',
type: 1
}
],
id: 640,
loginName: '',
lvl: 3,
mobile: '',
name: '杭州淳安分公司',
nameMobile: '',
type: 1
}
],
id: 3772268,
loginName: '',
lvl: 2,
mobile: '',
name: '杭州分公司',
nameMobile: '',
type: 1
}
]
}
},
components: {
preRecursive
},
methods: {
getUserInfo (val) {
console.log(val)
this.personListStr = ''
if (this.multiple) {
if (val && val.length !== 0) {
for (let i = 0; i < val.length; i++) {
this.personListStr += val[i].nameMobile
}
}
} else {
if (val) {
this.personListStr = val.nameMobile
}
}
console.log(this.personListStr, 4576)
},
showUserTree () {
this.businessUserShow = true
},
// 人员获取接口
getUserList (item) {
console.log(item) // item为请求下级数据时候要传入的参数
const _this = this
// 需要使用promiss将数据异步回调到子组件
return new Promise((resolve, reject) => {
// 这里写请求数据,下面注释的为模拟返回的结果
// let list = JSON.parse(JSON.stringify(_this.personListVal)) // 实际开发中后台返回的数据不需要转
// list.forEach(element => {
// if (element.type === 1) {
// element.isDown = false
// } else if (element.type === 2) {
// element.isCheck = false
// }
// })
// resolve(list)
})
},
// 外呼人员查询
getSearchUserList (searchText) {
console.log(searchText) // searchText为请求搜索人员参数
const _this = this
return new Promise((resolve, reject) => {
// 这里写请求数据,下面注释的为模拟返回的结果
// let list = JSON.parse(JSON.stringify(_this.searchPersonListVal)) // 实际开发中后台返回的数据不需要转
//resolve(list)
})
},
}
}
</script>
<style scoped lang="less">
.example-area {
width: 100%;
height: 100%;
overflow: auto;
.title {
font-size: 14px;
padding: 10px 5px;
}
.title12 {
display: flex;
align-items: center;
}
}
.table-area {
height: 300px;
}
.area-select-box {
height: 50px;
}
.show-list {
padding: 5px;
}
</style>
如果multiple参数传false则为单选点击确定后返回为对象效果如下:
如果multiple参数传tree则为多选点击确定后返回为数组效果如下:
可能大家有个疑问为啥搜索结果要另外写一个接口不放在一起呢,不就是一个参数的问题吗?当时后端小哥能力有限,只能分开,为了不难为他,我只能委屈自己!