From da4f08e9a16c84bdfd937fa2c5c34b8fce705666 Mon Sep 17 00:00:00 2001 From: duorameng <2997944583@qq.com> Date: Tue, 9 Jun 2026 16:12:55 +0800 Subject: [PATCH] refactor: use fallback clipboard for non-https env (#111) --- .../views/agents/components/TokenListTab.vue | 6 ++-- web/src/views/notify/Notify.vue | 13 +++++---- web/src/views/notify/components/ApiUsage.vue | 29 ++++++++++--------- .../views/notify/components/ChannelList.vue | 19 +++++++----- web/src/views/settings/SiteSettings.vue | 7 +++-- web/src/views/tasks/RepoDialog.vue | 6 ++-- web/src/views/tasks/Tasks.vue | 13 +++++---- web/src/views/terminal/Terminal.vue | 28 ++++-------------- 8 files changed, 60 insertions(+), 61 deletions(-) diff --git a/web/src/views/agents/components/TokenListTab.vue b/web/src/views/agents/components/TokenListTab.vue index 038375a..38d886d 100644 --- a/web/src/views/agents/components/TokenListTab.vue +++ b/web/src/views/agents/components/TokenListTab.vue @@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button' import { Plus, Ticket, Check, X, Copy, Pencil, Trash2 } from 'lucide-vue-next' import { type AgentToken, api } from '@/api' import { toast } from 'vue-sonner' +import { copyToClipboard } from '@/utils/clipboard' const props = defineProps<{ tokens: AgentToken[] @@ -25,8 +26,9 @@ function isTokenExhausted(token: AgentToken) { } function copyToken(token: string) { - navigator.clipboard.writeText(token) - toast.success('已复制') + copyToClipboard(token).then((success) => { + if (success) toast.success('已复制') + }) } function openTokenDialog() { diff --git a/web/src/views/notify/Notify.vue b/web/src/views/notify/Notify.vue index 6ae7eb9..117077c 100644 --- a/web/src/views/notify/Notify.vue +++ b/web/src/views/notify/Notify.vue @@ -11,6 +11,7 @@ import EventBinding from './components/EventBinding.vue' import ApiUsage from './components/ApiUsage.vue' import ChannelDialog from './components/ChannelDialog.vue' import TemplateSettings from './components/TemplateSettings.vue' +import { copyToClipboard } from '@/utils/clipboard' const activeTab = ref('channels') @@ -291,10 +292,10 @@ async function copyApiToken() { toast.error('请先生成 Token') return } - try { - await navigator.clipboard.writeText(apiToken.value) + const success = await copyToClipboard(apiToken.value) + if (success) { toast.success('Token 已复制到剪贴板') - } catch { + } else { toast.error('复制失败') } } @@ -311,10 +312,10 @@ async function copyApiExample() { -H "Content-Type: application/json" \\ -H "notify-token: ${token}" \\ -d '{"channel_id": "${ch.id}", "title": "测试通知", "text": "来自脚本的通知"}'` - try { - await navigator.clipboard.writeText(example) + const success = await copyToClipboard(example) + if (success) { toast.success('API 调用示例已复制到剪贴板') - } catch { + } else { toast.error('复制失败') } } diff --git a/web/src/views/notify/components/ApiUsage.vue b/web/src/views/notify/components/ApiUsage.vue index 11e6456..1cf3cd5 100644 --- a/web/src/views/notify/components/ApiUsage.vue +++ b/web/src/views/notify/components/ApiUsage.vue @@ -19,6 +19,7 @@ import { import type { NotifyChannel, ChannelType } from '@/api' import { ref, computed } from 'vue' import { toast } from 'vue-sonner' +import { copyToClipboard } from '@/utils/clipboard' const props = defineProps<{ channels: NotifyChannel[] @@ -49,13 +50,15 @@ function handleConfirmGenerate() { emit('generateToken') } -function copyToClipboard(text: string, blockId: string) { - navigator.clipboard.writeText(text).then(() => { - copiedBlock.value = blockId - toast.success('已复制到剪贴板') - setTimeout(() => { - copiedBlock.value = null - }, 2000) +function handleCopy(text: string, blockId: string) { + copyToClipboard(text).then((success) => { + if (success) { + copiedBlock.value = blockId + toast.success('已复制到剪贴板') + setTimeout(() => { + copiedBlock.value = null + }, 2000) + } }) } @@ -264,7 +267,7 @@ const currentExample = computed(() => {
-
@@ -361,7 +364,7 @@ const currentExample = computed(() => {
@@ -389,7 +392,7 @@ const currentExample = computed(() => {
import baihu
baihu.notify("标题", "内容")
@@ -406,7 +409,7 @@ const currentExample = computed(() => {
const baihu = require('baihu');
baihu.notify("标题", "内容");
@@ -452,7 +455,7 @@ const currentExample = computed(() => {
diff --git a/web/src/views/notify/components/ChannelList.vue b/web/src/views/notify/components/ChannelList.vue index 5399211..a892369 100644 --- a/web/src/views/notify/components/ChannelList.vue +++ b/web/src/views/notify/components/ChannelList.vue @@ -6,6 +6,7 @@ import { Plus, Trash2, TestTube, Pencil, Bell, Calendar, Copy, Check } from 'luc import type { NotifyChannel, ChannelType } from '@/api' import { ref } from 'vue' import { toast } from 'vue-sonner' +import { copyToClipboard } from '@/utils/clipboard' defineProps<{ channels: NotifyChannel[] @@ -26,13 +27,15 @@ function getChannelTypeName(type: string, channelTypes: ChannelType[]): string { const copiedBlock = ref(null) -function copyToClipboard(text: string, blockId: string) { - navigator.clipboard.writeText(text).then(() => { - copiedBlock.value = blockId - toast.success('已复制长 ID') - setTimeout(() => { - copiedBlock.value = null - }, 2000) +function handleCopy(text: string, blockId: string) { + copyToClipboard(text).then((success) => { + if (success) { + copiedBlock.value = blockId + toast.success('已复制长 ID') + setTimeout(() => { + copiedBlock.value = null + }, 2000) + } }) } @@ -94,7 +97,7 @@ function copyToClipboard(text: string, blockId: string) {
diff --git a/web/src/views/settings/SiteSettings.vue b/web/src/views/settings/SiteSettings.vue index 78cf27d..35fbe13 100644 --- a/web/src/views/settings/SiteSettings.vue +++ b/web/src/views/settings/SiteSettings.vue @@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button' import { api, type SiteSettings } from '@/api' import { toast } from 'vue-sonner' import { useSiteSettings } from '@/composables/useSiteSettings' +import { copyToClipboard as copyTextToClipboard } from '@/utils/clipboard' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { RefreshCw, Copy, AlertTriangle, ExternalLink, Info, Clock } from 'lucide-vue-next' @@ -107,10 +108,10 @@ async function generateOpenapiToken() { async function copyOpenapiToken() { if (!form.value.openapi_token) return - try { - await navigator.clipboard.writeText(form.value.openapi_token) + const success = await copyTextToClipboard(form.value.openapi_token) + if (success) { toast.success('Token 已复制到剪贴板') - } catch { + } else { toast.error('复制失败,请手动复制') } } diff --git a/web/src/views/tasks/RepoDialog.vue b/web/src/views/tasks/RepoDialog.vue index d98964d..47fd429 100644 --- a/web/src/views/tasks/RepoDialog.vue +++ b/web/src/views/tasks/RepoDialog.vue @@ -16,6 +16,7 @@ import { toast } from 'vue-sonner' import { cn } from '@/lib/utils' import { parseBaihuCommand, parseQlCommand } from '@/utils/repo-parser' +import { copyToClipboard } from '@/utils/clipboard' import TaskNotificationConfig from './components/TaskNotificationConfig.vue' import TaskAdvancedConfig from './components/TaskAdvancedConfig.vue' import TaskCronConfig from './components/TaskCronConfig.vue' @@ -120,8 +121,9 @@ function exportBaihuCommand() { } const cmd = parts.join(' ') - navigator.clipboard.writeText(cmd) - toast.success('baihu 指令已复制到剪贴板') + copyToClipboard(cmd).then((success) => { + if (success) toast.success('baihu 指令已复制到剪贴板') + }) } function importFromBaihu() { diff --git a/web/src/views/tasks/Tasks.vue b/web/src/views/tasks/Tasks.vue index a70c232..2f0a7fa 100644 --- a/web/src/views/tasks/Tasks.vue +++ b/web/src/views/tasks/Tasks.vue @@ -31,6 +31,7 @@ import { TASK_TYPE, AGENT_STATUS, TRIGGER_TYPE, TASK_STATUS } from '@/constants' import TextOverflow from '@/components/TextOverflow.vue' import { getCronDescription } from '@/utils/cron' import { generateBaihuCommand } from '@/utils/repo-parser' +import { copyToClipboard } from '@/utils/clipboard' const router = useRouter() @@ -186,11 +187,13 @@ function openExportDialog(task: Task) { function copyCommandText() { if (!exportCommandText.value) return - navigator.clipboard.writeText(exportCommandText.value).then(() => { - toast.success('同步指令已复制到剪贴板') - showExportDialog.value = false - }).catch(() => { - toast.error('复制失败,请手动选择复制') + copyToClipboard(exportCommandText.value).then((success) => { + if (success) { + toast.success('同步指令已复制到剪贴板') + showExportDialog.value = false + } else { + toast.error('复制失败,请手动选择复制') + } }) } diff --git a/web/src/views/terminal/Terminal.vue b/web/src/views/terminal/Terminal.vue index edd6a84..a1dd0e7 100644 --- a/web/src/views/terminal/Terminal.vue +++ b/web/src/views/terminal/Terminal.vue @@ -47,34 +47,18 @@ function handleStatusChange(status: any) { statusState.value = status } +import { copyToClipboard } from '@/utils/clipboard' + const copiedCommand = ref(null) -const copyToClipboard = async (text: string) => { - try { - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(text) - } else { - const textArea = document.createElement("textarea") - textArea.value = text - textArea.style.position = "absolute" - textArea.style.opacity = "0" - document.body.prepend(textArea) - textArea.select() - try { - document.execCommand('copy') - } catch (err) { - console.error('Fallback copy failed', err) - } finally { - textArea.remove() - } - } +const handleCopy = async (text: string) => { + const success = await copyToClipboard(text) + if (success) { copiedCommand.value = text setTimeout(() => { if (copiedCommand.value === text) { copiedCommand.value = null } }, 2000) - } catch (err) { - console.error('Failed to copy', err) } } @@ -105,7 +89,7 @@ const copyToClipboard = async (text: string) => { baihu {{ cmd.name }} {{ cmd.description }}
-