feat:添加测试页面并优化时间戳工具
refactor: 优化时间戳转换组件及整体代码结构 chore: 更新构建配置和包依赖项
This commit is contained in:
@@ -61,10 +61,7 @@ export default defineBackground(() => {
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (error.message.includes('restricted URL')) {
|
||||
console.warn(
|
||||
'Could not check status: on a restricted page.',
|
||||
error.message,
|
||||
);
|
||||
console.warn('Could not check status: on a restricted page.', error.message);
|
||||
} else if (error.message.includes('No active tab found')) {
|
||||
console.warn('Could not check status: no active tab found.');
|
||||
} else {
|
||||
@@ -84,10 +81,7 @@ export default defineBackground(() => {
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (error.message.includes('restricted URL')) {
|
||||
console.warn(
|
||||
'Could not start recording: on a restricted page.',
|
||||
error.message,
|
||||
);
|
||||
console.warn('Could not start recording: on a restricted page.', error.message);
|
||||
} else if (error.message.includes('No active tab found')) {
|
||||
console.warn('Could not start recording: no active tab found.');
|
||||
} else {
|
||||
@@ -107,10 +101,7 @@ export default defineBackground(() => {
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (error.message.includes('restricted URL')) {
|
||||
console.warn(
|
||||
'Could not stop recording: on a restricted page.',
|
||||
error.message,
|
||||
);
|
||||
console.warn('Could not stop recording: on a restricted page.', error.message);
|
||||
} else if (error.message.includes('No active tab found')) {
|
||||
console.warn('Could not stop recording: no active tab found.');
|
||||
} else {
|
||||
@@ -132,6 +123,10 @@ export default defineBackground(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onMessage('getStringLength', (message) => {
|
||||
return message.data.length;
|
||||
});
|
||||
|
||||
async function sendToActiveTab(message: {
|
||||
type: string;
|
||||
data?: unknown;
|
||||
|
||||
@@ -15,6 +15,10 @@ export default defineContentScript({
|
||||
const recorder = createRecorder();
|
||||
let isRecording = false;
|
||||
|
||||
onMessage('getStringLength', (message) => {
|
||||
console.log(`[content] getStringLength received: ${message.data}`);
|
||||
});
|
||||
|
||||
// 监听来自 background script 的消息
|
||||
chrome.runtime.onMessage.addListener(
|
||||
(msg: { type: string }, _sender, sendResponse: (response: IResponse) => void) => {
|
||||
|
||||
+11
-17
@@ -1,38 +1,32 @@
|
||||
// App.js
|
||||
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
import TimestampPage from './pages/TimestampPage';
|
||||
import RecordeReplayPage from './pages/RecordeReplayPage';
|
||||
import TestPage from './pages/TestPage';
|
||||
import Navbar from './components/Navbar';
|
||||
import RoutePersistence from './components/RoutePersistence';
|
||||
import './App.css';
|
||||
|
||||
// 路由配置数据
|
||||
const navItems = [
|
||||
{ path: '/test', label: '测试页面', element: <TestPage /> },
|
||||
{ path: '/', label: '时间戳', element: <TimestampPage /> },
|
||||
// {path: '/dzmy', label: '电子木鱼', element: <ElectronicWoodenFishPage/>},
|
||||
{ path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> },
|
||||
];
|
||||
|
||||
const theme = createTheme();
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||
<RoutePersistence />
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SnackbarProvider maxSnack={3} anchorOrigin={{ vertical: 'top', horizontal: 'center' }}>
|
||||
<Navbar items={navItems} />
|
||||
<Routes>
|
||||
{navItems.map((item) => (
|
||||
<Route key={item.path} path={item.path} element={item.element} />
|
||||
))}
|
||||
</Routes>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
<div className="app">
|
||||
<Navbar items={navItems} />
|
||||
|
||||
<Routes>
|
||||
{navItems.map((item) => (
|
||||
<Route key={item.path} path={item.path} element={item.element} />
|
||||
))}
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { useState, useCallback, FC, ReactNode, useEffect } from 'react';
|
||||
import Button, { ButtonProps } from '@mui/material/Button';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import Alert, { AlertColor } from '@mui/material/Alert';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
|
||||
type CopyStatus = 'idle' | 'copying' | 'success' | 'error';
|
||||
|
||||
interface CopyButtonProps extends Omit<ButtonProps, 'onClick' | 'variant'> {
|
||||
textToCopy: string | number;
|
||||
buttonText?: ReactNode;
|
||||
successMessage?: string;
|
||||
errorMessage?: string;
|
||||
variant?: ButtonProps['variant'];
|
||||
}
|
||||
|
||||
const CopyButton: FC<CopyButtonProps> = ({
|
||||
textToCopy,
|
||||
buttonText = '复制',
|
||||
successMessage = '复制成功!',
|
||||
errorMessage = '复制失败,请手动复制。',
|
||||
variant = 'contained',
|
||||
...buttonProps
|
||||
}) => {
|
||||
const [status, setStatus] = useState<CopyStatus>('idle');
|
||||
const [openSnackbar, setOpenSnackbar] = useState(false);
|
||||
const [snackbarContent, setSnackbarContent] = useState<{
|
||||
message: string;
|
||||
severity: AlertColor;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'success' || status === 'error') {
|
||||
const timer = setTimeout(() => setStatus('idle'), 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
return undefined;
|
||||
}, [status]);
|
||||
|
||||
const performCopy = useCallback(async () => {
|
||||
if (!textToCopy) {
|
||||
console.warn('没有提供要复制的文本');
|
||||
return false;
|
||||
}
|
||||
const safeText = String(textToCopy);
|
||||
try {
|
||||
await navigator.clipboard.writeText(safeText);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('使用 Clipboard API 复制失败:', err);
|
||||
return false;
|
||||
}
|
||||
}, [textToCopy]);
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
if (status !== 'idle') return;
|
||||
|
||||
setStatus('copying');
|
||||
let isSuccess = false;
|
||||
try {
|
||||
[isSuccess] = await Promise.all([
|
||||
performCopy(),
|
||||
new Promise((resolve) => setTimeout(resolve, 300)),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('复制时出错:', error);
|
||||
isSuccess = false;
|
||||
} finally {
|
||||
const newStatus = isSuccess ? 'success' : 'error';
|
||||
setStatus(newStatus);
|
||||
setSnackbarContent({
|
||||
message: isSuccess ? successMessage : errorMessage,
|
||||
severity: newStatus,
|
||||
});
|
||||
setOpenSnackbar(true);
|
||||
}
|
||||
}, [performCopy, status, successMessage, errorMessage]);
|
||||
|
||||
const handleCloseSnackbar = (_event?: Event | React.SyntheticEvent, reason?: string) => {
|
||||
if (reason === 'clickaway') return;
|
||||
setOpenSnackbar(false);
|
||||
};
|
||||
|
||||
const renderButtonIcon = () => {
|
||||
if (status === 'success') {
|
||||
return <CheckIcon sx={{ mr: 1 }} fontSize="small" />;
|
||||
}
|
||||
return <ContentCopyIcon sx={{ mr: 1 }} fontSize="small" />;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
disabled={status !== 'idle'}
|
||||
variant={variant}
|
||||
{...buttonProps}
|
||||
sx={{
|
||||
...buttonProps.sx,
|
||||
transition: 'background-color 0.3s',
|
||||
...(status === 'success' && {
|
||||
bgcolor: 'success.main',
|
||||
'&:hover': {
|
||||
bgcolor: 'success.dark',
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{renderButtonIcon()}
|
||||
{buttonText}
|
||||
</Button>
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
autoHideDuration={2000}
|
||||
onClose={handleCloseSnackbar}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
{snackbarContent ? (
|
||||
<Alert
|
||||
onClose={handleCloseSnackbar}
|
||||
severity={snackbarContent.severity}
|
||||
variant="filled"
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{snackbarContent.message}
|
||||
</Alert>
|
||||
) : undefined}
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyButton;
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Paper,
|
||||
Typography,
|
||||
Stack,
|
||||
Box,
|
||||
SelectChangeEvent,
|
||||
@@ -87,9 +86,6 @@ export function DatetimeToTimestamp() {
|
||||
|
||||
return (
|
||||
<Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2 }}>
|
||||
<Typography variant="h6" component="h2" align="center" gutterBottom>
|
||||
日期时间转时间戳
|
||||
</Typography>
|
||||
<Stack spacing={2} sx={{ mt: 2 }}>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<TextField
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import CopyButton from './CopyButton';
|
||||
import { Button, Paper, Typography, Stack, Box } from '@mui/material';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import { useCopy } from './useCopy';
|
||||
|
||||
/**
|
||||
* 时间戳显示和执行组件
|
||||
@@ -16,7 +14,9 @@ import { useCopy } from './useCopy';
|
||||
* @returns {JSX.Element} 时间戳组件
|
||||
*/
|
||||
export function TimestampExecution() {
|
||||
const [currentTimestamp, setCurrentTimestamp] = useState(() => Math.floor(Date.now()));
|
||||
const [currentTimestamp, setCurrentTimestamp] = useState(() =>
|
||||
Math.floor(Date.now()),
|
||||
);
|
||||
const [showMilliseconds, setShowMilliseconds] = useState(true);
|
||||
const [isRunningTimestamp, setIsRunningTimestamp] = useState(true);
|
||||
|
||||
@@ -45,14 +45,19 @@ export function TimestampExecution() {
|
||||
setIsRunningTimestamp((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const unitButtonLabel = showMilliseconds ? '切换为秒显示' : '切换为毫秒显示';
|
||||
const toggleButtonLabel = isRunningTimestamp ? '停止时间戳自动更新' : '开始时间戳自动更新';
|
||||
const unitButtonLabel = showMilliseconds
|
||||
? '切换为秒显示'
|
||||
: '切换为毫秒显示';
|
||||
const toggleButtonLabel = isRunningTimestamp
|
||||
? '停止时间戳自动更新'
|
||||
: '开始时间戳自动更新';
|
||||
const toggleButtonText = isRunningTimestamp ? '停止' : '开始';
|
||||
|
||||
const { isCopied, copy } = useCopy();
|
||||
|
||||
return (
|
||||
<Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2, minWidth: 320, textAlign: 'center' }}>
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{ p: 2, my: 2, borderRadius: 2, minWidth: 320, textAlign: 'center' }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -96,21 +101,12 @@ export function TimestampExecution() {
|
||||
切换单位
|
||||
</Button>
|
||||
|
||||
{/* <CopyButton
|
||||
<CopyButton
|
||||
textToCopy={String(currentTimestamp)}
|
||||
buttonText="复制"
|
||||
aria-label="复制当前时间戳到剪贴板"
|
||||
color="primary"
|
||||
/> */}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => copy(String(currentTimestamp))}
|
||||
startIcon={isCopied ? <CheckIcon /> : <ContentCopyIcon />}
|
||||
color={isCopied ? 'success' : 'primary'}
|
||||
>
|
||||
{isCopied ? '已复制' : '复制'}
|
||||
</Button>
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Paper,
|
||||
Typography,
|
||||
Stack,
|
||||
Box,
|
||||
SelectChangeEvent,
|
||||
@@ -110,9 +109,6 @@ export function TimestampToDatetime() {
|
||||
|
||||
return (
|
||||
<Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2 }}>
|
||||
<Typography variant="h6" component="h2" align="center" gutterBottom>
|
||||
时间戳转日期时间
|
||||
</Typography>
|
||||
<Stack spacing={2} sx={{ mt: 2 }}>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<TextField
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useSnackbar } from 'notistack'; // 1. 引入 hook
|
||||
|
||||
export const useCopy = (resetInterval = 2000) => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const { enqueueSnackbar } = useSnackbar(); // 2. 获取触发函数
|
||||
|
||||
const copy = useCallback(
|
||||
async (text: string | number) => {
|
||||
if (!text) {
|
||||
enqueueSnackbar('没有可复制的内容', { variant: 'warning' });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(String(text));
|
||||
setIsCopied(true);
|
||||
|
||||
// 3. 触发成功通知
|
||||
enqueueSnackbar('复制成功!', { variant: 'success' });
|
||||
|
||||
// 自动重置状态
|
||||
setTimeout(() => setIsCopied(false), resetInterval);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Copy failed:', error);
|
||||
|
||||
// 4. 触发错误通知
|
||||
enqueueSnackbar('复制失败,请手动复制', { variant: 'error' });
|
||||
|
||||
setIsCopied(false);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[enqueueSnackbar, resetInterval],
|
||||
);
|
||||
|
||||
return { isCopied, copy };
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useSnackbar } from 'notistack'; // 1. 引入 hook
|
||||
|
||||
export const useCopy = (resetInterval = 2000) => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const copy = useCallback(
|
||||
async (text: string | number) => {
|
||||
if (!text) {
|
||||
enqueueSnackbar('没有可复制的内容', { variant: 'warning' });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(String(text));
|
||||
setIsCopied(true);
|
||||
|
||||
// 3. 触发成功通知
|
||||
enqueueSnackbar('复制成功!', { variant: 'success' });
|
||||
|
||||
// 自动重置状态
|
||||
setTimeout(() => setIsCopied(false), resetInterval);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Copy failed:', error);
|
||||
|
||||
// 4. 触发错误通知
|
||||
enqueueSnackbar('复制失败,请手动复制', { variant: 'error' });
|
||||
|
||||
setIsCopied(false);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[enqueueSnackbar, resetInterval],
|
||||
);
|
||||
|
||||
return { isCopied, copy };
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { sendMessage } from '@/utils/messages';
|
||||
import { Button } from '@mui/material';
|
||||
|
||||
const TestPage = () => {
|
||||
const handleSeedMessage = async () => {
|
||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
tabs.forEach(async (tab) => {
|
||||
console.log(tab.id);
|
||||
const length = await sendMessage('getStringLength', 'hello world', tab.id);
|
||||
console.log('字符串长度:', length);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Button variant="contained" color="primary" onClick={handleSeedMessage}>
|
||||
发送消息
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestPage;
|
||||
Reference in New Issue
Block a user