From e19dff6f79996490393ed84eccb06ec3a69e6760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Thu, 18 Dec 2025 22:54:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(extension):=20=E6=B7=BB=E5=8A=A0=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=88=B3=E8=BD=AC=E6=8D=A2=E5=92=8C=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增时间戳与日期互相转换功能 - 实现带反馈的文本复制组件 - 添加时间戳实时更新与控制按钮 - 配置ESLint规则支持React和Web Extensions - 注册background script处理复制权限 - 更新manifest.json配置文件 - 优化CSS样式支持新功能界面 - 移除用户列表页面相关路由和导入 - 添加输入框和选择器样式 - 实现日期格式化工具函数 --- .eslintrc | 29 ++++++++ public/background.js | 8 +++ public/manifest.json | 35 ++++++---- src/App.css | 105 ++++++++++++++++++---------- src/App.js | 13 ++-- src/components/CopyButton.js | 79 +++++++++++++++++++++ src/pages/TimestampPage.js | 132 +++++++++++++++++++++++------------ 7 files changed, 295 insertions(+), 106 deletions(-) create mode 100644 .eslintrc create mode 100644 public/background.js create mode 100644 src/components/CopyButton.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..cf814b4 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,29 @@ +{ + "env": { + "browser": true, + "es2021": true, + "webextensions": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "react" + ], + "rules": { + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "no-undef": "error" + }, + "globals": { + "chrome": "readonly" + } +} \ No newline at end of file diff --git a/public/background.js b/public/background.js new file mode 100644 index 0000000..5d9fa12 --- /dev/null +++ b/public/background.js @@ -0,0 +1,8 @@ +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === 'start') { + navigator.clipboard.writeText(request.text) + .then(() => sendResponse({success: true})) + .catch((err) => console.log(err)); + return true; + } +}); \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json index 11a0424..36068e2 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,19 +1,24 @@ -{ - "manifest_version": 3, - "name": "React Router Chrome Extension", - "version": "1.0", - "description": "A Chrome Extension with React Router: User List + Actions Pages", - "permissions": ["storage"], - "icons": { - "16": "favicon.ico", +{ + "manifest_version": 3, + "name": "React Router Chrome Extension", + "version": "1.0", + "description": "A Chrome Extension with React Router: User List + Actions Pages", + "permissions": [ + "storage" + ], + "background": { + "service_worker": "background.js" + }, + "icons": { + "16": "favicon.ico", "48": "favicon.ico", "128": "favicon.ico" - }, - "action": { - "default_popup": "index.html" }, - "options_ui": { - "page": "index.html", - "open_in_tab": true - } + "action": { + "default_popup": "index.html" + }, + "options_ui": { + "page": "index.html", + "open_in_tab": true + } } \ No newline at end of file diff --git a/src/App.css b/src/App.css index 83d7b28..1d9e23d 100644 --- a/src/App.css +++ b/src/App.css @@ -1,65 +1,98 @@ .app { - max-width: 800px; - margin: 0 auto; - padding: 20px; - font-family: Arial, sans-serif; - width: 300px; - height: 100vh; - box-shadow: #0b7dda 0 0 0 10px; + max-width: 800px; + margin: 0 auto; + padding: 20px; + font-family: Arial, sans-serif; + width: 300px; + height: 100vh; } .navbar { - background: #2196f3; - color: white; - padding: 15px; - border-radius: 4px; - margin-bottom: 20px; + background: #2196f3; + color: white; + padding: 15px; + border-radius: 4px; + margin-bottom: 20px; } .page { - padding: 20px; - border: 1px solid #ddd; - border-radius: 4px; + padding: 20px; + border: 1px solid #ddd; + border-radius: 4px; } .user-list { - list-style: none; - padding: 0; + list-style: none; + padding: 0; } .user-list li { - padding: 8px; - border-bottom: 1px solid #eee; + padding: 8px; + border-bottom: 1px solid #eee; } .btn { - display: inline-block; - margin-top: 20px; - padding: 10px 20px; - background: #2196f3; - color: white; - text-decoration: none; - border-radius: 4px; + display: inline-block; + margin-top: 20px; + padding: 10px 20px; + background: #2196f3; + color: white; + text-decoration: none; + border-radius: 4px; } .btn:hover { - background: #0b7dda; + background: #0b7dda; } .actions { - margin: 20px 0; + margin: 20px 0; } .action-btn { - margin-right: 10px; - padding: 8px 16px; - background: #4caf50; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; + margin-right: 10px; + padding: 8px 16px; + background: #4caf50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; } .action-btn:hover { - background: #3d8b40; + background: #3d8b40; +} + +.stop-btn { + margin-right: 10px; + padding: 8px 16px; + background: #f32152; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.stop-btn:hover { + background: #ff0000; +} + +input { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + margin-bottom: 20px; + box-sizing: border-box; + font-size: 14px; +} + +select { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + margin-bottom: 20px; + box-sizing: border-box; + font-size: 14px; } diff --git a/src/App.js b/src/App.js index 2708067..ade4a5e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,4 @@ -import { HashRouter as Router, Routes, Route } from 'react-router-dom'; -import UserListPage from './pages/UserListPage'; +import {HashRouter as Router, Routes, Route} from 'react-router-dom'; import ActionsPage from './pages/ActionsPage'; import TimestampPage from './pages/TimestampPage'; import './App.css'; @@ -8,14 +7,10 @@ function App() { return (
- - - } /> - } /> - } /> + }/> + }/> + }/>
diff --git a/src/components/CopyButton.js b/src/components/CopyButton.js new file mode 100644 index 0000000..176e219 --- /dev/null +++ b/src/components/CopyButton.js @@ -0,0 +1,79 @@ +import {useState, useCallback} from "react"; + +const CopyButton = ({ + text = '要复制的文本', + buttonText = '复制文本', + className = 'action-btn', + }) => { + const [btnText, setBtnText] = useState(buttonText); + const [copyStatus, setCopyStatus] = useState(''); + + // 重置按钮状态的函数 + const resetButton = useCallback(() => { + setBtnText(buttonText); + setCopyStatus(''); + }, [buttonText]); + + // 复制成功的处理 + const handleCopySuccess = useCallback(() => { + setCopyStatus('success'); + + // 2秒后恢复 + setTimeout(() => { + resetButton(); + }, 2000); + }, [text, resetButton]); + + // 复制失败的处理 + const handleCopyError = useCallback(() => { + setCopyStatus('error'); + + // 2秒后恢复 + setTimeout(() => { + resetButton(); + }, 2000); + }, [resetButton]); + + const handleCopy = async () => { + try { + if (chrome && chrome.runtime && chrome.runtime.sendMessage) { + chrome.runtime.sendMessage({ + action: 'copy', + text: text + }, (response) => { + if (response && response.success) { + handleCopySuccess(); + } else { + handleCopyError(); + } + }); + } else { + await navigator.clipboard.writeText(text.toString()); + handleCopySuccess(); + } + } catch (err) { + handleCopyError(); + } + }; + + return ( + + ); +}; + +export default CopyButton; \ No newline at end of file diff --git a/src/pages/TimestampPage.js b/src/pages/TimestampPage.js index 156b718..e46ef4c 100644 --- a/src/pages/TimestampPage.js +++ b/src/pages/TimestampPage.js @@ -1,44 +1,76 @@ -import {useState, useEffect, useRef} from 'react'; -import {Link} from 'react-router-dom'; +import {useState, useEffect, useRef, useCallback, useMemo} from 'react'; +import CopyButton from "../components/CopyButton"; + +function formatDate(value) { + return (new Intl.DateTimeFormat('zh-CN', { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + })).format(value); +} const TimestampPage = () => { const [currentTimestamp, setCurrentTimestamp] = useState(Math.floor(Date.now())); const [showMilliseconds, setShowMilliseconds] = useState(true); const [isRunningTimestamp, setIsRunningTimestamp] = useState(true); const intervalRef = useRef(null); + const [timestampValue, setTimestampValue] = useState(Date.now()); + const [dateValue, setDateValue] = useState(formatDate(Date.now())); + const [timestampResult, setTimestampResult] = useState(''); + const [dateResult, setDateResult] = useState(''); + useEffect(() => { if (isRunningTimestamp) { intervalRef.current = setInterval(() => { setCurrentTimestamp(Math.floor(Date.now())); - }, 100); + }, 1000); } - + return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } } }, [isRunningTimestamp]); - + function handleChangeUnit() { setShowMilliseconds(!showMilliseconds); } - - function handleChangeTimestampRunning(status) { - setIsRunningTimestamp(status); - if (!status) { - console.log('停止时间戳') - } else { - console.log('开始时间戳') - } + + const handleToggleTimestampRunning = useCallback(() => { + const newStatus = !isRunningTimestamp; + setIsRunningTimestamp(newStatus); + console.log(newStatus ? '开始时间戳' : '停止时间戳'); + }, [isRunningTimestamp]); + useMemo(() => ({ + backgroundColor: isRunningTimestamp ? 'red' : 'green' + }), [isRunningTimestamp]); + + function handleConvertTimestampToDate() { + setTimestampResult(formatDate(new Date(Number(timestampValue)))); } - + + function handleConvertDateToTimestamp() { + if (!dateValue) { + setDateResult('请输入日期222'); + return; + } + + const date = new Date(dateValue); + + if (isNaN(date.getTime())) { + setDateResult('请输入日期333'); + return; + } + + setDateResult(date.getTime()); + } + return ( -
- - Back to User List - -

时间戳工具

+

当前时间戳

@@ -47,63 +79,71 @@ const TimestampPage = () => {

- - - + +
- +

时间戳转日期时间

-
-
- - setTimestampValue(e.target.value)}/> +
- -
-
- -
+ +
- +
- +

日期时间转时间戳

-
-
- - +
+
+ setDateValue(e.target.value)}/> +
- -
-
- -
+ +
-