Vue 3 图标选择器实现:基于 lucide-vue-next + shadcn/vue 的高效方案
最近在开发 Vue 3 项目时,需要一个灵活的图标选择器组件 —— 既要支持大量图标快速检索,又能无缝集成到现有界面,还得让父组件动态接收选中结果。对比了几个图标库后,最终选择了 lucide-vue-next(轻量、图标丰富、支持 Vue 3 按需导入),再配合 shadcn/vue 的基础组件快速搭建交互界面,完美实现了需求。这里把完整实现过程分享出来,希望能帮到有同样需求的朋友。先说说核心需求与技术选型需求明确支持 lucide-vue-next 全量图标选择(无需手动逐个导入)提供搜索功能,快速筛选目标图标支持分页加载(避免一次性渲染过多图标导致性能问题)选择图标后,通过事件通知父组件父组件能动态渲染选中的图标组件技术选型原因lucide-vue-next:轻量级矢量图标库,内置 1000+ 常用图标,支持批量导入,且与 Vue 3 兼容性极佳,图标尺寸、颜色可灵活定制。shadcn/vue:提供现成的基础组件(DropdownMenu、Input、Tooltip、Button 等),样式统一且可自定义,无需从零编写交互组件,节省开发时间。Vue 3 动态组件:通过 defineAsyncComponent 和 <component :is="xxx"> 语法,实现选中图标后的动态引入,避免初始加载冗余资源。图标选择器组件(IconSelector.vue)这是核心组件,负责图标展示、搜索过滤、分页加载和选中事件触发。整体结构分为「搜索框 + 图标网格 + 加载更多按钮」,利用 shadcn 组件快速搭建交互逻辑:<template> <!-- 基于 shadcn DropdownMenu 实现弹出式选择器 --> <DropdownMenuContent class="w-80 p-2"> <!-- 搜索框:筛选图标 --> <Input class="w-full mb-2" v-model="searchQuery" :placeholder="$t('searchIconTip')" /> <!-- 图标网格:分页渲染,支持滚动 --> <div class="grid grid-cols-8 max-h-80 gap-2 p-2 overflow-y-auto"> <div v-for="iconName in filteredIcons.slice(0, currentIndex + batchSize)" :key="iconName" class="flex justify-center items-center cursor-pointer" > <!-- Tooltip 显示图标名称 --> <TooltipProvider> <Tooltip> <TooltipTrigger as-child> <Button variant="outline" size="icon" @click="selectIcon(iconName)" class="hover:bg-primary/10 transition-colors" > <!-- 动态渲染 lucide 图标 --> <component :is="allIcons[iconName]" :size="24" /> </Button> </TooltipTrigger> <TooltipContent> <p>{{ iconName }}</p> </TooltipContent> </Tooltip> </TooltipProvider> </div> </div> <!-- 加载更多按钮:按需渲染图标 --> <Button class="w-full mt-2" variant="outline" v-if="hasMore" @click="loadMore" > {{ $t('loadMore') }} </Button> </DropdownMenuContent> </template> <script setup> import { ref, computed, defineEmits } from 'vue' // 导入 shadcn 基础组件 import { Button } from '@/components/ui/button' import { DropdownMenuContent } from '@/components/ui/dropdown-menu' import { Input } from '@/components/ui/input' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip' // 批量导入 lucide-vue-next 所有图标 import * as allIcons from 'lucide-vue-next' // 定义事件:向父组件传递选中的图标名称 const emit = defineEmits(['iconSelected']) // 所有图标名称(从导入的模块中提取 key) const allIconKeys = Object.keys(allIcons) // 分页配置:每次加载 50 个图标 const batchSize = 50 const currentIndex = ref(0) // 搜索关键词 const searchQuery = ref('') // 过滤图标:根据搜索关键词模糊匹配(不区分大小写) const filteredIcons = computed(() => { return allIconKeys.filter(iconName => { return iconName.toLowerCase().includes(searchQuery.value.toLowerCase()) }) }) // 判断是否还有更多图标可加载 const hasMore = computed(() => { return currentIndex.value + batchSize < filteredIcons.value.length }) // 加载更多图标 const loadMore = () => { currentIndex.value += batchSize } // 选中图标:触发事件向父组件传递结果 const selectIcon = (iconName) => { emit('iconSelected', iconName) } </script> <style scoped> /* 优化网格布局:自适应列宽,确保图标显示均匀 */ .grid { grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); } /* 移除按钮默认内边距,让图标居中显示 */ :deep(.btn-icon) { padding: 0; } </style>父组件集成与动态渲染父组件需要接收 IconSelector 传递的图标名称,通过 Vue 3 的 defineAsyncComponent 动态导入对应图标组件,再用 <component :is="xxx"> 渲染:<template> <!-- 基于 shadcn DropdownMenu 触发图标选择器 --> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="outline" size="icon"> <!-- 动态渲染选中的图标(默认显示 Squircle 图标) --> <component :is="selectedIconComponent || Squircle" :size="24" /> </Button> </DropdownMenuTrigger> <!-- 引入图标选择器组件,监听选中事件 --> <IconSelector @iconSelected="handleIconSelected" /> </DropdownMenu> </template> <script setup> // 导入默认图标(未选择时显示) import { Squircle } from "lucide-vue-next" import { ref, shallowRef, defineAsyncComponent } from "vue" // 导入图标选择器组件 import IconSelector from '@/components/IconSelector.vue' // 存储选中的图标名称和组件 const selectedIconName = ref('') const selectedIconComponent = shallowRef(null) // 处理图标选中事件:动态导入对应图标组件 const handleIconSelected = (iconName: string) => { selectedIconName.value = iconName // 动态导入 lucide-vue-next 中的目标图标(TS 忽略类型检查) // @ts-ignore selectedIconComponent.value = defineAsyncComponent(() => import('lucide-vue-next').then((mod) => mod[iconName]) ) } </script>最后总结这个图标选择器组件的核心优势在于 “高效 + 灵活”:借助 lucide-vue-next 的批量导入能力,无需手动导入单个图标,极大提升开发效率;基于 shadcn/vue 的基础组件,快速搭建交互界面,样式统一且可复用;分页加载和搜索功能确保了大量图标场景下的性能和易用性;父组件通过动态组件接收结果,集成成本极低,可灵活用于按钮、表单、导航等多种场景。整个实现过程中,最大的感受是:Vue 3 的组合式 API 和动态组件语法让组件通信和资源加载更灵活,而 lucide-vue-next 与 shadcn/vue 的组合则完美平衡了 “功能丰富” 和 “开发效率”。如果你的 Vue 3 项目也需要图标选择功能,不妨试试这个方案~