美文网首页
手写一个Vue层联选择器

手写一个Vue层联选择器

作者: 善顶洞人 | 来源:发表于2020-12-26 15:44 被阅读0次

    今天我们来手写一个Cascader,也就是层联选择器,一般长下面这样,通过点击输入框出来一个面板,可以层层选择数据,再次点击输入框或者外面,输入框会消失。

    image

    我们先准备一份数据

     options: [{
              value: 'zhinan',
              label: '数码',
              children: [{
                value: 'shejiyuanze',
                label: '手机',
                children: [{
                  value: 'yizhi',
                  label: '苹果'
                }, {
                  value: 'fankui',
                  label: '华为'
                }, {
                  value: 'xiaolv',
                  label: '三星'
                }, {
                  value: 'kekong',
                  label: '小米'
                }]
              }, {
                value: '',
                label: '笔记本电脑',
                children: [{
                  value: 'cexiangdaohang',
                  label: 'Macbook Pro'
                }, {
                  value: 'dingbudaohang',
                  label: 'iMac'
                }]
              }]
            }, {
              value: 'zujian',
              label: '家电',
              children: [{
                value: 'basic',
                label: '空调',
                children: [{
                  value: 'layout',
                  label: '格力'
                }, {
                  value: 'color',
                  label: '美的'
                }]
              }, 
              {
                value: 'basic',
                label: '洗衣机',
                children: [{
                  value: 'layout',
                  label: '西门子'
                }, {
                  value: 'color',
                  label: '松下'
                }]
              },]
            }
          ]
    
    

    然后创建一个Cascader.vue文件

    <template>
      <div class="container" v-click-outside="close">
        <div class="input" @click="toggle"></div>
        <div class="content" v-if="visiable">
          <div class="content-left">
            <div v-for="(item,index) in options" :key="index">
              <div @click="select(item)">{{item.label}}</div>
            </div>
          </div>
          <div class="content-right" v-if="list.length>0">
            <div v-for="(item,index) in list" :key="index">
              <div>{{item.label}}</div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import clickOutside from "../../directives/outside";
    export default {
      props: {
        options: {
          type: Array,
          default: () => []
        }
      },
      data() {
        return {
          visiable: false,
          selected: []
        };
      },
      computed: {
        list() {
          return this.selected.children ? this.selected.children : [];
        }
      },
      methods: {
        toggle() {
          this.visiable = !this.visiable;
        },
        select(item) {
          console.log(item);
          this.selected = item;
        },
        close() {
          this.visiable = false;
        }
      },
      directives: {
        clickOutside: clickOutside
      },
      mounted() {}
    };
    </script>
    

    可以看到,我们通过一个props来接收要展示的数据,用v-for循环展示这些数据,通过计算属性计算出点击左侧面板后右侧面板要显示的数据,通过visiable属性搭配v-if指令控制面板的显示和隐藏,同时这里我们使用一个自定义指令v-click-outside来实现点击面板外面关闭面板,代码如下

    const clickOutside = {
        inserted: function (el, binding) {
            function hide(e) {
                if (el === e.target || el.contains(e.target)) {
                    return
                }
                binding.value()
            }
            el._hide = hide
            document.addEventListener('click', el._hide)
    
        },
        unbind(el) {
            document.removeEventListener(el._hide)
        }
    }
    
    export default clickOutside
    

    我们在页面组件引入Cascader,将数据传给他,这时页面长这样

    image

    我们点击数码,出现手机 笔记本电脑这没毛病,但我们点击手机的时候,并没有出现下一层。。。因为我们的dom结构只写了两个div,也就是class名为content-leftcontent-right的这两个,想要显示第三层的话,就得再增加div,但你们只看到了第二层,而我看到了第五层。事实上我们不知道到底有多少层,所以我们得用Vue的递归组件,我们再创建一个CascaderItem.vue的文件,来循环右侧数据

    <template>
        <div class="panel">
          <div class="content-left">
            <div v-for="(item,index) in options" :key="index">
              <div @click="select(item,index)" :class="{'selected':selectIndex==index}">{{item.label}}</div>
            </div>
          </div>
          <div class="content-right" v-if="list.length>0">
              <CascaderItem :options='list'></CascaderItem>
          </div>
        </div>
    </template>
    
    <script>
    export default {
      name:'CascaderItem',
      props: {
        options: {
          type: Array,
          default: () => []
        }
      },
      data() {
        return {
          selected: [],
          selectIndex:null
        };
      },
      computed: {
        list() {
          return this.selected.children ? this.selected.children : [];
        }
      },
      methods: {
        select(item,index) {
          console.log(item);
          this.selected = item;
          this.selectIndex = index
        },
      },
     
      mounted() {}
    };
    </script>
    
    
    image
    好了 现在我们好像能展示多层数据了,但这时我们发现一个问题
    image
    我们这时点击家电,虽然第二层跟着变了,但第三层没有变。因为目前的写法第三层数据是由第二层数据点击产生的。那么如何解决这个问题呢?我们用在Cascader文件中声明一个数组 selectData 来存放我们选中的数据,为此我们还需要声明一个变量level来表示当前层数

    Cascader.vue

    <template>
      <div class="container" v-click-outside="close">
        <div class="input" @click="toggle">{{inputVal}}</div>
        <div class="content" v-if="visiable">
          <CascaderItem :options='options' :selectData='selectData' :level='0' @change='change'></CascaderItem>
        </div>
      </div>
    </template>
    
    <script>
    
    export default {
     ...
      data() {
        return {
         ...
          selectData:[]
        };
      },
     ...
      computed: {
         inputVal(){
           return this.selectData.map(item=>item.label).join('/')
         }
      },
      methods: {
        change(value,index){
          this.selectData[index] = value
          this.selectData.splice(index,1,value) //触发更新
        },
       
      },
     
    };
    </script>
    

    CascaderItem.vue

    
    <template>
        <div class="panel">
          <div class="content-left">
            <div v-for="(item,index) in options" :key="index">
              <div @click="select(item,index)" :class="{'selected':selected==item}">{{item.label}}</div>
            </div>
          </div>
          <div class="content-right" v-if="list && list.length>0">
              <CascaderItem :options='list' :selectData='selectData' :level='level+1' @change='change' ></CascaderItem>
          </div>
        </div>
    </template>
    
    <script>
    
    export default {
        ..
      props: {
       ..
         selectData: {
          type: Array,
          default: () => []
        },
        level:{
            type:Number
        }
      },
     
      computed: {
        list() {
          return this.selectData[this.level] && this.selectData[this.level].children 
        }
      },
      methods: {
        select(item,index) {
          this.selected = item;
          this.selectIndex = index
          this.selectData.splice(this.level+1)
          this.$emit('change',item,this.level)
        },
        change(item,index){
          this.$emit('change',item,index)
        }
      },
    };
    </script>
    

    可以看到,点击的时候我们把selectData下一层之后的数据都删除了,这就符合了我们的逻辑,我们的右侧数据此时就可以通过selectDatalevel来得到了。这里我们要主要的是我们需要将level传给递归组件,并且+1,因为每递归一次,他的层数就会加一,此外,我们需要监听递归组件emit出来的事件,然后再传递出去。
    我们再稍作修改,将选择的数据传递出去

    props:{
    ...
     value: {
          type: Array,
          default: () => []
        }
    }
     change(){
     ...
          let selectArr = this.selectData.map(item => item.value);
          console.log(selectArr);
          this.$emit("input", selectArr);
          }
    
    

    这时,我们的静态层联基本就完成了


    image

    接下来我们来写异步的。。

    相关文章

      网友评论

          本文标题:手写一个Vue层联选择器

          本文链接:https://www.haomeiwen.com/subject/vscgnktx.html