{"record":{"id":"9d989b7f94a1ac5b","repo":"flipped-aurora/gin-vue-admin","slug":"copy-failed","errorCode":null,"errorMessage":"copy failed","messagePattern":"copy failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/view/systemTools/logViewer/index.vue","lineNumber":602,"sourceCode":"  if (path !== activePath.value) return\n  const nextPath = openedPaths.value[index] || openedPaths.value[index - 1] || ''\n  resetContentState()\n  if (nextPath) openFile(nextPath)\n}\n\nasync function copyContent() {\n  if (!content.value) return\n  try {\n    if (navigator.clipboard?.writeText) {\n      await navigator.clipboard.writeText(content.value)\n    } else {\n      const textarea = document.createElement('textarea')\n      textarea.value = content.value\n      textarea.setAttribute('readonly', '')\n      textarea.className = 'fixed left-[-9999px] top-0'\n      document.body.appendChild(textarea)\n      textarea.select()\n      if (!document.execCommand('copy')) throw new Error('copy failed')\n      document.body.removeChild(textarea)\n    }\n    ElMessage.success('日志内容已复制')\n  } catch {\n    ElMessage.error('复制失败')\n  }\n}\n\nconst updateRootHeight = () => {\n  const element = rootRef.value\n  if (!element) return\n  const top = element.getBoundingClientRect().top\n  rootHeight.value = `${Math.max(320, window.innerHeight - top - 32)}px`\n}\n\nonMounted(() => {\n  nextTick(updateRootHeight)\n  window.addEventListener('resize', updateRootHeight)","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/web/src/view/systemTools/logViewer/index.vue#L584-L620","documentation":"The log viewer's copy function falls back to the deprecated document.execCommand('copy') when the Clipboard API is unavailable. execCommand returns false when the browser refuses the copy, so the code throws 'copy failed' and shows ElMessage error '复制失败'.","triggerScenarios":"navigator.clipboard is unavailable (non-secure context) AND document.execCommand('copy') returns false, e.g. the synthetic textarea is not selectable or copy is blocked.","commonSituations":"Page served over plain HTTP (clipboard API requires HTTPS/localhost); browser blocks programmatic copy without user activation; iframe with restrictive permissions policy.","solutions":["Serve the app over HTTPS or localhost so navigator.clipboard.writeText is available","Ensure the copy runs inside the click handler (user activation) rather than async after await","Fall back to showing the log content in a selectable modal so the user can copy manually","Handle the textarea removal in a finally block so the hidden node is never leaked"],"exampleFix":"// before\ntextarea.select()\nif (!document.execCommand('copy')) throw new Error('copy failed')\ndocument.body.removeChild(textarea)\n// after\ntextarea.select()\nlet ok = false\ntry {\n  ok = document.execCommand('copy')\n} finally {\n  document.body.removeChild(textarea)\n}\nif (!ok) throw new Error('copy failed')","handlingStrategy":"fallback","validationCode":"const canUseClipboard = navigator.clipboard && window.isSecureContext\nif (!canUseClipboard) console.warn('Clipboard API unavailable, execCommand fallback will be used')","typeGuard":"function supportsClipboardAPI() {\n  return Boolean(navigator.clipboard && typeof navigator.clipboard.writeText === 'function')\n}","tryCatchPattern":"try {\n  await copyLog(content.value)\n  ElMessage.success('日志内容已复制')\n} catch {\n  ElMessage.error('复制失败，请手动选择文本复制')\n}","preventionTips":["Prefer navigator.clipboard.writeText with execCommand only as last resort","Call copy synchronously inside the click handler to keep user activation","Serve the app over HTTPS so the Clipboard API is available","Remove the temp textarea in a finally block"],"tags":["clipboard","browser","dom"],"backgroundTag":"clipboard-copy-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}