feat(extension): 添加时间戳转换和复制功能

- 新增时间戳与日期互相转换功能
- 实现带反馈的文本复制组件
- 添加时间戳实时更新与控制按钮
- 配置ESLint规则支持React和Web Extensions
- 注册background script处理复制权限
- 更新manifest.json配置文件
- 优化CSS样式支持新功能界面
- 移除用户列表页面相关路由和导入
- 添加输入框和选择器样式
- 实现日期格式化工具函数
This commit is contained in:
雨霖铃
2025-12-18 22:54:16 +08:00
parent 519e53851d
commit e19dff6f79
7 changed files with 295 additions and 106 deletions
+69 -36
View File
@@ -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;
}
+4 -9
View File
@@ -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 (
<Router>
<div className="app">
<nav className="navbar">
<h2>User Manager Extesion</h2>
</nav>
<Routes>
<Route path="/" element={<UserListPage />} />
<Route path="/actions" element={<ActionsPage />} />
<Route path="/timestamp" element={<TimestampPage />} />
<Route path="/" element={<TimestampPage/>}/>
<Route path="/actions" element={<ActionsPage/>}/>
<Route path="/timestamp" element={<TimestampPage/>}/>
</Routes>
</div>
</Router>
+79
View File
@@ -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 (
<button
onClick={handleCopy}
className={className}
style={{
backgroundColor: copyStatus === 'success' ? '#4CAF50' :
copyStatus === 'error' ? '#f44336' : '',
color: copyStatus ? 'white' : '',
transition: 'all 0.3s ease',
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
{btnText}
</button>
);
};
export default CopyButton;
+86 -46
View File
@@ -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 (
<div id="page-timestamp" class="page">
<Link to="/" className="btn">
Back to User List
</Link>
<h2>时间戳工具</h2>
<div>
<div id="current-timestamp">
<h2>当前时间戳</h2>
<p>
@@ -47,63 +79,71 @@ const TimestampPage = () => {
</p>
<div>
<button className="action-btn" onClick={handleChangeUnit}>切换单位</button>
<button className="action-btn">复制</button>
<button className="action-btn" onClick={() => handleChangeTimestampRunning(false)}>停止</button>
<button className="action-btn" onClick={() => handleChangeTimestampRunning(true)}>开始</button>
<CopyButton text={currentTimestamp.toString()} buttonText='复制'></CopyButton>
<button className={isRunningTimestamp ? 'stop-btn' : 'action-btn'}
onClick={handleToggleTimestampRunning}>
{isRunningTimestamp ? '停止' : '开始'}
</button>
</div>
</div>
<div>
<h2>时间戳转日期时间</h2>
<div class="datetime-box">
<div class="input-group">
<input type="text" id="timestamp-input" placeholder="请输入时间戳" class="input-text"/>
<select id="timestamp-input-unit-select" class="select-box">
<div className="datetime-box">
<div className="input-group">
<input type="text" id="timestamp-input" placeholder="请输入时间戳" className="input-text"
value={timestampValue} onChange={(e) => setTimestampValue(e.target.value)}/>
<select id="timestamp-input-unit-select" className="select-box">
<option value="milliseconds">毫秒(ms)</option>
<option value="seconds">(s)</option>
</select>
</div>
<div class="input-group">
<button id="convert-timestamp-to-date-btn" className="action-btn">
<div className="input-group">
<button id="convert-timestamp-to-date-btn" className="action-btn" onClick={handleConvertTimestampToDate}>
转换
</button>
</div>
<div class="input-group">
<div className="input-group">
<input
type="text"
id="timestamp-conversion-result"
placeholder="转换结果"
class="input-text"
className="input-text"
value={timestampResult}
readOnly
/>
<select class="select-box" id="timezone-result"></select>
<select className="select-box" id="timezone-result"></select>
</div>
</div>
</div>
<div>
<h2>日期时间转时间戳</h2>
<div class="datetime-box">
<div class="input-group">
<input type="text" id="datetime-input" placeholder="输入日期时间" class="input-text"/>
<select class="select-box" id="timezone-input"></select>
<div className="datetime-box">
<div className="input-group">
<input type="text" id="datetime-input" placeholder="输入日期时间" className="input-text" value={dateValue}
onChange={(e) => setDateValue(e.target.value)}/>
<select className="select-box" id="timezone-input"></select>
</div>
<div class="input-group">
<button id="convert-date-to-timestamp-btn" className="action-btn">
<div className="input-group">
<button id="convert-date-to-timestamp-btn" className="action-btn" onClick={handleConvertDateToTimestamp}>
转换
</button>
</div>
<div class="input-group">
<div className="input-group">
<input
type="text"
id="date-conversion-result"
placeholder="转换结果"
class="input-text"
className="input-text"
value={dateResult}
readOnly
/>
<select id="date-result-unit-select" class="select-box">
<select id="date-result-unit-select" className="select-box">
<option value="milliseconds">毫秒(ms)</option>
<option value="seconds">(s)</option>
</select>