(横向循环:递归组件)+(纵向循环:多条数据)实现二维数据选择

106 阅读1分钟

1、场景

在实际开发中,存在这样一个场景,存在多条数据,每一条数据都有对应的子数据选项,通过选择某一子数据,向后渲染其下一个子数据选项,例如:

截屏2023-09-06 09.42.04.png

以上存在两条数据,每一条数据中存在多层级的选择,根据上一层级的选项,对应渲染下一层级的选择,因此,层级的深度并不确定。

2、实现方式

由于考虑到每一个选择框结构都是类似的,所以将每一个选择框作为一个组件,针对每一条数据在该组件内递归该组件。在父组件则通过v-for循环该组件。

父组件: 其中selectList用于存放选择的数据。

<template>
  <div class="page">
    <div v-for="(item, index) in listData" :key="item.id" class="item">
      <list :listItem="item" :listIndex="index" @getVal="getSelectedVal"></list>
    </div>
  </div>
</template>

<script>
import list from './list.vue'
import { listData } from './data.js'
export default {
  components: {
    list,
  },
  data() {
    return {
      listData,
      selectList: [],
    }
  },
  created() {
    //二维数据,存放每一个选择框的选项
    for (let i = 0; i < this.listData.length; i++) {
      this.selectList.push([])
    }
  },
  methods: {
    getSelectedVal(val) {
      console.log(val)
      if (val.value === '') {
        //未选择数据项,则删除对应位置的数据
        this.selectList[val.index].splice(
          val.level,
          this.selectList[val.index].length - val.level
        )
      } else if (
        val.isEnding &&
        val.level !== this.selectList[val.index].length
      ) {
        //当某一数据没有children,其他数据有children,改位置后续位置有可能保留上一次选择的值,需要清除
        this.selectList[val.index].splice(
          val.level + 1,
          this.selectList[val.index].length - val.level - 1
        )
      } else {
        //添加选择的值进对应位置
        this.selectList[val.index][val.level] = val.value
      }
    },
  },
}
</script>

<style lang="less" scoped>
.page {
  padding: 20px;
}
</style>

子组件:

其中通过数据中的level标识层级,对应于数组中的位置。当某一个位置清空后,由于下一个位置是通过选项渲染出的,不同的选项可能渲染出的数据不同,所以应不展示后续选项框,以及清空上一次在数组中放置的值,此处采用监听changeFlag的方式。

<template>
  <div>
    <div class="list-item">
      <el-form ref="form" label-width="100px">
        <el-form-item :label="listItem.name">
          <el-select
            v-model="selectVal"
            placeholder="请选择"
            @change="changeSelect"
          >
            <el-option
              v-for="it in listItem.children"
              :key="it.id"
              :label="it.name"
              :value="it.id"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>

      <list
        v-if="selectItem && selectItem.children && selectItem.children.length"
        :changeFlag="flag"
        :listItem="selectItem"
        :listIndex="listIndex"
        @getVal="getSelectedVal"
      ></list>
    </div>
  </div>
</template>

<script>
export default {
  name: 'list',
  props: {
    listItem: {
      type: Object,
      default: () => {},
    },
    listIndex: {
      type: Number,
      default: 0,
    },
    //当切换选项,后续选择都需要清空
    changeFlag: {
      type: Boolean,
      default: () => false,
    },
  },
  data() {
    return {
      selectVal: null,
      selectItem: null,
      flag: false,
    }
  },
  watch: {
    changeFlag() {
      //如果上一组件的选项发生变化,则该组件需要清空清空
      this.selectVal = null
      this.selectItem = null
      let obj = {
        level: this.listItem.level - 1,
        value: '',
        index: this.listIndex,
      }
      this.$emit('getVal', obj)
    },
  },
  methods: {
    //选择某个选项
    changeSelect(val) {
      let ind = this.listItem.children.findIndex((item) => {
        return item.id === val
      })
      //isEnding:由于每个项的长度不同,所以通过判断是否是最后的节点,去除之前选择的历史数据
      if (ind !== -1) {
        this.selectItem = this.listItem.children[ind]
        let obj = {
          level: this.listItem.level - 1,
          value: this.selectItem.name,
          index: this.listIndex,
          isEnding: !this.selectItem.children?.length,
        }
        this.$emit('getVal', obj)
        this.flag = !this.flag //当切换当前选项,后续选项应清空
      }
    },
    //从子节点触发的函数,需要向上传递到根组件
    getSelectedVal(val) {
      console.log('测试', val)
      this.$emit('getVal', val)
    },
  },
}
</script>

<style lang="less" scoped>
.list-item {
  display: flex;
}
</style>

截屏2023-09-06 10.06.57.png

截屏2023-09-06 10.07.30.png

截屏2023-09-06 10.14.41.png

截屏2023-09-06 10.15.07.png