vue组件递归遍历
实现遍历效果。
·
参考vue文件地址:递归组件
实现遍历效果
递归组件
实现思路:
- 遍历数组元素,并判断子元素的子集数量是否大于0。
- 如果子集的集合数量大于0,则需要需要再一次调用该组件。
- 如果子集的集合数量等于0,则直接显示内容。
创建组件
<!-- subordinate:判断是否为子集递归; activeNames:打开折叠面板的集合。 -->
<van-collapse v-if="!subordinate" v-model="activeNames">
<!-- 遍历数组 -->
<van-collapse-item v-for="(item, index) in list" :key="index" :name="item.id" :border="false">
<!-- 折叠面板的title数据 -->
<template slot="title">
<div class="checkBox">
<span class="iconfont active" v-if="item.checked" @click.stop.prevent="clickBoxFn(item, item.id)"></span>
<span class="noactive" v-else @click.stop.prevent="clickBoxFn(item, item.id)"></span>
<div @click.stop.prevent="openForm(item)" class="checktypeName first">{{item.title}}</div>
</div>
</template>
<!-- 遍历当前元素的子集元素 -->
<div class="cheackBoxList" v-for="c of item.children" :key="c.id">
<!-- 判断子集元素的子集集合长度是否大于0,如果大于0则需要重新调用当前组件 -->
<div v-if="c.children.length > 0">
<!-- editfn: 编辑事件,点击title文字,会打开编辑框,编辑当前数据
changebox: 复选框选中的change事件,复选框的切换会调用该事件。 -->
<category-item
:list="c.children"
:subordinate="true"
:parent="c"
:activeNameCopy="activeNames"
@editfn="openForm"
@changebox="clickBox"
></category-item>
</div>
<!-- 不需要再次递归的情况,在这里展示内容 -->
<div class="checkBox" v-else>
<van-checkbox v-model="c.checked" shape="square" @change="clickBox('', c.pid)"></van-checkbox>
<div @click="openForm(c)" class="checktypeName">{{c.title}}</div>
</div>
</div>
</van-collapse-item>
</van-collapse>
<!-- 子组件需要再次递归当前组件 -->
<van-collapse v-else v-model="activeChildrenNames">
<!-- 显示当前父级的内容,否则父级内容会直接跳过 -->
<van-collapse-item :name="parent.id" :border="false">
<template slot="title">
<div class="checkBox">
<span class="iconfont active" v-if="parent.checked" @click.stop.prevent="clickBoxFn(parent, parent.id)"></span>
<span class="noactive" @click.stop.prevent="clickBoxFn(parent, parent.id)" v-else></span>
<div @click.stop.prevent="openForm(parent)" class="checktypeName">{{parent.title}}</div>
</div>
</template>
<!-- 遍历当前元素的子集元素 -->
<div class="cheackBoxList" v-for="c of list" :key="c.id">
<!-- 判断子集元素的子集集合长度是否大于0,如果大于0且count小于5则需要重新调用当前组件 -->
<div v-if="c.children.length > 0 && c.count < 5">
<category-item
:list="c.children"
:subordinate="true"
:parent="c"
:activeNameCopy="activeChildrenNames"
@editfn="openForm"
@changebox="clickBox"
></category-item>
</div>
<!-- 不需要再次递归的情况,在这里展示内容 -->
<div class="checkBox" v-else>
<van-checkbox v-model="c.checked" shape="square" @change="clickBox('', c.pid)"></van-checkbox>
<div @click="openForm(c)" class="checktypeName">{{c.title}}</div>
<van-icon
v-if="c.children.length > 0 && c.count === 5"
class="tip_arrow"
name="arrow-down"
size="0.32rem"
@click="alertTip"
/>
</div>
</div>
</van-collapse-item>
</van-collapse>
export default {
// 当前组件名称
name: 'category-item',
/**
* list: 当前组件接收的数据源
* parent: 父级对象数据
* subordinate: 是否子集调用
* activeNameCopy: 打开的折叠面板
*/
props: ['list', 'parent', 'subordinate', 'activeNameCopy'],
data () {
return {
}
},
methods: {
}
}
说明:首次递归和后面的递归的不同
- 首次递归没有父级元素,直接渲染当前元素的title ,判断子元素是否需要再次递归当前组件即可。但是递归组件,在递归遍历儿子组件的时候不仅仅要渲染孙子元素,也要将当前儿子元素的内容展示出来,然后将孙子元素递归的结果作为儿子元素的内容。
- 设置了 subordinate和 parent,subordinate是用来判断是否为子元素递归组件,从而采用不同的渲染方式;parent用于接收父元素的对象,如果是子集递归需要用到。
递归方法
遍历数组,如果子集的子集集合数量大于0,则继续调用当前函数
/**
* 数据递归转换 : 接收参数list, 循环遍历,如果有子集的长度大于0则重复调用
* @param {*} list 闭包轮询数据源
* @param {*} callback 回调函数
* @param {*} parentObj 父级的对象信息
* @param {*} count 层级,默认从1开始
*/
changeListData (list, callback, parentObj = {}, count = 1) {
let arr = list.map(item => {
let children = item.children
let parent = { id: item.id, title: item.title, checked: item.checked }
if (children.length > 0) {
// 重复调用
children = this.changeListData(children, callback, parent, count + 1)
}
if (callback) callback(item.id)
return { ...item, children, checked: this.checkedAll, pid: parentObj.id, count }
})
return arr
},
父组件和子组件的源码
父组件
<template>
<div class="content">
<!-- <div class="search">
<van-cell-group>
<van-field
clearable
@click-left-icon="search"
left-icon="search"
placeholder="搜索关键词"
/>
</van-cell-group>
</div> -->
<div class="category_items">
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
<category-item
:list="list"
ref="categoryitem"
@editfn="openForm"
@changebox="clickBox"
></category-item>
</van-pull-refresh>
</div>
<div class="addBox" >
<div @click="openForm(null)" class="add">
<van-icon name="plus" />
</div>
</div>
<div class="actionBar">
<div class="actionBarList">
<van-checkbox
v-model="checkedAll"
@change="checkAllFn"
shape="square">
全选
</van-checkbox>
</div>
<div class="actionBarList detele">
<span v-if="delIds.length > 0" @click="showDelDialog = true">删除</span>
</div>
<div class="actionBarList" @click="showOptionFn" :class="{color1:active}">
<div>更多</div>
<div class="iconfont">{{active?'':''}}</div>
</div>
<van-overlay :show="showOption" class-name="overlay_customer" @click="showOptionFn">
<transition name="van-fade">
<div class="options" v-show="active">
<span class="option" @click="onSelect('isRelation', true)">父子关联</span>
<span class="option" @click="onSelect('isRelation', false)">取消关联</span>
<span class="option" @click="onSelect('openAll', true)">展开所有</span>
<span class="option" @click="onSelect('openAll', false)">合并所有</span>
</div>
</transition>
</van-overlay>
</div>
<commodity-category-form
ref="form"
:dialogTitle="itemName"
:is-edit="isEdit"
:activeNames="activeNames"
@save="addData"
/>
<van-dialog
class="dialog_del"
v-model="showDelDialog"
show-cancel-button
@cancel="showDelDialog = false"
@confirm="delItems"
>
<div class="tip">
确定删除所选中的{{delIds.length}}条数据吗?
</div>
</van-dialog>
</div>
</template>
<script>
import * as dd from 'dingtalk-jsapi'
import { Toast, Dialog } from 'vant'
import { getMaterialCategoryTree, materialCategoryAdd, updateItem, deleteBatch } from '@/api/commodityCategory'
import CommodityCategoryForm from './commodityCategoryForm.vue'
import CategoryItem from './categoryItem.vue'
export default {
components: { CommodityCategoryForm, CategoryItem },
data () {
return {
checkedAll: false, // 全选
openAll: true, // 展开所有
isRelation: true, // 是否父子关联
isEdit: false, // 是否为编辑
delIds: [], // 删除的数据的ids
selectBoxs: [], // 当前选中的数据
active: false, // 是否打开更多
isLoading: false, // 下拉加载loading
itemName: '', // 编辑的类别名称
activeNames: [], // 当前选中的打开的面板集合
list: [], // 数据源
showDelDialog: false, // 打开提示框
showOption: false // 打开操作框
}
},
mounted () {
this.global.isShowTabr = false
this.getData()
},
computed: {
isDingDing () {
return dd.env.platform === 'ios' || dd.env.platform === 'android'
}
},
methods: {
/** 获取树级数据 */
getData () {
getMaterialCategoryTree({id: ''}).then(res => {
if (res && res.length > 0) {
this.list = this.changeListData(res)
this.openOrMergeAll()
}
})
},
showOptionFn () {
this.active = !this.active
this.showOption = !this.showOption
},
/** 全选和反选 */
checkAllFn () {
// const flag = this.checkedAll
this.list = this.changeListData(this.list)
},
/** 展开/合并所有子集菜单 */
openOrMergeAll () {
const flag = this.openAll
this.activeNames = []
if (flag) {
this.changeListData(this.list, (id) => {
this.activeNames.push(id)
})
} else this.activeNames = []
this.$refs['categoryitem'].activeNames = this.activeNames
this.$refs['categoryitem'].activeChildrenNames = this.activeNames
this.$refs['categoryitem'].$forceUpdate()
},
/** 点击复选框 */
clickBox (id, pid) {
if (this.isRelation) {
// 处理复选框点击后选中的结果
this.list = this.setListCheck(this.list, id, pid)
// 如果存在即是子集又是父级的元素点击,那么这个方法就是处理选中后各个复选框的选择结果
this.list = this.setAllParentCheck(this.list)
}
// 获取所有复选框中选中的id
this.delIds = this.getAllCheck(this.list)
},
// 查询所有数据
// 1. 判断当前是否父子关联
// 2. 只改子集,传入父级的id,查询该父级下面的所有chilren,然后将其所有子集状态修改为和父级状态相同
// 3. 只改父级,传入pid,根据pid查询当前父级的数据,然后判断其所有子元素是否全部选中,如果全部选中,则该父元素的状态选中,否则状态为false.
/**
* 修改数据
* @param {*} list 数据源
* @param {*} id 父级的id(父级传入的id)
* @param {*} pid 父级id(子集的pid)
*/
setListCheck (list, id, pid) {
const arr = list.map(item => {
let children = item.children
let checked = item.checked
// 按照父级的id查询修改子集数据
if (id && item.id === id) {
children = children.map(c => {
// eslint-disable-next-line camelcase
let c_children = c.children
// eslint-disable-next-line camelcase
if (c_children && c_children.length > 0) {
// eslint-disable-next-line camelcase
c_children = this.setAllItemCheck(c_children, checked)
}
return { ...c, children: c_children, checked }
})
} else if (id && item.children && item.children.length > 0) {
// 轮询根据父级的id查询并修改子集数据
children = this.setListCheck(item.children, id, pid)
} else if (pid && item.id === pid) {
// 根据子集传入的pid,找到该父级及其所有的子集,判断所有子集是否被选中
const flag = children.every(c => c.checked === true)
// 修改其父级的状态
checked = flag
} else if (pid && item.children && item.children.length > 0) {
// 轮询根据子集的传入的pid查询并修改父级的状态
children = this.setListCheck(item.children, id, pid)
} else {
console.log('边界情况没查到')
}
return { ...item, children, checked }
})
return arr
},
/**
* 设置所有子集的状态一致,主要用于,一级菜单被选中,所有子集及其子集的子集也需要被选中
* @param {*} list 数据源
* @param {*} flag 状态值
*/
setAllItemCheck (list, flag) {
const arr = list.map(item => {
let children = item.children
if (children && children.length > 0) {
children = this.setAllItemCheck(children, flag)
}
return { ...item, checked: flag, children }
})
return arr
},
// 4. 查询所有数据,从最后一级开始判断,如果所有子集的状态都为true,则父级的状态为true,否则为false
setAllParentCheck (list) {
const arr = list.map(item => {
let children = item.children
let checked = item.checked
if (children && children.length > 0) {
children = this.setAllParentCheck(children)
checked = children.every(c => c.checked === true)
}
return { ...item, checked, children }
})
return arr
},
/**
* 数据递归转换 : 接收参数list, 循环遍历,如果有子集的长度大于0则重复调用
* @param {*} list 闭包轮询数据源
* @param {*} callback 回调函数
* @param {*} parentObj 父级的对象信息
* @param {*} count 层级,默认从1开始
*/
changeListData (list, callback, parentObj = {}, count = 1) {
let arr = list.map(item => {
let children = item.children
let parent = { id: item.id, title: item.title, checked: item.checked }
if (children.length > 0) {
// 重复调用
children = this.changeListData(children, callback, parent, count + 1)
}
if (callback) callback(item.id)
return { ...item, children, checked: this.checkedAll, pid: parentObj.id, count }
})
return arr
},
/**
* 获取所有被选中的id
* @param {*} list 数据源
*/
getAllCheck (list) {
let arr = []
let childrenArr = []
list.map(item => {
if (item.checked) arr.push(item.id)
if (item.children && item.children.length > 0) {
const newArr = this.getAllCheck(item.children)
childrenArr = [...childrenArr, ...newArr]
}
})
return [...arr, ...childrenArr]
},
/** 添加数据 */
addData (form) {
if (this.isEdit) {
updateItem(form).then(res => {
const { code } = res
if (code === 200) {
Toast.success('修改成功!')
this.isClose = false
this.getData()
this.$refs['form'].form = {}
this.$refs['form'].categoryName = ''
}
}).catch(e => {
Toast.fail(JSON.stringify(e))
})
} else {
materialCategoryAdd(form).then(res => {
const { code } = res
if (code === 200) {
Toast.success('添加成功!')
this.isClose = false
this.getData()
this.$refs['form'].form = {}
this.$refs['form'].categoryName = ''
}
}).catch(e => {
Toast.fail(JSON.stringify(e))
})
}
},
/** 批量删除 */
delItems () {
if (this.delIds.length === 0) return
deleteBatch({ ids: this.delIds.join(',') + ',' }).then(res => {
const { code, data } = res
if (code === 200) {
Toast.success('删除成功!')
this.getData()
this.delIds = []
} else {
Dialog.alert({
title: '删除失败',
message: data.message || data,
confirmButtonColor: '#1890FF'
}).then(() => {
// on close
})
}
})
},
/** 打开form表单 */
openForm (item = {}) {
if (item && item.id) {
this.isEdit = true
this.$refs['form'].getItem(item.id)
} else this.isEdit = false
this.$refs['form'].Editdialog = true
// eslint-disable-next-line no-mixed-operators
this.$refs['form'].getMaterialCategoryTree(item && item.id || '')
},
/**
* 操作
* @param {*} code 改变的变量
* @param {*} flag 变量的布尔值
*/
onSelect (code, flag) {
this[code] = flag
if (code === 'openAll') {
this.openOrMergeAll()
}
},
onRefresh () {
setTimeout(() => {
this.getData()
this.isLoading = false
}, 1000)
}
}
}
</script>
子组件
<template>
<div>
<!-- subordinate:判断是否为子集递归; activeNames:打开折叠面板的集合。 -->
<van-collapse v-if="!subordinate" v-model="activeNames">
<!-- 遍历数组 -->
<van-collapse-item v-for="(item, index) in list" :key="index" :name="item.id" :border="false">
<!-- 折叠面板的title数据 -->
<template slot="title">
<div class="checkBox">
<span class="iconfont active" v-if="item.checked" @click.stop.prevent="clickBoxFn(item, item.id)"></span>
<span class="noactive" v-else @click.stop.prevent="clickBoxFn(item, item.id)"></span>
<div @click.stop.prevent="openForm(item)" class="checktypeName first">{{item.title}}</div>
</div>
</template>
<!-- 遍历当前元素的子集元素 -->
<div class="cheackBoxList" v-for="c of item.children" :key="c.id">
<!-- 判断子集元素的子集集合长度是否大于0,如果大于0则需要重新调用当前组件 -->
<div v-if="c.children.length > 0">
<!-- editfn: 编辑事件,点击title文字,会打开编辑框,编辑当前数据
changebox: 复选框选中的change事件,复选框的切换会调用该事件。 -->
<category-item
:list="c.children"
:subordinate="true"
:parent="c"
:activeNameCopy="activeNames"
@editfn="openForm"
@changebox="clickBox"
></category-item>
</div>
<!-- 不需要再次递归的情况,在这里展示内容 -->
<div class="checkBox" v-else>
<van-checkbox v-model="c.checked" shape="square" @change="clickBox('', c.pid)"></van-checkbox>
<div @click="openForm(c)" class="checktypeName">{{c.title}}</div>
</div>
</div>
</van-collapse-item>
</van-collapse>
<!-- 子组件需要再次递归当前组件 -->
<van-collapse v-else v-model="activeChildrenNames">
<!-- 显示当前父级的内容,否则父级内容会直接跳过 -->
<van-collapse-item :name="parent.id" :border="false">
<template slot="title">
<div class="checkBox">
<span class="iconfont active" v-if="parent.checked" @click.stop.prevent="clickBoxFn(parent, parent.id)"></span>
<span class="noactive" @click.stop.prevent="clickBoxFn(parent, parent.id)" v-else></span>
<div @click.stop.prevent="openForm(parent)" class="checktypeName">{{parent.title}}</div>
</div>
</template>
<!-- 遍历当前元素的子集元素 -->
<div class="cheackBoxList" v-for="c of list" :key="c.id">
<!-- 判断子集元素的子集集合长度是否大于0,如果大于0且count小于5则需要重新调用当前组件 -->
<div v-if="c.children.length > 0 && c.count < 5">
<category-item
:list="c.children"
:subordinate="true"
:parent="c"
:activeNameCopy="activeChildrenNames"
@editfn="openForm"
@changebox="clickBox"
></category-item>
</div>
<!-- 不需要再次递归的情况,在这里展示内容 -->
<div class="checkBox" v-else>
<van-checkbox v-model="c.checked" shape="square" @change="clickBox('', c.pid)"></van-checkbox>
<div @click="openForm(c)" class="checktypeName">{{c.title}}</div>
<van-icon
v-if="c.children.length > 0 && c.count === 5"
class="tip_arrow"
name="arrow-down"
size="0.32rem"
@click="alertTip"
/>
</div>
</div>
</van-collapse-item>
</van-collapse>
</div>
</template>
<script>
import { Toast } from 'vant'
export default {
// 当前组件名称
name: 'category-item',
/**
* list: 当前组件接收的数据源
* parent: 父级对象数据
* subordinate: 是否子集调用
* activeNameCopy: 打开的折叠面板
*/
props: ['list', 'parent', 'subordinate', 'activeNameCopy'],
data () {
return {
activeNames: [],
activeChildrenNames: this.activeNameCopy
}
},
methods: {
openForm (item) {
this.$emit('editfn', item)
},
clickBoxFn (item, id, pid) {
item.checked = !item.checked
this.clickBox(id, pid)
},
/** 弹窗提示 */
alertTip () {
// this.$toast()
Toast({
message: '抱歉,手机端无法查看5级以上分类,\n请前往PC端查看全部分类!'
})
},
/**
* 点击复选框操作
* @param {*} id 父级item的id
* @param {*} pid 子集的pid
*/
clickBox (id, pid) {
if (id) {
// this.activeNames.push(id)
// this.activeChildrenNames.push(id)
}
this.$emit('changebox', id, pid)
},
changeChecked (value) {
console.log(value, this.list)
this.$emit('changebox')
}
}
}
</script>
更多推荐
已为社区贡献1条内容
所有评论(0)