You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vuetify自定义下拉框:如何添加自定义selection插槽?

自定义Vuetify下拉框:selection插槽不显示及选中项列表展示解决方案

问题概述

开发自定义Vuetify下拉框,已实现分组选项和单选图标样式,但添加的#selection插槽内容无法显示,推测是v-select未绑定v-model导致。需要实现选中项的列表展示效果。

核心问题分析

  • Vuetify的v-select组件中,selection插槽仅在绑定v-model且存在选中值时才会渲染,未绑定则插槽内容不会显示。
  • 当前场景是多分组单选,选中状态由父组件的selected对象管理,无法直接使用v-select默认的单值选中逻辑,需要自定义选中项展示逻辑。

解决方案步骤

  1. 给v-select绑定临时v-model:仅用于触发selection插槽渲染,不参与实际选中状态管理。
  2. 改造selection插槽:遍历selected对象,匹配对应选项文本,渲染成标签式选中列表。
  3. 优化交互体验:添加选中项高亮样式,无选中时显示占位提示,选中变化时强制插槽更新。

修改后的组件代码

<template>
    <v-container fluid>
        <!-- 绑定临时v-model触发插槽渲染,隐藏默认输入框细节 -->
        <v-select 
            :label="label" 
            no-data-text 
            v-model="dummyModel" 
            hide-details
        >
            <!-- 自定义选中项展示插槽 -->
            <template #selection>
                <div class="selected-items">
                    <span 
                        v-for="(value, key) in selected" 
                        :key="key"
                        class="selected-item-tag"
                    >
                        {{ getSelectedText(key, value) }}
                    </span>
                    <!-- 无选中项时显示占位提示 -->
                    <span v-if="Object.keys(selected).length === 0" class="placeholder">请选择筛选条件</span>
                </div>
            </template>
            <template #prepend-item>
                <div
                    v-for="([key, vals], $list) in Object.entries(options)"
                    :key="$list"
                >
                    <!-- 添加分组标题 -->
                    <v-list-item-title class="text-muted font-weight-bold px-4">{{ key }}</v-list-item-title>
                    <v-list-item
                        v-for="({ text, value: val }, $option) in vals"
                        :key="$option"
                        ripple
                        @mousedown.prevent
                        @click="toggle(key, val)"
                        :class="{ 'selected-item': selected[key] === val }"
                    >
                        <v-list-item-action>
                            <v-icon color="indigo darken-4">
                                {{ icon(key, val) }}
                            </v-icon>
                        </v-list-item-action>
                        <v-list-item-content>
                            <v-list-item-title>{{ text }}</v-list-item-title>
                        </v-list-item-content>
                    </v-list-item>
                    <v-divider class="mt-2"></v-divider>
                </div>
            </template>
        </v-select>
    </v-container>
</template>

<script>
export default {
    name: 'LabspendMultiselect',
    props: {
        label: {
            type: String,
            default: 'Additional Filters',
        },
        selected: {
            type: Object,
            default: () => {},
        },
        options: {
            type: Object,
            default: () => {},
        },
    },
    data() {
        return {
            // 临时变量,仅用于触发selection插槽渲染
            dummyModel: null
        }
    },
    methods: {
        icon(key, value) {
            return this.selected[key] === value ? 'radio_button_checked' : 'radio_button_unchecked';
        },
        toggle(key, value) {
            this.$emit('selected', { key, value });
            // 更新临时变量,强制插槽重新渲染
            this.dummyModel = Date.now();
        },
        // 根据key和value匹配对应的选项文本
        getSelectedText(key, value) {
            const optionGroup = this.options[key];
            if (!optionGroup) return '';
            const matchedOption = optionGroup.find(item => item.value === value);
            return matchedOption ? matchedOption.text : '';
        },
    },
};
</script>

<style scoped>
.selected-items {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 4px 0;
}
.selected-item-tag {
    background-color: #e3f2fd;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 0.875rem;
}
.placeholder {
    color: #9e9e9e;
    font-size: 0.875rem;
}
.selected-item {
    background-color: rgba(63, 81, 181, 0.05);
}
</style>

父组件代码(无需修改)

<template>
// ...
         <v-row>
            <v-col>
                <custom-multiselect
                    :selected="additional_selections"
                    :options="additional_options"
                    @selected="setSelections"
                />
                <pre>{{ additional_selections }}</pre>
            </v-col>
        </v-row>
// ...
</template>

<script>
    data() {
        return {
            additional_selections: {},
            additional_options: {
                hazmat: [
                    { text: 'HAZMAT', value: true },
                    { text: 'Not HAZMAT', value: false },
                ],
                expired: [
                    { text: 'Expired', value: 0 },
                    { text: 'Expiring Within 7 Days', value: 7 },
                    { text: 'Expiring Within 30 Days', value: 30 },
                ],
                depleted: [
                    { text: 'Depleted', value: 1 },
                    { text: 'Not Depleted', value: 2 },
                    { text: 'Below Threshold', value: 3 },
                    { text: 'Above Threshold', value: 4 },
                ],
            },
        };
    },
    methods: {
        setSelections({ key, value }) {
            this.$set(this.additional_selections, key, value);
        },
    }
</script>

关键说明

  • dummyModel:作为临时绑定变量,每次选中项变化时更新它,强制selection插槽重新渲染,解决插槽不显示的问题。
  • getSelectedText:通过父组件传递的options匹配选中值对应的文本,确保选中项展示准确。
  • 样式优化:添加了选中标签样式和列表项选中高亮,提升视觉反馈和交互体验。

内容的提问来源于stack exchange,提问作者Riza Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 12:15:35