fix(ui): 报告复制在 HTTP 非安全上下文下失效,增加 execCommand 降级

Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB
This commit is contained in:
Autumn-27 committed 2026-08-20 11:21:07 -04:00
1 parent d14ce4d9f4
commit ed96f256a4
1 file changed
+37 -5
@@ -10,6 +10,34 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { api } from "@/lib/api";
async function copyText(text: string): Promise<boolean> {
// 优先使用异步剪贴板 API(仅在 HTTPS / localhost 等安全上下文可用)
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// 继续走降级方案
}
}
// 降级:HTTP 等非安全上下文下用 execCommand
try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
textarea.style.top = "0";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
return ok;
} catch {
return false;
}
}
export function ReportTab({ taskId }: { taskId: string }) {
const [report, setReport] = React.useState<string>("");
const [loading, setLoading] = React.useState(true);
@@ -34,12 +62,16 @@ export function ReportTab({ taskId }: { taskId: string }) {
};
}, [taskId]);
function copy() {
async function copy() {
if (!report) return;
navigator.clipboard?.writeText(report);
setCopied(true);
toast.success("已复制 Markdown");
setTimeout(() => setCopied(false), 1500);
const ok = await copyText(report);
if (ok) {
setCopied(true);
toast.success("已复制 Markdown");
setTimeout(() => setCopied(false), 1500);
} else {
toast.error("复制失败,请手动选择文本复制");
}
}
let content: React.ReactNode;