diff --git a/apps/admin-web/src/lib/clipboard.ts b/apps/admin-web/src/lib/clipboard.ts new file mode 100644 index 0000000..9281d4a --- /dev/null +++ b/apps/admin-web/src/lib/clipboard.ts @@ -0,0 +1,32 @@ +export async function copyTextToClipboard(text: string): Promise { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text) + return + } + } catch { + // Fall back to the selection-based copy path below. + } + + const textarea = document.createElement('textarea') + textarea.value = text + textarea.setAttribute('readonly', 'true') + textarea.style.position = 'fixed' + textarea.style.top = '0' + textarea.style.left = '-9999px' + textarea.style.opacity = '0' + + document.body.appendChild(textarea) + textarea.focus() + textarea.select() + textarea.setSelectionRange(0, textarea.value.length) + + try { + const copied = document.execCommand('copy') + if (!copied) { + throw new Error('copy command failed') + } + } finally { + document.body.removeChild(textarea) + } +} diff --git a/apps/admin-web/src/views/FreeCreateView.vue b/apps/admin-web/src/views/FreeCreateView.vue index 13b2dd5..8e7be62 100644 --- a/apps/admin-web/src/views/FreeCreateView.vue +++ b/apps/admin-web/src/views/FreeCreateView.vue @@ -17,6 +17,7 @@ import PublishArticleModal from '@/components/PublishArticleModal.vue' import { articlesApi } from '@/lib/api' import { buildArticleClipboardContent, resolveArticleActionState } from '@/lib/article-list-actions' import { useArticleRegenerateAction } from '@/lib/article-regenerate-action' +import { copyTextToClipboard } from '@/lib/clipboard' import { getPublishStatusMeta } from '@/lib/display' import { formatError } from '@/lib/errors' @@ -191,7 +192,7 @@ async function copyArticle(articleId: number): Promise { return } - await navigator.clipboard.writeText(content) + await copyTextToClipboard(content) message.success(t('common.copySuccess')) } catch (error) { message.error(formatError(error) || t('common.noData'))