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>