feat:
1. 重构CopyButton 2. 修改Router特性 3. 修改CopyButton传参 4. 添加options页面
This commit is contained in:
+81
-88
@@ -1,110 +1,103 @@
|
|||||||
import {useState, useCallback} from "react";
|
import { useState, useCallback } from 'react';
|
||||||
|
import { copyToClipboard } from '@/utils/chromeUtils';
|
||||||
|
|
||||||
const CopyButton = ({
|
const CopyButton = ({
|
||||||
text = '要复制的文本',
|
text = '要复制的文本',
|
||||||
buttonText = '复制文本',
|
buttonText = '复制文本',
|
||||||
className = 'action-btn',
|
className = 'action-btn',
|
||||||
}) => {
|
successMessage = '复制成功!',
|
||||||
const [btnText, setBtnText] = useState(buttonText);
|
errorMessage = '复制失败,请手动复制。',
|
||||||
const [copyStatus, setCopyStatus] = useState('');
|
onCopyOverride = null,
|
||||||
|
}) => {
|
||||||
// 重置按钮状态的函数
|
const [status, setStatus] = useState('idle'); // 'idle' | 'copying' | 'success' | 'error'
|
||||||
const resetButton = useCallback(() => {
|
|
||||||
setBtnText(buttonText);
|
useEffect(() => {
|
||||||
setCopyStatus('');
|
let timer;
|
||||||
}, [buttonText]);
|
if (status === 'success' || status === 'error') {
|
||||||
|
timer = setTimeout(() => {
|
||||||
// 复制成功的处理
|
setStatus('idle');
|
||||||
const handleCopySuccess = useCallback(() => {
|
}, 2000);
|
||||||
setCopyStatus('success');
|
}
|
||||||
setBtnText('复制成功!');
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
const performCopy = async () => {
|
||||||
|
if (!text) {
|
||||||
|
console.warn('没有提供要复制的文本');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeText = String(text);
|
||||||
|
|
||||||
|
// 优先使用传入的复制函数
|
||||||
|
if (onCopyOverride) {
|
||||||
|
return await onCopyOverride(safeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = safeText;
|
||||||
|
|
||||||
// 2秒后恢复
|
// 确保元素存在但不可见,且不影响布局
|
||||||
setTimeout(() => {
|
textArea.style.position = "fixed";
|
||||||
resetButton();
|
textArea.style.left = "-9999px";
|
||||||
}, 2000);
|
textArea.style.top = "0";
|
||||||
}, [text, resetButton]);
|
textArea.style.opacity = "0";
|
||||||
|
|
||||||
// 复制失败的处理
|
|
||||||
const handleCopyError = useCallback(() => {
|
|
||||||
setCopyStatus('error');
|
|
||||||
|
|
||||||
// 2秒后恢复
|
document.body.appendChild(textArea);
|
||||||
setTimeout(() => {
|
textArea.focus();
|
||||||
resetButton();
|
textArea.select();
|
||||||
}, 2000);
|
|
||||||
}, [resetButton]);
|
const successful = document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
const handleCopy = async () => {
|
|
||||||
|
// 使用通用的复制函数
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = useCallback(async () => {
|
||||||
|
setStatus('copying');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('开始复制:', text);
|
const isSuccess = await performCopy();
|
||||||
|
setStatus(isSuccess ? 'success' : 'error');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制时出错:', error);
|
||||||
|
setStatus('error');
|
||||||
|
}
|
||||||
|
}, [text, onCopyOverride]);
|
||||||
|
|
||||||
// 首先尝试直接使用navigator.clipboard(在popup页面中可能可用)
|
// 根据状态计算当前显示的文本
|
||||||
try {
|
const currentText = status === 'success' ? successMessage
|
||||||
console.log('尝试直接使用navigator.clipboard复制');
|
: status === 'error' ? errorMessage
|
||||||
await navigator.clipboard.writeText(text.toString());
|
: buttonText;
|
||||||
console.log('直接复制成功');
|
|
||||||
handleCopySuccess();
|
|
||||||
return;
|
|
||||||
} catch (directError) {
|
|
||||||
console.log('直接复制失败,尝试使用Chrome扩展API:', directError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果直接复制失败,尝试使用Chrome扩展API
|
// 根据状态计算样式 (建议使用 CSS Module 或 Tailwind,这里为了演示保留内联)
|
||||||
if (chrome && chrome.runtime && chrome.runtime.sendMessage) {
|
const getBackgroundColor = () => {
|
||||||
console.log('使用Chrome扩展API复制');
|
switch (status) {
|
||||||
|
case 'success': return '#4CAF50';
|
||||||
// 使用Chrome扩展API复制
|
case 'error': return '#f44336';
|
||||||
chrome.runtime.sendMessage({
|
default: return '#4CAF50'; // 让 CSS 类控制默认颜色
|
||||||
action: 'copy',
|
|
||||||
text: text
|
|
||||||
}, (response) => {
|
|
||||||
console.log('收到background响应:', response, 'lastError:', chrome.runtime.lastError);
|
|
||||||
|
|
||||||
// 检查是否有运行时错误
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
console.error('Chrome运行时错误:', chrome.runtime.lastError.message);
|
|
||||||
handleCopyError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查响应
|
|
||||||
if (response && response.success) {
|
|
||||||
console.log('通过background复制成功');
|
|
||||||
handleCopySuccess();
|
|
||||||
} else {
|
|
||||||
console.error('通过background复制失败:', response?.error);
|
|
||||||
handleCopyError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error('没有可用的复制方法');
|
|
||||||
handleCopyError();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('复制过程中发生错误:', err);
|
|
||||||
handleCopyError();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
onClick={handleClick}
|
||||||
className={className}
|
className={`${className} ${status} copy-button`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: copyStatus === 'success' ? '#4CAF50' :
|
backgroundColor: getBackgroundColor(),
|
||||||
copyStatus === 'error' ? '#f44336' : '',
|
color: status !== 'idle' ? 'white' : undefined,
|
||||||
color: copyStatus ? 'white' : '',
|
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
padding: '8px 16px',
|
padding: '8px 16px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
cursor: 'pointer'
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{btnText}
|
{currentText}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CopyButton;
|
export default CopyButton;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export function TimestampExecution() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<CopyButton
|
<CopyButton
|
||||||
text={currentTimestamp.toString()}
|
text={String(currentTimestamp)}
|
||||||
buttonText="复制时间戳"
|
buttonText="复制时间戳"
|
||||||
aria-label="复制当前时间戳到剪贴板"
|
aria-label="复制当前时间戳到剪贴板"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Hello Options Page
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -43,7 +43,7 @@ function App() {
|
|||||||
const collapsedNavItems = navItems.slice(visibleItems);
|
const collapsedNavItems = navItems.slice(visibleItems);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router future={{v7_startTransition: true, v7_relativeSplatPath: true}}>
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<nav className="nav">
|
<nav className="nav">
|
||||||
<div className="nav-content">
|
<div className="nav-content">
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 使用Chrome扩展API复制文本到剪贴板
|
||||||
|
* @param text 复制文本
|
||||||
|
* @returns
|
||||||
|
* - false: 复制失败
|
||||||
|
* - true: 复制成功
|
||||||
|
*/
|
||||||
|
export const copyToClipboard = async (text: string) => {
|
||||||
|
console.log('尝试复制文本:', text);
|
||||||
|
if (chrome && chrome.runtime && chrome.runtime.sendMessage) {
|
||||||
|
console.log('使用Chrome扩展API复制');
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
{
|
||||||
|
action: 'copy',
|
||||||
|
text: text,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
console.log('收到background响应:', response, 'lastError:', chrome.runtime.lastError);
|
||||||
|
|
||||||
|
// 检查是否有运行时错误
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
console.error('Chrome运行时错误:', chrome.runtime.lastError.message);
|
||||||
|
return reject(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查响应
|
||||||
|
if (response && response.success) {
|
||||||
|
console.log('通过background复制成功');
|
||||||
|
return resolve(true);
|
||||||
|
} else {
|
||||||
|
console.error('通过background复制失败:', response?.error);
|
||||||
|
return reject(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(false);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user