vue树形选择器(树形下拉框)

开局一张图,内容全靠编

基础功能
vue树形选择器(树形下拉框)_第1张图片

附加选择项
vue树形选择器(树形下拉框)_第2张图片

前言 因为项目需要一个带二层展开的树形选择器,用elment的select和tree组件组合起来虽然可以用但是改样式和添加其他功能太过麻烦,所以就仿照element的select样式自己写了一个类似的组件,其实主要功能很简单,主要是要模拟一些element的一些细节比较费时间,但好在是足够灵活,这样就可以想怎么改样式改功能都可以直接改。

使用

参数说明

参数名 说明
value 监听值
optionList 下拉项列表
operateVisiable 是否展示操作栏

示例
vue
tip
change方法用于将组件内的数据更新到父组件传入的监听值,手动实现双向绑定

          <treeOptionSelect
            :optionList="typeOptionList"
            :value="collectType"
            @change="(s)=>{collectType = s}"
            :operateVisiable="true"
          ></treeOptionSelect>


js
tip optionList 中的选项如果有children则为展开项

  data() {
    return {
      collectType: "",
      typeOptionList: [
        {
          value: "选项1",
          label: "选项1",
          children: [
            {
              label: "一级 1",
              value: "一级 1"
            },
            {
              label: "一级 2",
              value: "一级 2"
            }
          ]
        },
        {
          value: "选项3",
          label: "选项3"
        }
      ]
    };
  }

源码
直接拷入项目(vue,element,scss)使用即可
tip
使用前需要全局引入一下我自己封装的一本通用css主要用于flex布局

http://www.oujin.fun/webPage/css/Universal.css


<template>
  <div class="tree_select_box row-flex-start" @click="optionBoxVisble = !optionBoxVisble">
    <i
      class="el-icon-arrow-down arrow_icon"
      :style="{transform:optionBoxVisble?'rotate('+180+'deg)':'rotate('+0+'deg)'}"
    ></i>
    <transition name="slide">
      <div class="triangle_up" v-if="optionBoxVisble"></div>
    </transition>
    <transition name="slide">
      <div
        class="option_box column-start-center"
        :style="{paddingBottom:operateVisiable?'60px':'0px'}"
        v-if="optionBoxVisble"
        @click.stop
      >
        <div class="option_wrapper">
          <template v-for="(item,index) in options">
            <div class="tree_option_banner nowrap" :key="index" @click.stop="optionClick(item)">
              <div class="expand_icon_wrapper c_c" v-if="item.children">
                <i
                  class="el-icon-arrow-down expand_icon c_c"
                  :style="{transform:item.expand?'rotate('+0+'deg)':'rotate('+270+'deg)'}"
                ></i>
              </div>
              {{item.label}}
            </div>
            <template v-if="item.expand&&item.children">
              <div
                class="tree_option_banner nowrap"
                style="color: #606764;"
                v-for="(it,i) in item.children"
                @click.stop="optionClick(it)"
                :class="{
              'last_option_banner':i===(item.children.length-1)
            }"
                :key="it.label"
              >{{it.label}}</div>
            </template>
          </template>
        </div>
        <!-- 是否显示操作框 -->
        <div class="operate_banner row-space-between" v-if="operateVisiable">
          <div class="row-flex-start">
            <el-button class="operete_but c_c c_border">新建</el-button>
            <el-button class="operete_but c_c c_border">删除</el-button>
          </div>
          <el-button type="primary" class="operete_but">保存</el-button>
        </div>
      </div>
    </transition>
    {{val}}
  </div>
</template>
<script>
export default {
  props: {
    // 监听值
    value: {
      type: String
    },
    // 下拉项列表
    optionList: {
      type: Array,
      default: () => {
        return [];
      }
    },
    // 是否展示操作栏
    operateVisiable: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    options() {
     return  this.optionList.map(x=>{
       if(x.children)this.$set(x,'expand',false)
       return x
     })
    }
  },
  data() {
    return {
      val: this.value,
      optionBoxVisble: true,
       
    };
  },
  // 手动双向绑定
  watch:{
    value(n,o){
      this.val = n
    }
  },
  methods: {
    optionClick(s) {
      s.expand = !s.expand;
      if (s.children) return;
      // 修改外部组件监听数据
      this.$emit('change',s.value)
    }
  }
};
</script>
<style lang="scss" scoped>
.tree_select_box {
  position: relative;
  height: 34px;
  cursor: pointer;
  color: #606764;
  padding: 6px 0px 6px 20px;
  width: 100%;
  border-radius: 4px;
  border: 1px solid rgba(236, 236, 236, 1);
  background: #fff;
  max-width: 650px;
  .arrow_icon {
    position: absolute;
    transform-origin: center center;
    transition: transform 0.2s;
    right: 15px;
    top: calc(50% - 7px);
  }

  .triangle_up {
    width: 0;
    height: 0;
    position: absolute;
    top: calc(100% + 4px);
    left: 30px;
    z-index: 2009;
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
    border-bottom: 8px solid #fff;
    border-top-width: 0;
    border-bottom-color: #e8e8e8;
  }
  .triangle_up::after {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    top: 1px;
    left: -7px;
    z-index: 2010;
    border-left: 7px solid transparent;
    border-right: 7px solid transparent;
    border-bottom: 7px solid #fff;
    border-top-width: 0;
  }

  .option_box {
    position: absolute;
    z-index: 2000;
    padding: 10px 0px;
    top: calc(100% + 10px);
    left: 0px;
    background: #fff;
    width: 100%;
    border-radius: 4px;
    min-height: 50px;
    max-height: 300px;
    overflow-y: auto;
    box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.06);
    border: 1px solid rgba(232, 232, 232, 1);
    // padding-bottom: 60px;
    .tree_option_banner {
      font-size: 14px;
      // z-index: 2002;
      color: #999999;
      position: relative;
      width: 100%;
      height: 34px;
      padding: 6px 0px 6px 20px;
    }
    .option_wrapper {
      width: 100%;
      overflow-y: auto;
      flex: 1;
    }
    .operate_banner {
      position: absolute;
      bottom: 0;
      width: 100%;
      z-index: 2002;
      padding: 0 14px;
      background: #fff;
      height: 60px;
      .operete_but {
        width: 60px;
        height: 32px;
      }
    }
    .last_option_banner {
      margin-bottom: 8px;
    }
    .last_option_banner::after {
      content: "";
      position: absolute;
      width: calc(100% - 40px);
      bottom: 0px;
      height: 1px;
      background: #e8e8e8;
      right: 20px;
    }
    .tree_option_banner:hover {
      background: #f5f7fa;
    }
    .expand_icon_wrapper {
      position: absolute;
      z-index: 2003;
      font-size: 12px;
      transform: scale(0.75);
      left: 5px;
      top: 7px;
      .expand_icon {
        font-weight: bold;
        transform-origin: center center;
        transition: transform 0.3s;
      }
    }
  }

  @keyframes slideShow {
    from {
      top: 0px;
    }
    to {
      top: calc(100% + 10px);
    }
  }
  @keyframes slideHide {
    from {
      transform: scale(1);
    }
    to {
      transform: scale(0);
    }
  }
  .slide-enter-active {
    animation: slideShow 0.2s;
  }
  .slide-leave-active {
    animation: slideHide 0.2s;
  }
}
</style>

你可能感兴趣的:(vue自定义组件)