Tauri + Rust 实现 Windows 窗口材质动态切换:从底层 API 到前端交互
在 Tauri 开发中,原生 API 无法支持 Windows 11 中 Mica、Acrylic 等半透明材质的动态切换。通过 Rust 对接 Windows DWM 系统 API 的解决方案:先检测系统版本筛选支持的材质,再获取顶层窗口句柄,最后调用 DWM 接口实现切换。搭配 Vue 3 前端交互,让应用支持运行中实时切换窗口质感,解决 Tauri 原生局限,适合需要提升界面体验的开发者参考。
最近在开发 Vue 3 项目时,需要一个灵活的图标选择器组件 —— 既要支持大量图标快速检索,又能无缝集成到现有界面,还得让父组件动态接收选中结果。对比了几个图标库后,最终选择了 lucide-vue-next(轻量、图标丰富、支持 Vue 3 按需导入),再配合 shadcn/vue 的基础组件快速搭建交互界面,完美实现了需求。这里把完整实现过程分享出来,希望能帮到有同样需求的朋友。
支持 lucide-vue-next 全量图标选择(无需手动逐个导入)
提供搜索功能,快速筛选目标图标
支持分页加载(避免一次性渲染过多图标导致性能问题)
选择图标后,通过事件通知父组件
父组件能动态渲染选中的图标组件
defineAsyncComponent 和 <component :is="xxx"> 语法,实现选中图标后的动态引入,避免初始加载冗余资源。这是核心组件,负责图标展示、搜索过滤、分页加载和选中事件触发。整体结构分为「搜索框 + 图标网格 + 加载更多按钮」,利用 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>这个图标选择器组件的核心优势在于 “高效 + 灵活”: