最近在开发 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 项目也需要图标选择功能,不妨试试这个方案~

推荐文章

Tauri + Rust 实现 Windows 窗口材质动态切换:从底层 API 到前端交互

在 Tauri 开发中,原生 API 无法支持 Windows 11 中 Mica、Acrylic 等半透明材质的动态切换。通过 Rust 对接 Windows DWM 系统 API 的解决方案:先检测系统版本筛选支持的材质,再获取顶层窗口句柄,最后调用 DWM 接口实现切换。搭配 Vue 3 前端交互,让应用支持运行中实时切换窗口质感,解决 Tauri 原生局限,适合需要提升界面体验的开发者参考。

评论区(暂无评论)

我要评论

昵称
邮箱
网址
0/200
没有评论
可按 ESC 键退出搜索

0 篇文章已搜寻到~