@@ -501,37 +501,61 @@ export default {
501501
502502 // 复制代码到剪贴板
503503 tryExecCommandCopy (text ) {
504+ let textArea = null ;
504505 try {
505- const textArea = document .createElement (' textarea' );
506+ textArea = document .createElement (' textarea' );
506507 textArea .value = text;
507508 document .body .appendChild (textArea);
508509 textArea .focus ();
509510 textArea .select ();
510511 const ok = document .execCommand (' copy' );
511- document .body .removeChild (textArea);
512512 return ok;
513513 } catch (_) {
514514 return false ;
515+ } finally {
516+ try {
517+ textArea? .remove ? .();
518+ } catch (_) {
519+ // ignore cleanup errors
520+ }
515521 }
516522 },
517523
518524 async copyTextToClipboard (text ) {
519525 // 优先使用同步复制,尽量保留用户手势上下文;
520526 // 在非安全来源(例如通过局域网 IP + vite --host)时成功率更高。
521527 if (this .tryExecCommandCopy (text)) {
522- return true ;
528+ return { ok : true , method : ' execCommand ' } ;
523529 }
524530
525531 if (navigator .clipboard ? .writeText ) {
526532 try {
527533 await navigator .clipboard .writeText (text);
528- return true ;
529- } catch (_ ) {
530- return false ;
534+ return { ok : true , method : ' clipboard ' } ;
535+ } catch (error ) {
536+ return { ok : false , method : ' clipboard ' , error } ;
531537 }
532538 }
533539
534- return false ;
540+ return { ok: false , method: ' unavailable' };
541+ },
542+
543+ async copyWithFeedback (text , messageIndex = null ) {
544+ const result = await this .copyTextToClipboard (text);
545+ const ok = !! result? .ok ;
546+
547+ if (messageIndex !== null && messageIndex !== undefined ) {
548+ if (ok) this .showCopySuccess (messageIndex);
549+ else this .showCopyFailure (messageIndex);
550+ }
551+
552+ if (ok) {
553+ this .toast ? .success ? .(this .t (' core.common.copied' ));
554+ } else {
555+ this .toast ? .error ? .(this .t (' core.common.copyFailed' ));
556+ }
557+
558+ return result;
535559 },
536560
537561 buildCopyTextFromParts (messageParts ) {
@@ -565,30 +589,15 @@ export default {
565589
566590 async copyCodeToClipboard (code ) {
567591 const text = String (code ?? ' ' );
568- if (! text) return false ;
569-
570- const ok = await this .copyTextToClipboard (text);
571- if (ok) {
572- this .toast ? .success ? .(this .t (' core.common.copied' ));
573- } else {
574- this .toast ? .error ? .(this .t (' core.common.copyFailed' ));
575- }
576- return ok;
592+ if (! text) return { ok: false , method: ' empty' };
593+ return await this .copyWithFeedback (text, null );
577594 },
578595
579596 // 复制bot消息到剪贴板
580597 async copyBotMessage (messageParts , messageIndex ) {
581598 let textToCopy = this .buildCopyTextFromParts (messageParts);
582599 if (! textToCopy) textToCopy = ' [媒体内容]' ;
583-
584- const ok = await this .copyTextToClipboard (textToCopy);
585- if (ok) {
586- this .showCopySuccess (messageIndex);
587- this .toast ? .success ? .(this .t (' core.common.copied' ));
588- } else {
589- this .showCopyFailure (messageIndex);
590- this .toast ? .error ? .(this .t (' core.common.copyFailed' ));
591- }
600+ await this .copyWithFeedback (textToCopy, messageIndex);
592601 },
593602
594603 // 显示复制成功提示
@@ -673,7 +682,8 @@ export default {
673682 button .innerHTML = this .getCopyIconSvg ();
674683 button .title = this .t (' core.common.copy' );
675684 button .addEventListener (' click' , async () => {
676- const ok = await this .copyCodeToClipboard (codeBlock .textContent || ' ' );
685+ const res = await this .copyCodeToClipboard (codeBlock .textContent || ' ' );
686+ const ok = !! res? .ok ;
677687 button .innerHTML = ok ? this .getSuccessIconSvg () : this .getErrorIconSvg ();
678688 button .style .color = ok ? ' #4caf50' : ' #f44336' ;
679689 button .setAttribute (" title" , this .t (` core.common.${ ok ? " copied" : " copyFailed" } ` ));
0 commit comments