Vue3 cascade selection (Cascader)

Vue2 cascade selection

A cascading selection component implemented based on Vue3 selectors

The following properties can be customized: 

  • Optional data source (options), type: Array<any>, default []

  • (v-model) cascade selected item (selectedValue), type: Array<number|string>, default []

  • The text field name (label) of the drop-down dictionary item, type: string, default 'label'

  • The value field name (value) of the drop-down dictionary item, type: string, default 'value'

  • The descendant field name (children) of the drop-down dictionary item, type: string, default 'children'

  • When this is true, the option value (v-model) of each level of the menu will change; otherwise, the option value will only change after the third-level option is selected (changeOnSelect), type: boolean, default false

  • Drop-down level (zIndex), type: number, default 1

  • The width of the gap between the cascading drop-down boxes, type: number, unit px, default 8px

  • Width of each of the three levels of drop-down (width), type: number|number[], default 160

  • Drop-down box height (height), type: number, unit px, default 36px

  • Whether each of the three levels is disabled (disabled), type: boolean|boolean[], default false

  • The three-level drop-down placeholder text (placeholder), type: string|string[], the default is 'please select'

  • The maximum number of drop-down items that can be displayed in the drop-down panel, scrolling display after exceeding (num), type: number, default 6

The effect is as follows:

 Expanded view:

① Create a cascade selection component Cascader.vue:

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'

interface Option {
  value: string | number,
  label: string,
  children?: Option[]
}
const props = defineProps({
    options: { // 可选项数据源
      type: Array<any>,
      default: () => []
    },
    selectedValue: { // (v-model)级联选中项
      type: Array<number|string>,
      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<number>],
      default: 160
    },
    height: { // 下拉框高度
      type: Number,
      default: 36
    },
    disabled: { // 三级各自是否禁用
      type: [Boolean, Array<boolean>],
      default: false
    },
    placeholder: { // 三级下拉各自占位文本
      type: [String, Array<string>],
      default: '请选择'
    },
    num: { // 下拉面板最多能展示的下拉项数,超过后滚动显示
      type: Number,
      default: 6
    }
  })
const values = ref(props.selectedValue) // 级联value值数组
const labels = ref<any[]>([]) // 级联label文本数组
const firstOptions = ref(props.options)
const secondOptions = ref<any[]>([])
const thirdOptions = ref<Option[]>([])

watch(
  () => props.selectedValue,
  (to) => {
    values.value = to
    if (to.length) {
      initCascader(to)
      initLabels(to)
    }
  })
onMounted(() => {
  if (props.selectedValue.length) {
    initCascader(props.selectedValue)
    initLabels(props.selectedValue)
  }
})
function findChildren (options: any[], index: number): Option[] {
  const len = options.length
  for (let i = 0; i < len; i++) {
    if (options[i][props.value] === props.selectedValue[index]) {
      return options[i][props.children] || []
    }
  }
  return []
}
function initCascader (selectedValue: (string|number)[]) {
  secondOptions.value = findChildren(firstOptions.value, 0)
  thirdOptions.value = []
  if (selectedValue.length > 1) {
    thirdOptions.value = findChildren(secondOptions.value, 1)
  }
}
function findLabel (options: any[], index: number): any {
  const len = options.length
  for (let i = 0; i < len; i++) {
    if (options[i][props.value] === props.selectedValue[index]) {
      return options[i][props.label]
    }
  }
  return props.selectedValue[index]
}
function initLabels (selectedValue: (string|number)[]) {
  labels.value[0] = findLabel(firstOptions.value, 0)
  if (selectedValue.length > 1) {
    labels.value[1] = findLabel(secondOptions.value, 1)
  }
  if (selectedValue.length > 2) {
    labels.value[2] = findLabel(thirdOptions.value, 2)
  }
}
const emits = defineEmits(['update:selectedValue', 'change'])
function onFirstChange (value: string|number, label: string, index: number) { // 一级下拉回调
  values.value = [value]
  labels.value = [label]
  if (props.changeOnSelect) {
    emits('update:selectedValue', values.value)
    emits('change', values.value, labels.value)
  }
  // 获取二级下拉选项
  secondOptions.value = firstOptions.value[index][props.children] || []
  thirdOptions.value = []
}
function onSecondChange (value: string|number, label: string, index: number) { // 二级下拉回调
  values.value = [values.value[0], value]
  labels.value = [labels.value[0], label]
  if (props.changeOnSelect) {
    emits('update:selectedValue', values.value)
    emits('change', values.value, labels.value)
  }
  // 获取三级下拉选项
  thirdOptions.value = secondOptions.value[index][props.children] || []
}
function onThirdChange (value: string|number, label: string) { // 三级下拉回调
  values.value[2] = value
  labels.value[2] = label
  emits('update:selectedValue', values.value)
  emits('change', values.value, labels.value)
}
</script>
<template>
  <div class="m-cascader-wrap" :style="`height: ${height}px;`">
    <Select
      :style="`margin-right: ${gap}px; z-index: ${zIndex};`"
      :options="firstOptions"
      v-model:selectedValue="selectedValue[0]"
      :label="label"
      :value="value"
      :disabled="Array.isArray(disabled) ? disabled[0] : disabled"
      :width="Array.isArray(width) ? width[0] : width"
      :height="height"
      :num="num"
      :placeholder="Array.isArray(placeholder) ? placeholder[0] : placeholder"
      @change="onFirstChange" />
    <Select
      :style="`margin-right: ${gap}px; z-index: ${zIndex};`"
      :options="secondOptions"
      v-model:selectedValue="selectedValue[1]"
      :label="label"
      :value="value"
      :disabled="Array.isArray(disabled) ? disabled[1] : disabled"
      :width="Array.isArray(width) ? width[1] : width"
      :height="36"
      :num="num"
      :placeholder="Array.isArray(placeholder) ? placeholder[1] : placeholder"
      @change="onSecondChange" />
    <Select
      :style="`z-index: ${zIndex};`"
      :options="thirdOptions"
      v-model:selectedValue="selectedValue[2]"
      :label="label"
      :value="value"
      :disabled="Array.isArray(disabled) ? disabled[2] : disabled"
      :width="Array.isArray(width) ? width[2] : width"
      :height="height"
      :num="num"
      :placeholder="Array.isArray(placeholder) ? placeholder[2]:placeholder"
      @change="onThirdChange" />
  </div>
</template>
<style lang="less" scoped>
.m-cascader-wrap {
  display: inline-block;
}
</style>

 ②Introduce in the page to be used:

<script setup lang="ts">
import { Cascader } from './Cascader.vue'
import { ref, watch } from 'vue'

const options = ref([
  {
    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: '安吉区'
          }
        ]
      }
    ]
  }
])
const selectedValue = ref<any[]>(['2', '21', '212'])
watch(selectedValue, (to) => {
  console.log('p to:', to)
})
function onChange (value: Array<number|string>, label: Array<string>) {
  console.log('value:', value)
  console.log('label:', label)
}
</script>
<template>
  <div>
    <h2 class="mb10">Cascader 级联选择基本使用</h2>
    <Cascader
      :options="options"
      v-model:selectedValue="selectedValue"
      label="label"
      value="value"
      children="children"
      changeOnSelect
      :zIndex="9"
      :gap="8"
      :width="120"
      :height="36"
      :disabled="false"
      placeholder="请选择"
      :num="6"
      @change="onChange" />
  </div>
</template>
<style lang="less" scoped>
</style>

Guess you like

Origin blog.csdn.net/Dandrose/article/details/129881817
Recommended