可自定义设置以下属性:
可选项数据源(options),类型:Option[],默认 [],其中 Option 类型:{ label?: string, value?: string | number, disabled?: boolean, children?: Option[], [propName: string]: any }
下拉字典项的文本字段名(label),类型:string,默认 'label'
下拉字典项的值字段名(value),类型:string,默认 'value'
下拉字典项的后代字段名(children),类型:string,默认 'children'
三级下拉各自占位文本(placeholder),类型:string | string[],默认 '请选择'
当此项为 true 时,点选每级菜单选项值 (v-model) 都会发生变化;否则只有选择第三级选项后选项值才会变化(changeOnSelect),类型:boolean,默认 false
级联下拉框相互间隙宽度(gap),类型:number,单位 px,默认 8
三级下拉各自宽度(width),类型:'auto' | number | number[],默认 'auto'
下拉框高度(height),类型:number,单位 px,默认 32
三级各自是否禁用(disabled),类型:boolean | boolean[],默认 false
是否支持清除(allowClear),类型:boolean,默认 false
是否支持搜索(search),使用搜索时请设置 width,类型:boolean,默认 false
过滤条件函数(filter),仅当支持搜索时生效,类型:Function | true;根据输入项进行筛选,默认为 true 时,筛选每个选项的文本字段 label 是否包含输入项,包含返回 true,反之返回 false;当其为函数 Function 时,接受 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false
下拉面板最多能展示的下拉项数,超过后滚动显示(maxDisplay),类型:number,默认 6
级联选中项(v-model:modelValue),类型:number[] | string[],默认 []
效果如下图:在线预览
展开图:
①创建级联选择组件Cascader.vue:
<script setup lang="ts">
import Select from '../select'
import { ref, watchEffect } from 'vue'
interface Option {
label?: string // 选项名
value?: string | number // 选项值
disabled?: boolean // 是否禁用选项,默认 false
children?: Option[] // 选项 children 数组
[propName: string]: any // 添加一个字符串索引签名,用于包含带有任意数量的其他属性
}
interface Props {
options?: Option[] // 可选项数据源
label?: string // 下拉字典项的文本字段名
value?: string // 下拉字典项的值字段名
children?: string // 下拉字典项的后代字段名
placeholder?: string | string[] // 三级下拉各自占位文本
changeOnSelect?: boolean // 当此项为 true 时,点选每级菜单选项值(v-model)都会发生变化;否则只有选择第三级选项后选项值才会变化
gap?: number // 级联下拉框相互间隙宽度,单位 px
width?: 'auto' | number | number[] // 三级下拉各自宽度
height?: number // 下拉框高度
disabled?: boolean | boolean[] // 三级各自是否禁用
allowClear?: boolean // 是否支持清除
search?: boolean // 是否支持搜索,使用搜索时请设置 width
/*
根据输入项进行筛选,默认为 true 时,筛选每个选项的文本字段 label 是否包含输入项,包含返回 true,反之返回 false
当其为函数 Function 时,接受 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false
*/
filter?: Function | true // 过滤条件函数,仅当支持搜索时生效
maxDisplay?: number // 下拉面板最多能展示的下拉项数,超过后滚动显示
modelValue?: number[] | string[] //(v-model)级联选中项
}
const props = withDefaults(defineProps<Props>(), {
options: () => [],
label: 'label',
value: 'value',
children: 'children',
placeholder: '请选择',
changeOnSelect: false,
gap: 8,
width: 'auto',
height: 32,
disabled: false,
allowClear: false,
search: false,
filter: true,
maxDisplay: 6,
modelValue: () => []
})
const values = ref<(string | number)[]>([]) // 级联value值数组
const labels = ref<string[]>([]) // 级联label文本数组
const firstOptions = ref<Option[]>([])
const secondOptions = ref<Option[]>([])
const thirdOptions = ref<Option[]>([])
watchEffect(() => {
firstOptions.value = [...props.options]
})
watchEffect(() => {
values.value = [...props.modelValue]
})
watchEffect(() => {
initCascader(values.value)
initLabels(values.value)
})
function findChildren(options: Option[], index: number): Option[] {
const len = options.length
for (let i = 0; i < len; i++) {
if (options[i][props.value] === values.value[index]) {
return options[i][props.children] || []
}
}
return []
}
function initCascader(values: (string | number)[]) {
// 获取二级/三级下拉项
secondOptions.value = findChildren(firstOptions.value, 0)
thirdOptions.value = []
if (values.length > 1) {
thirdOptions.value = findChildren(secondOptions.value, 1)
}
}
function findLabel(options: Option[], index: number): any {
const len = options.length
for (let i = 0; i < len; i++) {
if (options[i][props.value] === values.value[index]) {
return options[i][props.label]
}
}
return values.value[index]
}
function initLabels(values: (string | number)[]) {
labels.value[0] = findLabel(firstOptions.value, 0)
if (values.length > 1) {
labels.value[1] = findLabel(secondOptions.value, 1)
}
if (values.length > 2) {
labels.value[2] = findLabel(thirdOptions.value, 2)
}
}
const emits = defineEmits(['update:modelValue', 'change'])
function onFirstChange(value: string | number, label: string) {
// 一级下拉回调
if (props.changeOnSelect) {
emits('update:modelValue', [value])
emits('change', [value], [label])
} else {
values.value = [value]
labels.value = [label]
}
}
function onSecondChange(value: string | number, label: string) {
// 二级下拉回调
if (props.changeOnSelect) {
emits('update:modelValue', [values.value[0], value])
emits('change', [values.value[0], value], [labels.value[0], label])
} else {
values.value = [values.value[0], value]
labels.value = [labels.value[0], label]
}
}
function onThirdChange(value: string | number, label: string) {
// 三级下拉回调
emits('update:modelValue', [...values.value.slice(0, 2), value])
emits('change', [...values.value.slice(0, 2), value], [...labels.value.slice(0, 2), label])
}
</script>
<template>
<div class="m-cascader" :style="`height: ${height}px; gap: ${gap}px;`">
<Select
:options="firstOptions"
:label="label"
:value="value"
:placeholder="Array.isArray(placeholder) ? placeholder[0] : placeholder"
:disabled="Array.isArray(disabled) ? disabled[0] : disabled"
:allow-clear="allowClear"
:search="search"
:filter="filter"
:width="Array.isArray(width) ? width[0] : width"
:height="height"
:max-display="maxDisplay"
v-model="values[0]"
@change="onFirstChange"
/>
<Select
:options="secondOptions"
:label="label"
:value="value"
:placeholder="Array.isArray(placeholder) ? placeholder[1] : placeholder"
:disabled="Array.isArray(disabled) ? disabled[1] : disabled"
:allow-clear="allowClear"
:search="search"
:filter="filter"
:width="Array.isArray(width) ? width[1] : width"
:height="height"
:max-display="maxDisplay"
v-model="values[1]"
@change="onSecondChange"
/>
<Select
:options="thirdOptions"
:label="label"
:value="value"
:placeholder="Array.isArray(placeholder) ? placeholder[2] : placeholder"
:disabled="Array.isArray(disabled) ? disabled[2] : disabled"
:allow-clear="allowClear"
:search="search"
:filter="filter"
:width="Array.isArray(width) ? width[2] : width"
:height="height"
:max-display="maxDisplay"
v-model="values[2]"
@change="onThirdChange"
/>
</div>
</template>
<style lang="less" scoped>
.m-cascader {
display: inline-flex;
}
</style>
②在要使用的页面引入:
<script setup lang="ts">
import Cascader from './Cascader.vue'
import { ref, watchEffect } 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 optionsDisabled = ref([
{
value: '1',
label: '北京',
disabled: true,
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 optionsCustom = ref([
{
code: '1',
name: '北京',
items: [
{
code: '11',
name: '北京市',
items: [
{
code: '111',
name: '东城区'
},
{
code: '112',
name: '西城区'
}
]
}
]
},
{
code: '2',
name: '浙江',
items: [
{
code: '21',
name: '杭州市',
items: [
{
code: '211',
name: '西湖区'
},
{
code: '212',
name: '余杭区'
}
]
},
{
code: '22',
name: '湖州市',
items: [
{
code: '221',
name: '吴兴区'
},
{
code: '222',
name: '安吉区'
}
]
}
]
}
])
const selectedValue = ref(['2', '21', '212'])
watchEffect(() => {
console.log('selectedValue:', selectedValue.value)
})
function onChange(values: (number | string)[], labels: string[]) {
console.log('values:', values)
console.log('labels:', labels)
}
function onAntChange(values: (number | string)[], selectedOptions: any) {
console.log('values:', values)
console.log('selectedOptions:', selectedOptions)
}
// 自定义过滤函数,当选项的 value 值大于 输入项时返回 true
function filter(inputValue: string, option: any) {
return option.value > inputValue
}
</script>
<template>
<div>
<h1>{
{ $route.name }} {
{ $route.meta.title }}</h1>
<h2 class="mt30 mb10">基本使用</h2>
<Cascader :options="options" v-model="selectedValue" />
<h2 class="mt30 mb10">禁用</h2>
<Cascader :options="options" v-model="selectedValue" disabled />
<h2 class="mt30 mb10">禁用某一级</h2>
<h3 class="mb10">只禁用第一级:disabled: [true]</h3>
<h3 class="mb10">禁用前两级:disabled: [true, true]</h3>
<Cascader :options="options" v-model="selectedValue" :disabled="[true]" @change="onChange" />
<h2 class="mt30 mb10">禁用选项</h2>
<h3 class="mb10">只需指定 options 里的 disabled 字段</h3>
<Cascader :options="optionsDisabled" v-model="selectedValue" @change="onChange" />
<h2 class="mt30 mb10">选择即改变</h2>
<Cascader :options="options" v-model="selectedValue" change-on-select @change="onChange" />
<h2 class="mt30 mb10">支持清除</h2>
<Cascader :options="options" v-model="selectedValue" allow-clear @change="onChange" />
<h2 class="mt30 mb10">支持搜索</h2>
<Cascader :options="options" :width="100" v-model="selectedValue" search @change="onChange" />
<h2 class="mt30 mb10">自定义搜索过滤函数</h2>
<Cascader :options="options" :width="100" v-model="selectedValue" search :filter="filter" @change="onChange" />
<h2 class="mt30 mb10">自定义样式</h2>
<Cascader :options="options" v-model="selectedValue" :width="120" :height="36" :gap="12" @change="onChange" />
<h2 class="mt30 mb10">自定义字段名</h2>
<Cascader
:options="optionsCustom"
v-model="selectedValue"
label="name"
value="code"
children="items"
@change="onChange"
/>
<h2 class="mt30 mb10">Ant Design Vue 级联选择</h2>
<a-cascader
:options="options"
style="width: 200px"
placeholder="Please select"
:disabled="false"
allowClear
v-model:value="selectedValue"
@change="onAntChange"
/>
</div>
</template>