news 2026/9/10 1:24:54

AIRI 项目中的 VueUse useClipboard 实战指南:响应式剪贴板读写与降级方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AIRI 项目中的 VueUse useClipboard 实战指南:响应式剪贴板读写与降级方案

AIRI 项目中的 VueUse useClipboard 实战指南:响应式剪贴板读写与降级方案

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

useClipboard 是 VueUse 提供的响应式 Clipboard API 封装,它让 Vue 组件能够以声明式方式响应剪切、复制、粘贴命令,并异步读写系统剪贴板。在 AIRI(self-hosted 的 AI 伴侣桌面应用,Web / macOS / Windows 多端支持)中,它被广泛用于"一键复制鉴权 Token""复制 Bug 报告内容""复制图片 Data URL"等高频交互场景。阅读完本文,你将掌握 useClipboard 的全部选项、返回值、legacy 降级方案与组件式用法,并看到它在 AIRI 真实源码中的落地模式。

useClipboard 解决了什么问题

浏览器原生 Clipboard API 提供了navigator.clipboard.writeText()navigator.clipboard.readText()等能力,但使用起来存在几个痛点:

  • 权限门槛:剪贴板内容的读写被 Permissions API 门控,未经用户授权,读取或修改剪贴板内容是不被允许的;
  • 非响应式:复制是否成功、当前剪贴板内容是什么,原生 API 不会以响应式状态暴露给 UI;
  • 兼容性差异:部分浏览器或嵌入式 WebView(如 Electron 旧版本渲染进程)不支持 Clipboard API,需要回退方案;
  • 重复样板:每个需要复制的按钮都要重复编写 try/catch、状态复位定时器等代码。

useClipboard 将这些全部收敛为一个 composable:返回textcopiedcopyisSupported等响应式状态与操作函数,并在 1.5 秒后自动将copied复位为false,让"复制成功"的 UI 反馈天然响应式。

在 AIRI 仓库中,@vueuse/core通过 pnpm workspace 的 catalog 统一管理,版本为^14.4.0(见 pnpm-workspace.yaml),各应用与包如 stage-ui 的 package.json 均从 catalog 引入,保证多端行为一致。

基础用法

<script setup>中引入并解构使用:

<script setup lang="ts"> import { useClipboard } from '@vueuse/core' const source = ref('Hello') const { text, copy, copied, isSupported } = useClipboard({ source }) </script> <template> <div v-if="isSupported"> <button @click="copy(source)"> <!-- by default, `copied` will be reset in 1.5s --> <span v-if="!copied">Copy</span> <span v-else>Copied!</span> </button> <p>Current copied: <code>{{ text || 'none' }}</code></p> </div> <p v-else> Your browser does not support Clipboard API </p> </template>

要点说明:

  • copy(source)传入的source可以是 ref 或 getter,调用时会取当前值写入剪贴板;
  • 模板中用v-if="isSupported"做能力探测分支,避免在不支持的浏览器中渲染不可用的按钮;
  • copied在复制成功后自动置true,默认 1.5 秒后复位,用于按钮文案从 "Copy" 切换到 "Copied!"。

Options 选项详解

OptionTypeDefaultDescription
sourceMaybeRefOrGetter<string>copy()无参数调用时默认复制的内容
readbooleanfalse是否在 copy/cut 事件时启用剪贴板内容读取
copiedDuringnumber1500copied复位为false前的毫秒数
legacybooleanfalseClipboard API 不可用时回退到document.execCommand

对每个选项的深入解读:

  • source:配置后,copy()不传参也能复制。它接受 ref 或 getter,因此可以与表单输入框的v-modelref 直接绑定——AIRI 的连接设置页正是利用这一点复制动态变化的 Token(见下文源码实例)。
  • read:设为true后,composable 会监听浏览器的 copy/cut 事件并读取剪贴板内容到textref,适合构建"粘贴板预览"类功能。默认false是为了避免无谓的权限请求。
  • copiedDuring:控制成功反馈的持续时间,单位毫秒。如设为0copied几乎立即复位,可用于纯触发式场景。
  • legacy:关键兼容性开关。启用后,当navigator.clipboard不可用时,会改用document.execCommand('copy')+ 临时隐藏文本域的方式完成复制,保证 Electron 旧内核或老浏览器中功能不缺失。

返回值说明

PropertyTypeDescription
isSupportedComputedRef<boolean>剪贴板是否受支持(原生或 legacy 模式)
textRef<string>当前剪贴板内容(仅在read: true时有效)
copiedRef<boolean>复制成功后为true,自动复位
copy(text?: string) => Promise<void>将文本复制到剪贴板

其中isSupported在 legacy 模式下会在原生 API 缺失时依然返回true(因为走 execCommand 回退),这与它"native or legacy"的语义一致。copy是异步函数,务必await并捕获 rejection——权限被拒绝、文档未聚焦等情况都会导致 Promise 失败。

Legacy 模式:老环境的兜底方案

当 Clipboard API 不可用(例如某些非安全上下文、旧版 WebView)时,设置legacy: true即可保留复制能力:

const { copy, isSupported } = useClipboard({ legacy: true })

内部实现会用document.execCommand('copy')作为回退。需要说明的是,execCommand 方案存在历史局限(依赖用户手势、可能影响选区),因此它只应作为兼容兜底,而不是默认首选。AIRI 在渲染错误上报与鉴权 Token 复制两处都显式启用了legacy: true,正是因为这些场景跨 Web 与 Electron 桌面端运行,必须保证所有环境下"复制"按钮都可用。

Component 用法:无脚本式复制按钮

如果不想写任何<script setup>逻辑,可以使用随 VueUse 导出的<UseClipboard>组件,通过 scoped slot 拿到copycopied

<template> <UseClipboard v-slot="{ copy, copied }" source="copy me"> <button @click="copy()"> {{ copied ? 'Copied' : 'Copy' }} </button> </UseClipboard> </template>

source作为组件 prop 传入,slot 内部直接调用copy()即可复制。适合纯展示型组件或 Storybook / Histoire 场景快速演示。

类型声明解读

完整类型声明如下:

export interface UseClipboardOptions<Source> extends ConfigurableNavigator { /** * Enabled reading for clipboard * * @default false */ read?: boolean /** * Copy source */ source?: Source /** * Milliseconds to reset state of `copied` ref * * @default 1500 */ copiedDuring?: number /** * Whether fallback to document.execCommand('copy') if clipboard is undefined. * * @default false */ legacy?: boolean } type ClipboardValue = string | (() => Promise<string | undefined>) export interface UseClipboardReturn<Optional> extends Supportable { text: Readonly<ShallowRef<string>> copied: Readonly<ShallowRef<boolean>> copyPending: Readonly<ShallowRef<boolean>> copy: Optional extends true ? (text?: ClipboardValue) => Promise<void> : (text: ClipboardValue) => Promise<void> } export declare function useClipboard( options?: UseClipboardOptions<undefined>, ): UseClipboardReturn<false> export declare function useClipboard( options: UseClipboardOptions<MaybeRefOrGetter<string>>, ): UseClipboardReturn<true>

几个值得注意的细节:

  • UseClipboardOptions<Source>继承ConfigurableNavigator,因此还支持通过window选项自定义全局对象(便于测试注入);
  • copyPending是额外暴露的"复制进行中"状态,可用于按钮 loading;
  • 通过泛型Optional区分重载:传入sourcecopy的文本参数变为可选,未传sourcecopy(text)必须显式传参——这种条件类型设计让"默认复制源"在编译期就得到约束;
  • textcopied均为Readonly<ShallowRef>,只读且浅层响应,避免外部误改。

AIRI 仓库中的真实落地

以下四个源码位置展示了 useClipboard 在不同业务场景中的典型用法,均可在当前仓库直接查阅。

场景一:渲染错误上报(legacy 兜底 + 异步复制)

在 stage-render-error.vue 中,舞台渲染出错时提供"复制报告并提交"的能力:

const { copy, isSupported: isClipboardSupported } = useClipboard({ legacy: true }) async function submitBugReport(payload: BugReportDialogSubmitPayload) { bugReportSending.value = true try { if (!isClipboardSupported.value) throw new Error('Clipboard API is unavailable') await copy(payload.formattedReport) showBugReportDialog.value = false } catch (error) { bugReportSubmitError.value = error } finally { bugReportSending.value = false } }

这段代码示范了规范写法:先校验isClipboardSupported,再await copy(),并将失败原因捕获后回显到弹窗中,配合finally复位 loading 状态。桌面端「关于页」的更新错误上报采用了几乎相同的模式(见 about.vue),构建信息、更新状态、错误信息被拼装为多行文本后一键复制,方便用户粘贴到反馈渠道。

场景二:复制鉴权 Token(source 动态绑定 + 图标反馈)

在 settings/connection/index.vue 中,服务器连接页的鉴权 Token 输入框与useClipboard直接联动:

const { copied: authTokenCopied, copy: copyAuthToken, isSupported: isClipboardSupported } = useClipboard({ source: authTokenInput, legacy: true }) const canCopyAuthToken = computed(() => isClipboardSupported.value && authTokenInput.value.length > 0)

模板中复制按钮的图标随authTokenCopied切换(从 copy 图标变为 check 图标),同时通过canCopyAuthToken在 Token 为空或环境不支持时禁用按钮:

<IconButton type="button" :icon="authTokenCopied ? 'i-solar:check-circle-bold-duotone' : 'i-solar:copy-line-duotone'" :disabled="!canCopyAuthToken" >const imageDataURL = ref<string>('') const { copy } = useClipboard({ source: imageDataURL })

配合@click="() => copy()"即可把超长的 Base64 Data URL 一键复制到剪贴板,用于把调试图片粘贴到其他工具中分析。

最佳实践小结

结合官方文档与 AIRI 源码实践,推荐以下使用准则:

  1. 先探测后使用:渲染前用isSupportedv-if分支,或配合canCopy计算属性禁用按钮,避免无效点击;
  2. 始终 await copycopy返回 Promise,必须在 try/catch 中等待并处理权限拒绝等失败路径,AIRI 的错误上报组件即为其范本;
  3. 跨端场景开启 legacy:只要应用可能运行在 Electron、老 WebView 或非安全上下文,就传legacy: true保住复制能力底线;
  4. 让 source 跟随数据:把可变的复制内容声明为 ref 并绑定到source,调用copy()时自动取最新值,减少显式传参带来的同步成本;
  5. 利用 copied 做轻量反馈:默认 1.5 秒自动复位,无需手写定时器;若需更长展示时间,用copiedDuring调整。

AIRI 的源码证据表明,一个十几行的小 composable 就能在错误上报、Token 复制、调试工具等多个模块中提供一致、可靠、跨端的剪贴板能力,这正是 VueUse 组合式函数"以响应式方式包装浏览器 API"理念的最佳注脚。

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 1:23:31

SAP年结必看:FAGLGVTR与F.16总账余额结转实操与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 1:20:47

基于Django+Vue3的校园租房系统全栈开发实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 1:20:46

从457页指引到落地:数据要素场景拆解与数据资产盘点实战

最近部门开项目复盘会&#xff0c;好几个项目经理都在吐槽同一件事&#xff1a;那份457页的“数据要素”典型场景指引&#xff0c;翻到第100页就放弃了&#xff0c;太厚&#xff0c;读不下去。但恰恰是这份被大家当成“床头催眠读物”的文件&#xff0c;把工业制造、现代农业、…

作者头像 李华