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:
雨霖铃
2026-03-21 09:46:41 +08:00
parent b56674b83b
commit 3bac87717f
4 changed files with 115 additions and 20 deletions
+12 -3
View File
@@ -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`)以避免类型检查
### 项目历史
+29
View File
@@ -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
View File
@@ -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 />}
+4
View File
@@ -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;