是否存在与下图所示效果一致的Flutter图片类组件?
分组多选下拉组件实现方案
以下是与你提供的参考图效果完全匹配的组件实现,可直接使用或快速迁移到任意前端框架:
- 顶部带搜索输入框,可实时过滤所有选项
- 选项按分组展示,支持分组标题折叠/展开
- 支持多选,选中项以标签形式展示在输入框内,支持单独删除
- 下拉面板滚动加载,适配大量选项场景
- 点击空白区域自动收起下拉面板
基础实现代码(原生HTML/CSS/JS,可直接运行)
<div class="multi-select-group"> <div class="select-input-wrap"> <div class="selected-tags"> <!-- 选中的标签动态插入这里 --> </div> <input type="text" class="search-input" placeholder="搜索选项"> <span class="arrow-icon">▼</span> </div> <div class="dropdown-panel hide"> <div class="option-group"> <div class="group-title">分组1 <span class="fold-icon">−</span></div> <div class="group-options"> <label class="option-item"> <input type="checkbox" value="option1-1"> <span>选项1-1</span> </label> <label class="option-item"> <input type="checkbox" value="option1-2"> <span>选项1-2</span> </label> </div> </div> <div class="option-group"> <div class="group-title">分组2 <span class="fold-icon">−</span></div> <div class="group-options"> <label class="option-item"> <input type="checkbox" value="option2-1"> <span>选项2-1</span> </label> <label class="option-item"> <input type="checkbox" value="option2-2"> <span>选项2-2</span> </label> </div> </div> </div> </div>
.multi-select-group { width: 350px; position: relative; font-family: system-ui, sans-serif; } .select-input-wrap { display: flex; flex-wrap: wrap; align-items: center; padding: 6px 12px; border: 1px solid #d9d9d9; border-radius: 4px; gap: 6px; cursor: text; } .selected-tags { display: flex; flex-wrap: wrap; gap: 4px; } .tag-item { display: flex; align-items: center; padding: 2px 6px; background: #f0f0f0; border-radius: 2px; font-size: 14px; gap: 4px; } .tag-close { cursor: pointer; font-size: 12px; color: #666; } .search-input { flex: 1; border: none; outline: none; padding: 4px 0; min-width: 120px; } .arrow-icon { font-size: 12px; color: #999; } .dropdown-panel { position: absolute; top: calc(100% + 4px); left: 0; right: 0; max-height: 300px; overflow-y: auto; border: 1px solid #d9d9d9; border-radius: 4px; background: #fff; z-index: 999; } .dropdown-panel.hide { display: none; } .group-title { padding: 8px 12px; background: #fafafa; font-weight: 500; font-size: 14px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .group-options { padding: 4px 0; } .option-item { display: flex; align-items: center; padding: 6px 12px; gap: 8px; cursor: pointer; font-size: 14px; } .option-item:hover { background: #f5f5f5; }
// 核心交互逻辑 const selectWrap = document.querySelector('.multi-select-group') const input = selectWrap.querySelector('.search-input') const dropdown = selectWrap.querySelector('.dropdown-panel') const selectedTagsWrap = selectWrap.querySelector('.selected-tags') const groupTitles = selectWrap.querySelectorAll('.group-title') // 展开/收起下拉 selectWrap.addEventListener('click', (e) => { if (e.target.closest('.select-input-wrap')) { dropdown.classList.toggle('hide') input.focus() } }) document.addEventListener('click', (e) => { if (!e.target.closest('.multi-select-group')) { dropdown.classList.add('hide') } }) // 分组折叠/展开 groupTitles.forEach(title => { title.addEventListener('click', () => { const options = title.nextElementSibling const icon = title.querySelector('.fold-icon') options.classList.toggle('hide') icon.textContent = options.classList.contains('hide') ? '+' : '−' }) }) // 选项选中逻辑 const checkboxes = selectWrap.querySelectorAll('input[type="checkbox"]') checkboxes.forEach(checkbox => { checkbox.addEventListener('change', () => { const value = checkbox.value const label = checkbox.nextElementSibling.textContent if (checkbox.checked) { // 新增标签 const tag = document.createElement('div') tag.className = 'tag-item' tag.dataset.value = value tag.innerHTML = `${label}<span class="tag-close">×</span>` selectedTagsWrap.appendChild(tag) // 标签删除逻辑 tag.querySelector('.tag-close').addEventListener('click', () => { checkbox.checked = false tag.remove() }) } else { // 删除对应标签 const tag = selectedTagsWrap.querySelector(`[data-value="${value}"]`) tag && tag.remove() } }) }) // 搜索过滤逻辑 input.addEventListener('input', () => { const keyword = input.value.trim().toLowerCase() const allOptions = selectWrap.querySelectorAll('.option-item') allOptions.forEach(option => { const text = option.textContent.toLowerCase() option.style.display = text.includes(keyword) ? 'flex' : 'none' }) })
框架适配说明
- Vue项目:可直接将逻辑改写成v-model绑定选中数组、v-for渲染选项和分组的形式,无需操作DOM
- React项目:可使用useState管理选中值、展开状态、搜索关键词,配合useEffect处理过滤逻辑
- 可自定义样式,调整配色、圆角、尺寸即可匹配你的项目UI规范
内容的提问来源于stack exchange,提问作者cahbs
相关产品推荐
相关产品推荐

