Skip to content

Commit f38673c

Browse files
committed
feat(scripts): 优化执行结果和 webhook 模态框的复制功能
将复制逻辑统一提取到工具函数 copyToClipboard,提升代码复用性。 在 ExecutionResultModal 和 WebhookModal 中替换原有的复制实现, 使用新的 handleCopy 方法处理复制成功与失败的提示信息。
1 parent b19951b commit f38673c

4 files changed

Lines changed: 72 additions & 34 deletions

File tree

web/src/pages/Scripts/components/ExecutionResultModal.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CheckCircleOutlined, CloseCircleOutlined, CopyOutlined } from '@ant-des
44
import { useIntl } from '@umijs/max';
55
import { ExecutionResult } from '@/services/scripts';
66
import OutputDisplay from '@/components/OutputDisplay';
7+
import { copyToClipboard } from '@/utils/clipboard';
78
import './ExecutionResultModal.less';
89

910
interface ExecutionResultModalProps {
@@ -23,12 +24,13 @@ const ExecutionResultModal: React.FC<ExecutionResultModalProps> = ({
2324

2425
if (!result) return null;
2526

26-
const copyToClipboard = (text: string) => {
27-
navigator.clipboard.writeText(text).then(() => {
27+
const handleCopy = async (text: string) => {
28+
const success = await copyToClipboard(text);
29+
if (success) {
2830
message.success(intl.formatMessage({ id: 'scripts.execution.copy_success' }));
29-
}).catch(() => {
31+
} else {
3032
message.error(intl.formatMessage({ id: 'scripts.execution.copy_failed' }));
31-
});
33+
}
3234
};
3335

3436
const getStatusIcon = () => {
@@ -90,7 +92,7 @@ const ExecutionResultModal: React.FC<ExecutionResultModalProps> = ({
9092
type="text"
9193
size="small"
9294
icon={<CopyOutlined />}
93-
onClick={() => copyToClipboard(result.output)}
95+
onClick={() => handleCopy(result.output)}
9496
className="copyButton"
9597
>
9698
{intl.formatMessage({ id: 'scripts.execution.copy' })}
@@ -120,7 +122,7 @@ const ExecutionResultModal: React.FC<ExecutionResultModalProps> = ({
120122
type="text"
121123
size="small"
122124
icon={<CopyOutlined />}
123-
onClick={() => copyToClipboard(result.error)}
125+
onClick={() => handleCopy(result.error)}
124126
className="copyButton"
125127
>
126128
{intl.formatMessage({ id: 'scripts.execution.copy' })}

web/src/pages/Scripts/components/WebhookModal.tsx

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Modal, Input, Button, Space, message, Typography, Divider, Alert, theme
33
import { CopyOutlined, LinkOutlined, ReloadOutlined } from '@ant-design/icons';
44
import { useIntl } from '@umijs/max';
55
import { getWebhookURL } from '@/services/scripts';
6+
import { copyToClipboard } from '@/utils/clipboard';
67

78
const { Text, Paragraph } = Typography;
89

@@ -47,32 +48,11 @@ const WebhookModal: React.FC<WebhookModalProps> = ({
4748
}
4849
};
4950

50-
// Copy to clipboard
51-
const copyToClipboard = async (text: string, label: string) => {
52-
try {
53-
// 优先使用 Clipboard API
54-
if (navigator.clipboard && navigator.clipboard.writeText) {
55-
await navigator.clipboard.writeText(text);
56-
message.success(intl.formatMessage({ id: 'scripts.webhook.copy_success' }, { label }));
57-
} else {
58-
// 降级方案:使用传统的 execCommand
59-
const textarea = document.createElement('textarea');
60-
textarea.value = text;
61-
textarea.style.position = 'fixed';
62-
textarea.style.opacity = '0';
63-
document.body.appendChild(textarea);
64-
textarea.select();
65-
const successful = document.execCommand('copy');
66-
document.body.removeChild(textarea);
67-
68-
if (successful) {
69-
message.success(intl.formatMessage({ id: 'scripts.webhook.copy_success' }, { label }));
70-
} else {
71-
throw new Error('execCommand failed');
72-
}
73-
}
74-
} catch (error) {
75-
console.error(intl.formatMessage({ id: 'scripts.webhook.copy_failed' }), error);
51+
const handleCopy = async (text: string, label: string) => {
52+
const success = await copyToClipboard(text);
53+
if (success) {
54+
message.success(intl.formatMessage({ id: 'scripts.webhook.copy_success' }, { label }));
55+
} else {
7656
message.error(intl.formatMessage({ id: 'scripts.webhook.copy_failed' }));
7757
}
7858
};
@@ -139,7 +119,7 @@ const WebhookModal: React.FC<WebhookModalProps> = ({
139119
/>
140120
<Button
141121
icon={<CopyOutlined />}
142-
onClick={() => copyToClipboard(webhookInfo.webhook_url, intl.formatMessage({ id: 'scripts.webhook.copy_url' }))}
122+
onClick={() => handleCopy(webhookInfo.webhook_url, intl.formatMessage({ id: 'scripts.webhook.copy_url' }))}
143123
/>
144124
</Input.Group>
145125
</div>
@@ -156,7 +136,7 @@ const WebhookModal: React.FC<WebhookModalProps> = ({
156136
/>
157137
<Button
158138
icon={<CopyOutlined />}
159-
onClick={() => copyToClipboard(webhookInfo.signature, intl.formatMessage({ id: 'scripts.webhook.copy_signature' }))}
139+
onClick={() => handleCopy(webhookInfo.signature, intl.formatMessage({ id: 'scripts.webhook.copy_signature' }))}
160140
/>
161141
</Input.Group>
162142
</div>

web/src/utils/clipboard.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* 统一的复制到剪贴板工具函数
3+
* 支持现代浏览器的 Clipboard API 和传统浏览器的降级方案
4+
*/
5+
export const copyToClipboard = async (text: string): Promise<boolean> => {
6+
try {
7+
// 优先使用 Clipboard API
8+
if (navigator.clipboard && navigator.clipboard.writeText) {
9+
await navigator.clipboard.writeText(text);
10+
return true;
11+
}
12+
13+
// 降级方案:使用传统的 execCommand
14+
const textarea = document.createElement('textarea');
15+
textarea.value = text;
16+
textarea.style.position = 'fixed';
17+
textarea.style.top = '0';
18+
textarea.style.left = '0';
19+
textarea.style.opacity = '0';
20+
textarea.style.pointerEvents = 'none';
21+
textarea.setAttribute('readonly', '');
22+
23+
document.body.appendChild(textarea);
24+
25+
// 选择文本
26+
if (navigator.userAgent.match(/ipad|iphone/i)) {
27+
// iOS 设备特殊处理
28+
const range = document.createRange();
29+
range.selectNodeContents(textarea);
30+
const selection = window.getSelection();
31+
if (selection) {
32+
selection.removeAllRanges();
33+
selection.addRange(range);
34+
}
35+
textarea.setSelectionRange(0, textarea.value.length);
36+
} else {
37+
textarea.select();
38+
}
39+
40+
const successful = document.execCommand('copy');
41+
document.body.removeChild(textarea);
42+
43+
if (!successful) {
44+
throw new Error('execCommand copy failed');
45+
}
46+
47+
return true;
48+
} catch (error) {
49+
console.error('Copy to clipboard failed:', error);
50+
return false;
51+
}
52+
};

web/src/utils/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { copyToClipboard } from './clipboard';
2+
export * from './auth';
3+
export * from './dateFormat';
4+
export * from './format';

0 commit comments

Comments
 (0)