feat: add route persistence and page visibility configuration
- Persist current route to Chrome Storage, restore on popup reopen - Add page visibility configuration to control which pages are shown - Update StorageSchema with new storage keys (app/currentRoute, app/visiblePages) - Add PageType type and PAGE_CONFIG for type-safe page management - Add navigation button styles with active state and hover effects - Use loading state to prevent UI flicker during async data load Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
- `postinstall` 会自动运行 `wxt prepare` 准备开发环境
|
||||
- `prepare` 钩子会初始化 Husky Git 钩子
|
||||
|
||||
### CI/CD
|
||||
|
||||
- GitHub Actions 配置: `.github/workflows/node.js.yml`
|
||||
- 在 main 分支推送或 PR 时触发
|
||||
- 使用 Node.js 22.x 运行 build
|
||||
- 测试命令当前被注释(项目暂无测试)
|
||||
|
||||
## 项目架构
|
||||
|
||||
### 技术栈
|
||||
@@ -142,15 +149,17 @@ host_permissions: ['<all_urls>'] // 访问所有网站
|
||||
|
||||
- 使用 ESLint 进行代码检查(零警告)
|
||||
- Husky 用于 Git 钩子管理
|
||||
- Lint-staged 确保暂存文件符合规范
|
||||
- Lint-staged 确保暂存文件符合规范(ESLint + TypeScript + Prettier)
|
||||
- Prettier 用于代码格式化
|
||||
- Prettier 配置: 100 字符行宽,2 空格缩进,单引号,trailing comma
|
||||
|
||||
### TypeScript 配置
|
||||
|
||||
- 严格模式开启(`strict: true`)
|
||||
- 不允许隐式 any(可配置,当前关闭)
|
||||
- 未使用变量/参数会报错
|
||||
- `noImplicitAny` 设置为 `false`(允许隐式 any)
|
||||
- 未使用变量/参数会报错(`noUnusedLocals`, `noUnusedParameters`)
|
||||
- 模块解析模式:Bundler
|
||||
- 排除测试文件(`**/*.test.tsx`, `**/*.test.ts`)以避免类型检查
|
||||
|
||||
### 项目历史
|
||||
|
||||
|
||||
@@ -48,3 +48,32 @@ body {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 导航容器 */
|
||||
.nav-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 导航按钮 */
|
||||
.nav-button {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--btn-bg);
|
||||
border-radius: var(--radius);
|
||||
background: transparent;
|
||||
color: var(--btn-text);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
background: var(--btn-bg);
|
||||
}
|
||||
|
||||
.nav-button.active {
|
||||
background: var(--btn-bg);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
+70
-17
@@ -1,30 +1,83 @@
|
||||
import { useState } from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import type { PageType } from '@/types/storage';
|
||||
import { storageUtil } from '@/utils/chromeStorage';
|
||||
import TimestampPage from './pages/TimestampPage';
|
||||
import StorageCleanerPage from './pages/StorageCleanerPage';
|
||||
import './App.css';
|
||||
|
||||
type PageType = 'timestamp' | 'storageCleaner';
|
||||
|
||||
const PAGE_CONFIG = {
|
||||
timestamp: { label: '时间戳', defaultVisible: true },
|
||||
storageCleaner: { label: '存储清理', defaultVisible: true },
|
||||
} as const satisfies Record<PageType, { label: string; defaultVisible: boolean }>;
|
||||
|
||||
function App() {
|
||||
const [currentPage, setCurrentPage] = useState<PageType>('timestamp');
|
||||
const [visiblePages, setVisiblePages] = useState<PageType[]>(['timestamp', 'storageCleaner']);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadInitialData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoaded) {
|
||||
storageUtil.set('app/currentRoute', currentPage);
|
||||
}
|
||||
}, [currentPage, isLoaded]);
|
||||
|
||||
const loadInitialData = async () => {
|
||||
try {
|
||||
const [savedRoute, savedVisiblePages] = await Promise.all([
|
||||
storageUtil.get('app/currentRoute', 'timestamp'),
|
||||
storageUtil.get('app/visiblePages', ['timestamp', 'storageCleaner'] as PageType[]),
|
||||
]);
|
||||
|
||||
if (savedRoute) {
|
||||
setCurrentPage(savedRoute);
|
||||
}
|
||||
|
||||
if (savedVisiblePages) {
|
||||
setVisiblePages(savedVisiblePages);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load initial data:', error);
|
||||
} finally {
|
||||
setIsLoaded(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageChange = (page: PageType) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const NavButton = ({ pageKey }: { pageKey: PageType }) => {
|
||||
const config = PAGE_CONFIG[pageKey];
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Box key={pageKey}>
|
||||
<button
|
||||
className={currentPage === pageKey ? 'nav-button active' : 'nav-button'}
|
||||
onClick={() => handlePageChange(pageKey)}
|
||||
>
|
||||
{config.label}
|
||||
</button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
if (!isLoaded) {
|
||||
return <div className="app">Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
|
||||
<Button
|
||||
variant={currentPage === 'timestamp' ? 'contained' : 'outlined'}
|
||||
onClick={() => setCurrentPage('timestamp')}
|
||||
>
|
||||
时间戳
|
||||
</Button>
|
||||
<Button
|
||||
variant={currentPage === 'storageCleaner' ? 'contained' : 'outlined'}
|
||||
onClick={() => setCurrentPage('storageCleaner')}
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
存储清理
|
||||
</Button>
|
||||
<Box className="nav-container">
|
||||
{(Object.keys(PAGE_CONFIG) as PageType[])
|
||||
.filter((key) => visiblePages.includes(key))
|
||||
.map((key) => <NavButton key={key} pageKey={key} />)}
|
||||
</Box>
|
||||
{currentPage === 'timestamp' && <TimestampPage />}
|
||||
{currentPage === 'storageCleaner' && <StorageCleanerPage />}
|
||||
|
||||
Vendored
+4
@@ -1,4 +1,8 @@
|
||||
export type PageType = 'timestamp' | 'storageCleaner';
|
||||
|
||||
export interface StorageSchema {
|
||||
'app/currentRoute': PageType;
|
||||
'app/visiblePages': PageType[];
|
||||
'app/lastRoute': string;
|
||||
'app/theme': string;
|
||||
'storageCleaner/preferences': StorageCleanerPreferences;
|
||||
|
||||
Reference in New Issue
Block a user