写在前面

因项目中需要用到树形控件,第三方的组件也不满足项目的使用需求,就自己造了一个车轮子,用Vue递归组件的方式实现,代码简单易懂,直接复制即可使用。

效果图

在这里插入图片描述

子组件代码

<template>
  <ul v-if="data.length">
    <li v-for="(item, i) in data" :key="i" @click.stop="selectItem(item)" v-show="expandFlag">
      <div class="item">
        <!-- 展开的图标 -->
        <i class='expandIcon'
         	@click.stop="expandItem(item, i)"
        	:class="[
        		expandArr.includes(i) ? 'reduce' : 'add',
        		item.children && item.children.length ? '' : 'disabled'
        	]">
        </i>
        <!-- 选项名 -->
        <h1>{{item[labelKey]}}</h1>
        <!-- 选择的图标 -->
        <i class='selectIcon'
        	:class="[
        		value[valueKey] == item[valueKey] ? 'checked' : 'noChecked',
        		 item[disabledKey] ? 'disabled' : ''
       		]">
       	</i>
      </div>
      <list-menu
        v-if='item.children'
        @input='input'
        :data='item.children'
        :valueKey='valueKey'
        :labelKey='labelKey'
        :disabledKey='disabledKey'
        :value="value"
        :toastText='toastText'
        :expandFlag='expandArr.includes(i)'
      />
    </li>
  </ul>
</template>
<script>
  export default {
    // 组件名必写
    name: 'ListMenu',
    props: {
      // 选中的值的属性名,必传
      valueKey: String,
      // 在页面要展示的选项属性名,必传
      labelKey: String,
      // 不可选的唯一标识,如item[disabledKey]未true则不可选择,非必传
      disabledKey: String,
      // 选中的值,必传
      value: Object,
      // 控制展开,不需要传
      expandFlag: {
        type: Boolean,
        default: true
      },
      // 总数据,必传
      data: Array,
      // 不可选提示文字,非必传
      toastText: String
    },
    data () {
      return {
        // 当前级组件已展开的项
        expandArr: []
      }
    },
    methods: {
      // 子组件逐级传递选中项
      input (item) {
        this.$emit('input', item)
      },
      // 选择
      selectItem (item) {
        // industryDeptType为1表示时不可选择该工会
        if (this.disabledKey && item[this.disabledKey]) {
          if (this.toastText) {
            alert(this.toastText)
          }
          return
        }
        this.$emit('input', item)
      },
      // 展开
      expandItem (item, i) {
        if (item.children && item.children.length) {
          let index = this.expandArr.indexOf(i)
          if (index > -1) {
            this.expandArr.splice(index, 1)
          } else {
            this.expandArr.push(i)
          }
        }
      }
    }
  }
</script>
<style lang="less" scoped>
  ul{
    width: 100%;
    color: #2a2a2a;
    font-size: 26/75rem;
    overflow: hidden;
    background: #fff;
    border-bottom: .8px solid #e1e1e1;
    li{
      .item{
        padding: 14/75rem 24/75rem;
        display: flex;
        align-items: center;
        width: 100%;
        .expandIcon{
          height:34/75rem;
          width:34/75rem;
          border: 1.5px solid;
          border-radius: 50%;
          position:relative;
          &:after{
            position: absolute;
            top: 50%;
            left: 50%;
            font-size: 34/75rem;
            transform: translate(-50%, -50%);
          }
          &.add{
            border-color: #2a2a2a;
            &:after{
              color: #2a2a2a;
              content: '+';
            }
          }
          &.reduce{
            border-color: #ff6633;
            &:after{
              color: #ff6633;
              content: '-';
            }
          }
          &.disabled{
            border-color: #ddd;
            &:after{
              color: #ddd;
            }
          }
        }
        .selectIcon{
          height:34/75rem;
          width:34/75rem;
          border: 1.5px solid;
          border-radius: 50%;
          position:relative;
          &:after{
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
          }
          &.checked{
            border-color: #ff6633;
            background: #ff6633;;
            &:after{
              font-size: 24/75rem;
              color: #fff;
              content: '✓';
            }
          }
          &.noChecked{
            border-color: #ff6633;
          }
          &.disabled{
            border-color: #ddd;
          }
        }
        h1{
          margin-right: 6/75rem;
          padding: 0 16/75rem;
          position: relative;
          top: 2/75rem;
          height: 60/75rem;
          line-height: 60/75rem;
          font-size:30/75rem;
          flex: 1;
          white-space: nowrap;
          color: #2a2a2a;
          // .one-line;
          overflow-x: auto;
        }
      }
      &:not(:first-child){
        border-top: .8px solid #e1e1e1;
      }
      >ul{
        border-bottom: 0;
        padding-left: 60/75rem;
        li{
          .item{
            padding-left: 12/75rem;
          }
          border-top: .8px solid #e1e1e1;
        }
      }
    }
  }
</style>


父组件调用代码

<template>
  <div class="lists">
    <list-menu
      :data='lists'
      v-model="selectVal"
      value-key='treeId'
      label-key='name'
      disabled-key='disabled'
      toastText='该选项不可选择'
    />
    <article>
      <p>选中的选项名:<span>{{selectVal.name || '未选择'}}</span></p>
      <p>选中的选项值:<span>{{selectVal.treeId || '未选择'}}</span></p>
    </article>
  </div>
</template>
<script>
  // 引入组件
  import ListMenu from './ListMenu'
  export default {
    components: {
      ListMenu
    },
    data () {
      return {
        selectVal: {},
        lists: []
      }
    },
    created () {
      this.getTreeList()
    },
    methods: {
      getTreeList () {
        // 模拟数据
        this.lists.push({
          treeId: 1,
          name: '第一级',
          disabled: 0,
          children: [{
            treeId: 2,
            name: '第二级(disadbed为true不可选)',
            disabled: 1,
            children: [
              {
                treeId: '3_1',
                name: '第三级(1)',
                disabled: 0
              },
              {
                treeId: '3_2',
                name: '第三级(2)',
                disabled: 0,
                children: [{
                  treeId: 4,
                  name: '第四级',
                  disabled: 0,
                  children: [{
                    treeId: 5,
                    name: '第五级',
                    disabled: 0,
                    children: [{
                      treeId: 6,
                      name: '第六级',
                      disabled: 0
                    }]
                  }]
                }]
              }
            ]
          }]
        })
      }
    }
  }
</script>
<style lang="less" scoped>
  .lists{
    article{
      margin: 30/75rem;
      line-height: 1.5;
      font-size: 30/75rem;
      span{
        color: #ff6633;
      }
    }
  }
</style>

觉得有用就给我点个赞吧,蟹蟹,(●ˇ∀ˇ●)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐