From 410bd30b2e90d3fe9733b0170dc96905375a5bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E7=BB=AB?= <486494914@qq.com> Date: Wed, 2 Sep 2026 07:57:39 +0800 Subject: [PATCH] 8 --- frontend/src/utils/clipboard.js | 37 +++++++++++++++++++++++++++ frontend/src/utils/clipboard.ts | 40 ++++++++++++++++++++++++++++++ frontend/src/views/KbDetail.vue | 9 ++++--- frontend/src/views/KbDetail.vue.js | 9 ++++--- frontend/tsconfig.tsbuildinfo | 2 +- 5 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 frontend/src/utils/clipboard.js create mode 100644 frontend/src/utils/clipboard.ts diff --git a/frontend/src/utils/clipboard.js b/frontend/src/utils/clipboard.js new file mode 100644 index 0000000..7fe9d3f --- /dev/null +++ b/frontend/src/utils/clipboard.js @@ -0,0 +1,37 @@ +/** + * 兼容所有浏览器的复制到剪贴板函数 + * 解决 iOS Safari 不支持 navigator.clipboard.writeText() 的问题 + */ +export async function copyToClipboard(text) { + // 方法1: 现代 Clipboard API(Chrome/Firefox/Edge 桌面端) + if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { + try { + await navigator.clipboard.writeText(text); + return true; + } + catch { + // Safari 可能抛出 NotAllowedError,继续尝试 fallback + } + } + // 方法2: 传统 execCommand(iOS Safari 兼容方案) + try { + const textarea = document.createElement('textarea'); + textarea.value = text; + // 防止页面滚动 + textarea.style.position = 'fixed'; + textarea.style.left = '-9999px'; + textarea.style.top = '-9999px'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + // iOS Safari 需要设置 selection range + textarea.focus(); + textarea.select(); + textarea.setSelectionRange(0, textarea.value.length); + const success = document.execCommand('copy'); + document.body.removeChild(textarea); + return success; + } + catch { + return false; + } +} diff --git a/frontend/src/utils/clipboard.ts b/frontend/src/utils/clipboard.ts new file mode 100644 index 0000000..b8c5520 --- /dev/null +++ b/frontend/src/utils/clipboard.ts @@ -0,0 +1,40 @@ +/** + * 兼容所有浏览器的复制到剪贴板函数 + * 解决 iOS Safari 不支持 navigator.clipboard.writeText() 的问题 + */ +export async function copyToClipboard(text: string): Promise { + // 方法1: 现代 Clipboard API(Chrome/Firefox/Edge 桌面端) + if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { + try { + await navigator.clipboard.writeText(text) + return true + } catch { + // Safari 可能抛出 NotAllowedError,继续尝试 fallback + } + } + + // 方法2: 传统 execCommand(iOS Safari 兼容方案) + try { + const textarea = document.createElement('textarea') + textarea.value = text + + // 防止页面滚动 + textarea.style.position = 'fixed' + textarea.style.left = '-9999px' + textarea.style.top = '-9999px' + textarea.style.opacity = '0' + + document.body.appendChild(textarea) + + // iOS Safari 需要设置 selection range + textarea.focus() + textarea.select() + textarea.setSelectionRange(0, textarea.value.length) + + const success = document.execCommand('copy') + document.body.removeChild(textarea) + return success + } catch { + return false + } +} diff --git a/frontend/src/views/KbDetail.vue b/frontend/src/views/KbDetail.vue index 64f2c91..f6ccf8a 100644 --- a/frontend/src/views/KbDetail.vue +++ b/frontend/src/views/KbDetail.vue @@ -3,6 +3,7 @@ import { ref, onMounted, computed } from 'vue' import { useRoute } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import apiClient from '@/api/client' +import { copyToClipboard } from '@/utils/clipboard' const route = useRoute() const kbId = route.params.id as string @@ -173,10 +174,12 @@ async function handleReprocess(doc: any) { } async function handleCopyLink() { - try { - await navigator.clipboard.writeText(aiUrl.value) + const success = await copyToClipboard(aiUrl.value) + if (success) { ElMessage.success('AI 链接已复制!') - } catch { /* ignore */ } + } else { + ElMessage.error('复制失败,请手动长按复制。') + } } // 重新生成 AI 链接 diff --git a/frontend/src/views/KbDetail.vue.js b/frontend/src/views/KbDetail.vue.js index aa28d66..5160e94 100644 --- a/frontend/src/views/KbDetail.vue.js +++ b/frontend/src/views/KbDetail.vue.js @@ -2,6 +2,7 @@ import { ref, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { ElMessage, ElMessageBox } from 'element-plus'; import apiClient from '@/api/client'; +import { copyToClipboard } from '@/utils/clipboard'; const route = useRoute(); const kbId = route.params.id; const isMobile = ref(window.innerWidth <= 768); @@ -162,11 +163,13 @@ async function handleReprocess(doc) { catch { /* ignore */ } } async function handleCopyLink() { - try { - await navigator.clipboard.writeText(aiUrl.value); + const success = await copyToClipboard(aiUrl.value); + if (success) { ElMessage.success('AI 链接已复制!'); } - catch { /* ignore */ } + else { + ElMessage.error('复制失败,请手动长按复制。'); + } } // 重新生成 AI 链接 async function handleRegenerateLink() { diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index 9a19fb0..1ed1869 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/env.d.ts","./src/main.ts","./src/api/client.ts","./src/router/index.ts","./src/stores/user.ts","./src/app.vue","./src/layouts/defaultlayout.vue","./src/views/dashboard.vue","./src/views/kbdetail.vue","./src/views/knowledgebases.vue","./src/views/login.vue","./src/views/register.vue","./src/views/settings.vue"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/env.d.ts","./src/main.ts","./src/api/client.ts","./src/router/index.ts","./src/stores/user.ts","./src/utils/clipboard.ts","./src/app.vue","./src/layouts/defaultlayout.vue","./src/views/dashboard.vue","./src/views/kbdetail.vue","./src/views/knowledgebases.vue","./src/views/login.vue","./src/views/register.vue","./src/views/settings.vue"],"version":"5.9.3"} \ No newline at end of file