<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="https://www.dooper.top/index.php/feed/rss/author/1/">
<title>Gabriel Ryder&#039;s Blog - Gabriel Ryder</title>
<link>https://www.dooper.top/index.php/author/1/</link>
<description>Gabriel Ryder</description>
<items>
<rdf:Seq>
<rdf:li resource="https://www.dooper.top/index.php/archives/10/"/>
<rdf:li resource="https://www.dooper.top/index.php/archives/9/"/>
<rdf:li resource="https://www.dooper.top/index.php/archives/4/"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://www.dooper.top/index.php/archives/10/">
<title>博客下线公告</title>
<link>https://www.dooper.top/index.php/archives/10/</link>
<dc:date>2026-03-14T17:13:00+08:00</dc:date>
<description>本站点将在5月底正式下线，文章不再保留，大家后会有期。</description>
</item>
<item rdf:about="https://www.dooper.top/index.php/archives/9/">
<title>Vue 3 图标选择器实现：基于 lucide-vue-next + shadcn/vue 的高效方案</title>
<link>https://www.dooper.top/index.php/archives/9/</link>
<dc:date>2025-12-16T20:49:00+08:00</dc:date>
<description>最近在开发 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 和 &lt;component :is=&quot;xxx&quot;&gt; 语法，实现选中图标后的动态引入，避免初始加载冗余资源。图标选择器组件（IconSelector.vue）这是核心组件，负责图标展示、搜索过滤、分页加载和选中事件触发。整体结构分为「搜索框 + 图标网格 + 加载更多按钮」，利用 shadcn 组件快速搭建交互逻辑：&lt;template&gt;
  &lt;!-- 基于 shadcn DropdownMenu 实现弹出式选择器 --&gt;
  &lt;DropdownMenuContent class=&quot;w-80 p-2&quot;&gt;
    &lt;!-- 搜索框：筛选图标 --&gt;
    &lt;Input 
      class=&quot;w-full mb-2&quot; 
      v-model=&quot;searchQuery&quot; 
      :placeholder=&quot;$t(&#039;searchIconTip&#039;)&quot; 
    /&gt;
    &lt;!-- 图标网格：分页渲染，支持滚动 --&gt;
    &lt;div class=&quot;grid grid-cols-8 max-h-80 gap-2 p-2 overflow-y-auto&quot;&gt;
      &lt;div 
        v-for=&quot;iconName in filteredIcons.slice(0, currentIndex + batchSize)&quot; 
        :key=&quot;iconName&quot;
        class=&quot;flex justify-center items-center cursor-pointer&quot;
      &gt;
        &lt;!-- Tooltip 显示图标名称 --&gt;
        &lt;TooltipProvider&gt;
          &lt;Tooltip&gt;
            &lt;TooltipTrigger as-child&gt;
              &lt;Button 
                variant=&quot;outline&quot; 
                size=&quot;icon&quot; 
                @click=&quot;selectIcon(iconName)&quot;
                class=&quot;hover:bg-primary/10 transition-colors&quot;
              &gt;
                &lt;!-- 动态渲染 lucide 图标 --&gt;
                &lt;component :is=&quot;allIcons[iconName]&quot; :size=&quot;24&quot; /&gt;
              &lt;/Button&gt;
            &lt;/TooltipTrigger&gt;
            &lt;TooltipContent&gt;
              &lt;p&gt;{{ iconName }}&lt;/p&gt;
            &lt;/TooltipContent&gt;
          &lt;/Tooltip&gt;
        &lt;/TooltipProvider&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 加载更多按钮：按需渲染图标 --&gt;
    &lt;Button 
      class=&quot;w-full mt-2&quot; 
      variant=&quot;outline&quot; 
      v-if=&quot;hasMore&quot; 
      @click=&quot;loadMore&quot;
    &gt;
      {{ $t(&#039;loadMore&#039;) }}
    &lt;/Button&gt;
  &lt;/DropdownMenuContent&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref, computed, defineEmits } from &#039;vue&#039;
// 导入 shadcn 基础组件
import { Button } from &#039;@/components/ui/button&#039;
import { DropdownMenuContent } from &#039;@/components/ui/dropdown-menu&#039;
import { Input } from &#039;@/components/ui/input&#039;
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from &#039;@/components/ui/tooltip&#039;
// 批量导入 lucide-vue-next 所有图标
import * as allIcons from &#039;lucide-vue-next&#039;

// 定义事件：向父组件传递选中的图标名称
const emit = defineEmits([&#039;iconSelected&#039;])

// 所有图标名称（从导入的模块中提取 key）
const allIconKeys = Object.keys(allIcons)

// 分页配置：每次加载 50 个图标
const batchSize = 50
const currentIndex = ref(0)
// 搜索关键词
const searchQuery = ref(&#039;&#039;)

// 过滤图标：根据搜索关键词模糊匹配（不区分大小写）
const filteredIcons = computed(() =&gt; {
  return allIconKeys.filter(iconName =&gt; {
    return iconName.toLowerCase().includes(searchQuery.value.toLowerCase())
  })
})

// 判断是否还有更多图标可加载
const hasMore = computed(() =&gt; {
  return currentIndex.value + batchSize &lt; filteredIcons.value.length
})

// 加载更多图标
const loadMore = () =&gt; {
  currentIndex.value += batchSize
}

// 选中图标：触发事件向父组件传递结果
const selectIcon = (iconName) =&gt; {
  emit(&#039;iconSelected&#039;, iconName)
}
&lt;/script&gt;

&lt;style scoped&gt;
/* 优化网格布局：自适应列宽，确保图标显示均匀 */
.grid {
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
}
/* 移除按钮默认内边距，让图标居中显示 */
:deep(.btn-icon) {
  padding: 0;
}
&lt;/style&gt;父组件集成与动态渲染父组件需要接收 IconSelector 传递的图标名称，通过 Vue 3 的 defineAsyncComponent 动态导入对应图标组件，再用 &lt;component :is=&quot;xxx&quot;&gt; 渲染：&lt;template&gt;
  &lt;!-- 基于 shadcn DropdownMenu 触发图标选择器 --&gt;
  &lt;DropdownMenu&gt;
    &lt;DropdownMenuTrigger as-child&gt;
      &lt;Button variant=&quot;outline&quot; size=&quot;icon&quot;&gt;
        &lt;!-- 动态渲染选中的图标（默认显示 Squircle 图标） --&gt;
        &lt;component :is=&quot;selectedIconComponent || Squircle&quot; :size=&quot;24&quot; /&gt;
      &lt;/Button&gt;
    &lt;/DropdownMenuTrigger&gt;
    &lt;!-- 引入图标选择器组件，监听选中事件 --&gt;
    &lt;IconSelector @iconSelected=&quot;handleIconSelected&quot; /&gt;
  &lt;/DropdownMenu&gt;
&lt;/template&gt;

&lt;script setup&gt;
// 导入默认图标（未选择时显示）
import { Squircle } from &quot;lucide-vue-next&quot;
import { ref, shallowRef, defineAsyncComponent } from &quot;vue&quot;
// 导入图标选择器组件
import IconSelector from &#039;@/components/IconSelector.vue&#039;

// 存储选中的图标名称和组件
const selectedIconName = ref(&#039;&#039;)
const selectedIconComponent = shallowRef(null)

// 处理图标选中事件：动态导入对应图标组件
const handleIconSelected = (iconName: string) =&gt; {
  selectedIconName.value = iconName
  // 动态导入 lucide-vue-next 中的目标图标（TS 忽略类型检查）
  // @ts-ignore
  selectedIconComponent.value = defineAsyncComponent(() =&gt;
    import(&#039;lucide-vue-next&#039;).then((mod) =&gt; mod[iconName])
  )
}
&lt;/script&gt;最后总结这个图标选择器组件的核心优势在于 “高效 + 灵活”：借助 lucide-vue-next 的批量导入能力，无需手动导入单个图标，极大提升开发效率；基于 shadcn/vue 的基础组件，快速搭建交互界面，样式统一且可复用；分页加载和搜索功能确保了大量图标场景下的性能和易用性；父组件通过动态组件接收结果，集成成本极低，可灵活用于按钮、表单、导航等多种场景。整个实现过程中，最大的感受是：Vue 3 的组合式 API 和动态组件语法让组件通信和资源加载更灵活，而 lucide-vue-next 与 shadcn/vue 的组合则完美平衡了 “功能丰富” 和 “开发效率”。如果你的 Vue 3 项目也需要图标选择功能，不妨试试这个方案～</description>
</item>
<item rdf:about="https://www.dooper.top/index.php/archives/4/">
<title>Tauri + Rust 实现 Windows 窗口材质动态切换：从底层 API 到前端交互</title>
<link>https://www.dooper.top/index.php/archives/4/</link>
<dc:date>2025-08-09T22:50:00+08:00</dc:date>
<description>注意：这里是 Tauri2最近在做一个 Tauri 桌面应用时，想加个窗口材质切换功能 —— 就是 Windows 11 里的 Mica（云母）、Acrylic（亚克力） 类半透明效果。本来以为 Tauri 自带相关 API，试了才发现：Tauri 确实能在初始化时设置窗口材质，但运行中动态切换是做不到的。没办法，只能自己动手用 Rust 调用系统 API 实现了，这里把过程分享出来，希望能帮到同样踩坑的朋友。先说说 Tauri 在材质设置的局限一开始我查 Tauri 文档，发现可以在 tauri.conf.json 里通过 windows 配置设置 transparent 后，能指定 WindowEffect 来实现材质的更改（还需要设置前端背景为 transparent）。但这些配置都是应用启动时生效的，一旦应用跑起来，就没法通过前端调用修改了。如果用户想在使用中切换不同的窗口效果，原生 API 根本满足不了，所以必须自己写 Rust 后端调用 Windows 的 DWM 接口。核心思路：用 Rust 对接 Windows DWM APIWindows 的窗口效果是由Desktop Window Manager（DWM）管理的，它提供了 DwmSetWindowAttribute 和 DwmGetWindowAttribute 两个关键 API，分别用来设置和获取窗口属性。我们要做的就是用 Rust 调用这两个 API，实现材质的动态切换。微软文档链接：DwmGetWindowAttribute 函数 （dwmapi.h）DwmSetWindowAttribute 函数 （dwmapi.h）DWMWINDOWATTRIBUTE 枚举 （dwmapi.h）第一步：检测系统版本，确定支持的材质不同 Windows 版本支持的材质不一样，比如 Mica 材质只在 Windows 11（Build 22000+）才支持。所以第一步必须先判断系统版本，避免在不支持的系统上显示无效选项。​我用 Rust 的 winreg 库（需要 cargo add winreg 使用）读取注册表来获取系统版本：// 读取注册表中的系统版本号
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let current_version = hklm.open_subkey(&quot;SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion&quot;)?;
let build_number: String = current_version.get_value(&quot;CurrentBuild&quot;)?;拿到build_number后，就能判断哪些材质可用了。比如 Build 22000 以上支持 Mica、Acrylic等，低版本可能只支持默认材质：// 根据版本动态生成支持的材质列表
if build_number &gt;= 22000 {
    materials.push(MaterialInfo { id: DWMSBT_MAINWINDOW.0, name: &quot;Mica&quot; });
    materials.push(MaterialInfo { id: DWMSBT_TRANSIENTWINDOW.0, name: &quot;Acrylic&quot; });
    materials.push(MaterialInfo { id: DWMSBT_TABBEDWINDOW.0, name: &quot;Tabbed (MicaAlt)&quot; });
}第二步：获取窗口句柄（HWND）​调用 DWM API 必须要有窗口句柄（HWND），Tauri 里可以通过 window_handle() 方法获取，但需要注意：Tauri 的窗口可能有多层父窗口，必须找到最顶层的那个句柄才有效。​我用 Windows API 的 GetParent函数 循环向上查找顶层窗口：// 从Tauri窗口获取HWND
let window = app.get_webview_window(&quot;main&quot;)?;
let handle = window.window_handle()?;
// 转换为Windows原生句柄
match handle.as_raw() {
    RawWindowHandle::Win32(h) =&gt; {
        let mut current = HWND(isize::from(h.hwnd) as *mut c_void);
        // 循环找顶层窗口
        unsafe {
            loop {
                match GetParent(current) {
                    Ok(parent) if !parent.0.is_null() =&gt; current = parent,
                    _ =&gt; break,
                }
            }
        }
        Some(current.0 as isize) // 最终拿到可用的HWND
    }
    _ =&gt; None
}这里的 unsafe 块是必须的，因为直接操作系统句柄涉及内存安全，Rust 无法在编译时验证，所以需要开发者自己保证逻辑正确。​第三步：实现材质切换功能​有了窗口句柄和系统版本信息，就可以调用 DwmSetWindowAttribute 设置材质了。核心代码如下：// 设置窗口材质
pub fn set_window_material(app: &amp;tauri::AppHandle, material: i32) -&gt; u32 {
    let hwnd = get_window_hwnd(app.clone()).unwrap(); // 获取前面拿到的HWND
    let mut material_id = material; // 材质ID（对应DWM的枚举值）
    unsafe {
        // 调用DWM API设置属性
        DwmSetWindowAttribute(
            HWND(hwnd as *mut c_void), 
            DWMWA_SYSTEMBACKDROP_TYPE, // 要设置的属性：系统背景类型
            (&amp;mut material_id as *mut i32).cast::&lt;c_void&gt;(), // 材质ID的指针
            std::mem::size_of::&lt;i32&gt;() as u32 // 参数大小
        );
    }
    material_id.try_into().unwrap()
}这里的 material参数 对应 DWM 的枚举值：DWMSBT_AUTO（默认）、DWMSBT_MAINWINDOW（Mica）等，前面定义的MaterialInfo结构体就是把这些值和友好名称对应起来，方便前端显示。​use windows::Win32::Graphics::Dwm::{
    DWMSBT_AUTO,
    DWMSBT_NONE,
    DWMSBT_MAINWINDOW,
    DWMSBT_TRANSIENTWINDOW,
    DWMSBT_TABBEDWINDOW,
    DWMWA_SYSTEMBACKDROP_TYPE,
    DwmGetWindowAttribute,
    DwmSetWindowAttribute
};

#[derive(Serialize)]
pub struct MaterialInfo {
    pub id: i32,
    pub name: &amp;&#039;static str,
}对于样式样式枚举 DWM_SYSTEMBACKDROP_TYPE 的解释：DWMSBT_AUTO = 0 自动：默认DWMSBT_NONE = 1 无背景样式DWMSBT_MAINWINDOW = 2 云母(Mica)DWMSBT_TRANSIENTWINDOW = 3 亚克力(Acrylic)DWMSBT_TABBEDWINDOW = 4 云母（MicaAlt）前端交互：Vue 3 如何调用后端功能​前端部分很简单，主要是用 Tauri 的 invoke函数 调用 Rust 写的命令，然后渲染材质列表和处理选择事件。​前端组件库则使用 shadcn/vue初始化时加载材质列表​页面加载时，先调用后端获取支持的材质和当前材质：// 前端初始化代码
onMounted(() =&gt; {
    // 获取支持的材质列表
    invoke(&#039;get_supported_window_materials&#039;).then(value =&gt; {
        materials.value = value as MaterialInfo[];
    });
    // 获取当前正在使用的材质
    invoke(&#039;get_current_window_materials&#039;).then(value =&gt; {
        selectedMaterial.value = (value as MaterialInfo[])[0];
    });
});用下拉框实现切换交互​我用了一个 Combobox 组件 展示材质列表，用户选择后调用 set_window_material 命令：&lt;!-- 前端交互组件 --&gt;
&lt;Combobox&gt;
  &lt;ComboboxTrigger as-child&gt;
    &lt;Button variant=&quot;outline&quot;&gt;
      {{ selectedMaterial?.name }}
      &lt;ChevronsUpDown class=&quot;ml-2 h-4 w-4&quot; /&gt;
    &lt;/Button&gt;
  &lt;/ComboboxTrigger&gt;
  &lt;ComboboxList&gt;
    &lt;ComboboxItem 
      v-for=&quot;material in materials&quot; 
      :key=&quot;material.id&quot;
      @select=&quot;() =&gt; setMaterial(material)&quot;
    &gt;
      {{ material.name }}
    &lt;/ComboboxItem&gt;
  &lt;/ComboboxList&gt;
&lt;/Combobox&gt;

// 切换材质的方法
const setMaterial = (material: MaterialInfo) =&gt; {
    invoke(&#039;set_window_material&#039;, { material: material.id })
        .then(() =&gt; {
            selectedMaterial.value = material; // 更新选中状态
        })
        .catch(error =&gt; {
            console.error(&#039;切换失败：&#039;, error);
        });
};遇到的坑窗口句柄获取错误：一开始直接用了 Tauri 返回的第一层窗口句柄，调用 DWM API 始终失败，后来才发现需要找顶层父窗口，用 GetParent 循环查找才解决，如果不找，则拿到的是 webview 的子窗口句柄。​最后总结​虽然 Tauri 原生不支持动态切换窗口材质，但通过 Rust 调用 Windows 的 DWM API 完全可以实现。整个过程的核心就是：正确获取窗口句柄→根据系统版本提供合适的材质选项→用 DWM API 执行切换。​这个功能写下来最大的感受是：Rust 虽然入门难，但处理系统级 API 时确实安全又高效；而 Tauri 的前后端通信机制也很方便，让前端能轻松调用底层功能。</description>
</item>
</rdf:RDF>