Vue2级联选择(Cascader)

简介: 这是一个基于 Vue 3 的级联选择组件,支持多种自定义属性,如数据源、选中项、文本字段等。提供了丰富的配置项,如层级间隙、宽度、高度、禁用状态和占位符等,便于灵活使用。组件通过监听选择变化并触发回调事件,实现了动态更新与交互。

可自定义设置以下属性:

  • 可选项数据源(options),类型:Array,默认 []

  • (v-model)级联选中项(selectedValue),类型:Array,默认[]

  • 下拉字典项的文本字段名(label),类型:string,默认 'label'

  • 下拉字典项的值字段名(value),类型:string,默认 'value'

  • 下拉字典项的后代字段名(children),类型:string,默认 'children'

  • 当此项为true时,点选每级菜单选项值(v-model)都会发生变化;否则只有选择三级选项后选项值才会变化(changeOnSelect),类型:boolean,默认 false

  • 下拉层级(zIndex),类型:number,默认 1

  • 级联下拉框相互间隙宽度,类型:number,单位px,默认 8px

  • 三级下拉各自宽度(width),类型:number|number[],默认 160

  • 下拉框高度(height),类型:number,单位px,默认 36px

  • 三级各自是否禁用(disabled),类型:boolean|boolean[],默认 false

  • 三级下拉各自占位文本(placeholder),类型:string|string[],默认 '请选择'

  • 下拉面板最多能展示的下拉项数,超过后滚动显示(num),类型:number,默认 6

效果如下图:

①创建级联下拉选择器Cascader.vue:

<template>
  <div class="m-cascader-wrap" :style="`height: ${height}px;`">
    <VueAmazingSelector
      :style="`margin-right: ${gap}px; z-index: ${zIndex};`"
      :options="firstOptions"
      v-model="selectedValue[0]"
      :label="label"
      :value="value"
      :disabled="disabled"
      :width="Array.isArray(width) ? width[0] : width"
      :height="height"
      :num="num"
      :placeholder="Array.isArray(placeholder) ? placeholder[0] : placeholder"
      @change="onFirstChange" />
    <VueAmazingSelector
      :style="`margin-right: ${gap}px; z-index: ${zIndex};`"
      :options="secondOptions"
      v-model="selectedValue[1]"
      :label="label"
      :value="value"
      :disabled="disabled"
      :width="Array.isArray(width) ? width[1] : width"
      :height="36"
      :num="num"
      :placeholder="Array.isArray(placeholder) ? placeholder[1] : placeholder"
      @change="onSecondChange" />
    <VueAmazingSelector
      :style="`z-index: ${zIndex};`"
      :options="thirdOptions"
      v-model="selectedValue[2]"
      :label="label"
      :value="value"
      :disabled="disabled"
      :width="Array.isArray(width) ? width[2] : width"
      :height="height"
      :num="num"
      :placeholder="Array.isArray(placeholder) ? placeholder[2]:placeholder"
      @change="onThirdChange" />
  </div>
</template>
<script>
// yarn add VueAmazingSelector
import { VueAmazingSelector } from 'vue-amazing-selector'
export default {
  name: 'Cascader',
  components: {
    VueAmazingSelector
  },
  model: {
    prop: 'selectedValue',
    event: 'model'
  },
  props: {
    options: { // 可选项数据源
      type: Array,
      default: () => []
    },
    selectedValue: { // (v-model)级联选中项
      type: Array,
      default: () => []
    },
    label: { // 下拉字典项的文本字段名
      type: String,
      default: 'label'
    },
    value: { // 下拉字典项的值字段名
      type: String,
      default: 'value'
    },
    children: { // 下拉字典项的后代字段名
      type: String,
      default: 'children'
    },
    changeOnSelect: { // 当此项为true时,点选每级菜单选项值(v-model)都会发生变化;否则只有选择三级选项后选项值才会变化
      type: Boolean,
      default: false
    },
    zIndex: { // 下拉层级
      type: Number,
      default: 1
    },
    gap: { // 级联下拉框相互间隙宽度,单位px,默认8px
      type: Number,
      default: 8
    },
    width: { // 三级下拉各自宽度
      type: [Number, Array],
      default: 160
    },
    height: { // 下拉框高度
      type: Number,
      default: 36
    },
    disabled: { // 统一是否全部禁用
      type: Boolean,
      default: false
    },
    placeholder: { // 三级下拉各自占位文本
      type: [String, Array],
      default: '请选择'
    },
    num: { // 下拉面板最多能展示的下拉项数,超过后滚动显示
      type: Number,
      default: 6
    }
  },
  data () {
    return {
      values: this.selectedValue,
      labels: [],
      firstOptions: this.options,
      secondOptions: [],
      thirdOptions: []
    }
  },
  watch: {
    selectedValue (to) {
      this.values = to
      if (to.length) {
        this.initCascader(to)
        this.initLabels(to)
      }
    }
  },
  mounted () {
    if (this.selectedValue.length) {
      this.initCascader(this.selectedValue)
      this.initLabels(this.selectedValue)
    }
  },
  methods: {
    findChildren (options, index) {
      const len = options.length
      for (let i = 0; i < len; i++) {
        if (options[i][this.value] === this.selectedValue[index]) {
          return options[i][this.children] || []
        }
      }
      return []
    },
    initCascader (selectedValue) {
      this.secondOptions = this.findChildren(this.firstOptions, 0)
      this.thirdOptions = []
      if (selectedValue.length > 1) {
        this.thirdOptions = this.findChildren(this.secondOptions, 1)
      }
    },
    findLabel (options, index) {
      const len = options.length
      for (let i = 0; i < len; i++) {
        if (options[i][this.value] === this.selectedValue[index]) {
          return options[i][this.label]
        }
      }
      return this.selectedValue[index]
    },
    initLabels (selectedValue) {
      this.labels[0] = this.findLabel(this.firstOptions, 0)
      if (selectedValue.length > 1) {
        this.labels[1] = this.findLabel(this.secondOptions, 1)
      }
      if (selectedValue.length > 2) {
        this.labels[2] = this.findLabel(this.thirdOptions, 2)
      }
    },
    onFirstChange (value, label, index) { // 一级下拉回调
      this.values = [value]
      this.labels = [label]
      if (this.changeOnSelect) {
        this.$emit('model', this.values)
        this.$emit('change', this.values, this.labels)
      }
      // 获取二级下拉选项
      this.secondOptions = this.firstOptions[index][this.children] || []
      this.thirdOptions = []
    },
    onSecondChange (value, label, index) { // 二级下拉回调
      this.values = [this.values[0], value]
      this.labels = [this.labels[0], label]
      if (this.changeOnSelect) {
        this.$emit('model', this.values)
        this.$emit('change', this.values, this.labels)
      }
      // 获取三级下拉选项
      this.thirdOptions = this.secondOptions[index][this.children] || []
    },
    onThirdChange (value, label) { // 三级下拉回调
      this.values[2] = value
      this.labels[2] = label
      this.$emit('model', this.values)
      this.$emit('change', this.values, this.labels)
    }
  }
}
</script>
<style lang="less" scoped>
.m-cascader-wrap {
  display: inline-block;
}
</style>

②在要使用的页面引入:

<template>
  <div class="selector">
    <Cascader
      v-model="selectedValue"
      label="label"
      value="value"
      children="children"
      changeOnSelect
      :options="options"
      :zIndex="9"
      :gap="8"
      :provinceWidth="120"
      :cityWidth="120"
      :areaWidth="120"
      :width="120"
      :height="36"
      :provinceDisabled="false"
      :cityDisabled="false"
      :disabled="false"
      :num="6"
      @change="onChange" />
  </div>
</template>
<script>
import Cascader from '@/components/Cascader'
export default {
  name: 'Selector',
  components: {
    Cascader
  },
  data () {
    return {
      options: [
        {
          value: '1',
          label: '北京',
          children: [
            {
              value: '11',
              label: '北京市',
              children: [
                {
                  value: '111',
                  label: '东城区'
                },
                {
                  value: '112',
                  label: '西城区'
                }
              ]
            }
          ]
        },
        {
          value: '2',
          label: '浙江',
          children: [
            {
              value: '21',
              label: '杭州市',
              children: [
                {
                  value: '211',
                  label: '西湖区'
                },
                {
                  value: '212',
                  label: '余杭区'
                }
              ]
            },
            {
              value: '22',
              label: '湖州市',
              children: [
                {
                  value: '221',
                  label: '吴兴区'
                },
                {
                  value: '222',
                  label: '安吉区'
                }
              ]
            }
          ]
        }
      ],
      selectedValue: []
    }
  },
  watch: {
    selectedValue (to) {
      console.log('selectedValue:', to)
    }
  },
  mounted () {
    setTimeout(() => {
      this.selectedValue = ['1', '11', '112']
    }, 1000)
  },
  methods: {
    onChange (value, label) {
      console.log('value:', value)
      console.log('label:', label)
    }
  }
}
</script>
<style lang="less" scoped>
</style>
相关文章
|
4月前
|
JavaScript 前端开发 UED
Vue:为什么要使用v-cloak
Vue:为什么要使用v-cloak
|
4月前
|
JavaScript
vue自定义指令详解
【4月更文挑战第5天】Vue自定义指令用于扩展模板功能,通过`Vue.directive()`全局或组件内注册。它们有生命周期钩子函数,如`bind`、`inserted`等,在不同阶段执行相应操作。在模板中以`v-`前缀或简写形式使用,可接受参数和修饰符来定制行为。
39 8
|
4月前
|
JavaScript
Vue3 自定义指令
Vue3 自定义指令
|
24天前
|
JavaScript 前端开发 编译器
Vue自定义指令详解
Vue自定义指令详解
14 0
|
3月前
|
JavaScript
Vue的双向绑定v-model详细介绍
Vue的双向绑定v-model详细介绍
|
4月前
|
JavaScript API 开发者
自定义指令:创建和使用Vue自定义指令
【4月更文挑战第24天】Vue.js允许开发者创建自定义指令以适应特定需求,增强代码复用和可维护性。通过`Vue.directive`全局注册或组件内`directives`局部注册,定义指令行为。以`highlight`指令为例,展示`bind`和`click`钩子改变元素背景色。自定义指令包含多个生命周期钩子,可处理参数,提供灵活性。它们扩展HTML功能,封装复杂逻辑,提升代码质量,是Vue开发中的强大工具。
37 3
|
4月前
|
JavaScript
Vue自定义指令的三个方法
Vue自定义指令的三个方法
23 0
|
4月前
|
JavaScript
(详解)Vue3自定义指令
(详解)Vue3自定义指令
125 2
|
10月前
|
容器
Vue3.0实现todolist-实现todolist每个组件需要用到的方法
Vue3.0实现todolist-实现todolist每个组件需要用到的方法
28 1
|
4月前
|
JavaScript 前端开发 测试技术
Vue 3.0 Teleport
Vue 3.0 Teleport
29 0