<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Gabriel Ryder&#039;s Blog - Gabriel Ryder</title>
<link>https://www.dooper.top/index.php/author/1/</link>
<atom:link href="https://www.dooper.top/index.php/feed/author/1/" rel="self" type="application/rss+xml" />
<language>zh-CN</language>
<description>Gabriel Ryder</description>
<lastBuildDate>Sat, 14 Mar 2026 17:13:00 +0800</lastBuildDate>
<pubDate>Sat, 14 Mar 2026 17:13:00 +0800</pubDate>
<item>
<title>博客下线公告</title>
<link>https://www.dooper.top/index.php/archives/10/</link>
<guid>https://www.dooper.top/index.php/archives/10/</guid>
<pubDate>Sat, 14 Mar 2026 17:13:00 +0800</pubDate>
<dc:creator>Gabriel Ryder</dc:creator>
<description><![CDATA[本站点将在5月底正式下线，文章不再保留，大家后会有期。]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p>本站点将在5月底正式下线，文章不再保留，大家后会有期。</p>
]]></content:encoded>
<slash:comments>1</slash:comments>
<comments>https://www.dooper.top/index.php/archives/10/#comments</comments>
<wfw:commentRss>https://www.dooper.top/index.php/feed/author/1/</wfw:commentRss>
</item>
<item>
<title>Vue 3 图标选择器实现：基于 lucide-vue-next + shadcn/vue 的高效方案</title>
<link>https://www.dooper.top/index.php/archives/9/</link>
<guid>https://www.dooper.top/index.php/archives/9/</guid>
<pubDate>Tue, 16 Dec 2025 20:49:00 +0800</pubDate>
<dc:creator>Gabriel Ryder</dc:creator>
<description><![CDATA[最近在开发 Vue 3 项目时，需要一个灵活的图标选择器组件 —— 既要支持大量图标快速检索，又能无缝集成到现有界面，还得让父组件动态接收选中结果。对比了几个图标库后，最终选择了 lucide-...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p>最近在开发 Vue 3 项目时，需要一个灵活的图标选择器组件 —— 既要支持大量图标快速检索，又能无缝集成到现有界面，还得让父组件动态接收选中结果。对比了几个图标库后，最终选择了 lucide-vue-next（轻量、图标丰富、支持 Vue 3 按需导入），再配合 shadcn/vue 的基础组件快速搭建交互界面，完美实现了需求。这里把完整实现过程分享出来，希望能帮到有同样需求的朋友。</p><h1>先说说核心需求与技术选型</h1><h2>需求明确</h2><p>支持 lucide-vue-next 全量图标选择（无需手动逐个导入）<br>提供搜索功能，快速筛选目标图标<br>支持分页加载（避免一次性渲染过多图标导致性能问题）<br>选择图标后，通过事件通知父组件<br>父组件能动态渲染选中的图标组件</p><h2>技术选型原因</h2><ul><li><strong>lucide-vue-next</strong>：轻量级矢量图标库，内置 1000+ 常用图标，支持批量导入，且与 Vue 3 兼容性极佳，图标尺寸、颜色可灵活定制。</li><li><strong>shadcn/vue</strong>：提供现成的基础组件（DropdownMenu、Input、Tooltip、Button 等），样式统一且可自定义，无需从零编写交互组件，节省开发时间。</li><li><strong>Vue 3 动态组件</strong>：通过 <code>defineAsyncComponent</code> 和 <code>&lt;component :is=&quot;xxx&quot;&gt;</code> 语法，实现选中图标后的动态引入，避免初始加载冗余资源。</li></ul><h1>图标选择器组件（IconSelector.vue）</h1><p>这是核心组件，负责图标展示、搜索过滤、分页加载和选中事件触发。整体结构分为「搜索框 + 图标网格 + 加载更多按钮」，利用 shadcn 组件快速搭建交互逻辑：</p><pre><code class="lang-vue">&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;</code></pre><h2>父组件集成与动态渲染</h2><p>父组件需要接收 <code>IconSelector</code> 传递的图标名称，通过 Vue 3 的 <code>defineAsyncComponent</code> 动态导入对应图标组件，再用 <code>&lt;component :is=&quot;xxx&quot;&gt;</code> 渲染：</p><pre><code class="lang-vue">&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;</code></pre><h1>最后总结</h1><p>这个图标选择器组件的核心优势在于 “高效 + 灵活”：</p><ul><li>借助 lucide-vue-next 的批量导入能力，无需手动导入单个图标，极大提升开发效率；</li><li>基于 shadcn/vue 的基础组件，快速搭建交互界面，样式统一且可复用；</li><li>分页加载和搜索功能确保了大量图标场景下的性能和易用性；</li><li>父组件通过动态组件接收结果，集成成本极低，可灵活用于按钮、表单、导航等多种场景。<br>整个实现过程中，最大的感受是：Vue 3 的组合式 API 和动态组件语法让组件通信和资源加载更灵活，而 lucide-vue-next 与 shadcn/vue 的组合则完美平衡了 “功能丰富” 和 “开发效率”。如果你的 Vue 3 项目也需要图标选择功能，不妨试试这个方案～</li></ul>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://www.dooper.top/index.php/archives/9/#comments</comments>
<wfw:commentRss>https://www.dooper.top/index.php/feed/author/1/</wfw:commentRss>
</item>
<item>
<title>Tauri + Rust 实现 Windows 窗口材质动态切换：从底层 API 到前端交互</title>
<link>https://www.dooper.top/index.php/archives/4/</link>
<guid>https://www.dooper.top/index.php/archives/4/</guid>
<pubDate>Sat, 09 Aug 2025 22:50:00 +0800</pubDate>
<dc:creator>Gabriel Ryder</dc:creator>
<description><![CDATA[注意：这里是 Tauri2最近在做一个 Tauri 桌面应用时，想加个窗口材质切换功能 —— 就是 Windows 11 里的 Mica（云母）、Acrylic（亚克力） 类半透明效果。本来以为...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p>注意：这里是 Tauri2</p><p>最近在做一个 Tauri 桌面应用时，想加个窗口材质切换功能 —— 就是 Windows 11 里的 Mica（云母）、Acrylic（亚克力） 类半透明效果。本来以为 Tauri 自带相关 API，试了才发现：Tauri 确实能在初始化时设置窗口材质，但运行中动态切换是做不到的。没办法，只能自己动手用 Rust 调用系统 API 实现了，这里把过程分享出来，希望能帮到同样踩坑的朋友。</p><h2>先说说 Tauri 在材质设置的局限</h2><p>一开始我查 Tauri 文档，发现可以在 <code>tauri.conf.json</code> 里通过 <code>windows</code> 配置设置 <code>transparent</code> 后，能指定 <code>WindowEffect</code> 来实现材质的更改（还需要设置前端背景为 <code>transparent</code>）。但这些配置都是应用启动时生效的，一旦应用跑起来，就没法通过前端调用修改了。如果用户想在使用中切换不同的窗口效果，原生 API 根本满足不了，所以必须自己写 Rust 后端调用 Windows 的 DWM 接口。</p><h2>核心思路：用 Rust 对接 Windows DWM API</h2><p>Windows 的窗口效果是由<code>Desktop Window Manager（DWM）</code>管理的，它提供了 <code>DwmSetWindowAttribute</code> 和 <code>DwmGetWindowAttribute</code> 两个关键 API，分别用来设置和获取窗口属性。我们要做的就是用 Rust 调用这两个 API，实现材质的动态切换。</p><p>微软文档链接：</p><ol><li><a href="https://learn.microsoft.com/zh-cn/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute">DwmGetWindowAttribute 函数 （dwmapi.h）</a></li><li><a href="https://learn.microsoft.com/zh-cn/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute">DwmSetWindowAttribute 函数 （dwmapi.h）</a></li><li><a href="https://learn.microsoft.com/zh-cn/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute">DWMWINDOWATTRIBUTE 枚举 （dwmapi.h）</a></li></ol><h3>第一步：检测系统版本，确定支持的材质</h3><p>不同 Windows 版本支持的材质不一样，比如 Mica 材质只在 Windows 11（Build 22000+）才支持。所以第一步必须先判断系统版本，避免在不支持的系统上显示无效选项。​<br>我用 Rust 的 <code>winreg</code> 库（需要 <code>cargo add winreg</code> 使用）读取注册表来获取系统版本：</p><pre><code class="lang-rust">// 读取注册表中的系统版本号
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;)?;</code></pre><p>拿到build_number后，就能判断哪些材质可用了。比如 Build 22000 以上支持 Mica、Acrylic等，低版本可能只支持默认材质：</p><pre><code class="lang-rust">// 根据版本动态生成支持的材质列表
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; });
}</code></pre><h3>第二步：获取窗口句柄（HWND）​</h3><p>调用 DWM API 必须要有窗口句柄（HWND），Tauri 里可以通过 <code>window_handle()</code> 方法获取，但需要注意：Tauri 的窗口可能有多层父窗口，必须找到最顶层的那个句柄才有效。​<br>我用 Windows API 的 <code>GetParent函数</code> 循环向上查找顶层窗口：</p><pre><code class="lang-rust">// 从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
}</code></pre><p>这里的 <code>unsafe</code> 块是必须的，因为直接操作系统句柄涉及内存安全，Rust 无法在编译时验证，所以需要开发者自己保证逻辑正确。<br>​</p><h3>第三步：实现材质切换功能​</h3><p>有了窗口句柄和系统版本信息，就可以调用 <code>DwmSetWindowAttribute</code> 设置材质了。核心代码如下：</p><pre><code class="lang-rust">// 设置窗口材质
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()
}</code></pre><p>这里的 <code>material参数</code> 对应 <code>DWM</code> 的枚举值：<code>DWMSBT_AUTO（默认）</code>、<code>DWMSBT_MAINWINDOW（Mica）</code>等，前面定义的MaterialInfo结构体就是把这些值和友好名称对应起来，方便前端显示。​</p><pre><code class="lang-rust">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,
}</code></pre><p>对于样式样式枚举 DWM_SYSTEMBACKDROP_TYPE 的解释：</p><ul><li>DWMSBT_AUTO = 0 自动：默认</li><li>DWMSBT_NONE = 1 无背景样式</li><li>DWMSBT_MAINWINDOW = 2 云母(Mica)</li><li>DWMSBT_TRANSIENTWINDOW = 3 亚克力(Acrylic)</li><li>DWMSBT_TABBEDWINDOW = 4 云母（MicaAlt）</li></ul><h2>前端交互：Vue 3 如何调用后端功能​</h2><p>前端部分很简单，主要是用 Tauri 的 <code>invoke函数</code> 调用 Rust 写的命令，然后渲染材质列表和处理选择事件。​前端组件库则使用 <code>shadcn/vue</code></p><h3>初始化时加载材质列表​</h3><p>页面加载时，先调用后端获取支持的材质和当前材质：</p><pre><code class="lang-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];
    });
});</code></pre><h3>用下拉框实现切换交互​</h3><p>我用了一个 <code>Combobox 组件</code> 展示材质列表，用户选择后调用 <code>set_window_material</code> 命令：</p><pre><code class="lang-vue">&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);
        });
};</code></pre><h2>遇到的坑</h2><ul><li>窗口句柄获取错误：一开始直接用了 Tauri 返回的第一层窗口句柄，调用 DWM API 始终失败，后来才发现需要找顶层父窗口，用 <code>GetParent</code> 循环查找才解决，如果不找，则拿到的是 <code>webview</code> 的子窗口句柄。​</li></ul><h2>最后总结​</h2><p>虽然 Tauri 原生不支持动态切换窗口材质，但通过 Rust 调用 Windows 的 DWM API 完全可以实现。整个过程的核心就是：正确获取窗口句柄→根据系统版本提供合适的材质选项→用 DWM API 执行切换。​<br>这个功能写下来最大的感受是：Rust 虽然入门难，但处理系统级 API 时确实安全又高效；而 Tauri 的前后端通信机制也很方便，让前端能轻松调用底层功能。</p>
]]></content:encoded>
<slash:comments>15</slash:comments>
<comments>https://www.dooper.top/index.php/archives/4/#comments</comments>
<wfw:commentRss>https://www.dooper.top/index.php/feed/author/1/</wfw:commentRss>
</item>
</channel>
</rss>