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` 准备开发环境
|
- `postinstall` 会自动运行 `wxt prepare` 准备开发环境
|
||||||
- `prepare` 钩子会初始化 Husky Git 钩子
|
- `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 进行代码检查(零警告)
|
- 使用 ESLint 进行代码检查(零警告)
|
||||||
- Husky 用于 Git 钩子管理
|
- Husky 用于 Git 钩子管理
|
||||||
- Lint-staged 确保暂存文件符合规范
|
- Lint-staged 确保暂存文件符合规范(ESLint + TypeScript + Prettier)
|
||||||
- Prettier 用于代码格式化
|
- Prettier 用于代码格式化
|
||||||
|
- Prettier 配置: 100 字符行宽,2 空格缩进,单引号,trailing comma
|
||||||
|
|
||||||
### TypeScript 配置
|
### TypeScript 配置
|
||||||
|
|
||||||
- 严格模式开启(`strict: true`)
|
- 严格模式开启(`strict: true`)
|
||||||
- 不允许隐式 any(可配置,当前关闭)
|
- `noImplicitAny` 设置为 `false`(允许隐式 any)
|
||||||
- 未使用变量/参数会报错
|
- 未使用变量/参数会报错(`noUnusedLocals`, `noUnusedParameters`)
|
||||||
- 模块解析模式:Bundler
|
- 模块解析模式:Bundler
|
||||||
|
- 排除测试文件(`**/*.test.tsx`, `**/*.test.ts`)以避免类型检查
|
||||||
|
|
||||||
### 项目历史
|
### 项目历史
|
||||||
|
|
||||||
|
|||||||
@@ -48,3 +48,32 @@ body {
|
|||||||
transform: translateY(0);
|
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 { useState, useEffect } from 'react';
|
||||||
import { Box, Button } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
|
import type { PageType } from '@/types/storage';
|
||||||
|
import { storageUtil } from '@/utils/chromeStorage';
|
||||||
import TimestampPage from './pages/TimestampPage';
|
import TimestampPage from './pages/TimestampPage';
|
||||||
import StorageCleanerPage from './pages/StorageCleanerPage';
|
import StorageCleanerPage from './pages/StorageCleanerPage';
|
||||||
import './App.css';
|
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() {
|
function App() {
|
||||||
const [currentPage, setCurrentPage] = useState<PageType>('timestamp');
|
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 (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
|
<Box className="nav-container">
|
||||||
<Button
|
{(Object.keys(PAGE_CONFIG) as PageType[])
|
||||||
variant={currentPage === 'timestamp' ? 'contained' : 'outlined'}
|
.filter((key) => visiblePages.includes(key))
|
||||||
onClick={() => setCurrentPage('timestamp')}
|
.map((key) => <NavButton key={key} pageKey={key} />)}
|
||||||
>
|
|
||||||
时间戳
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={currentPage === 'storageCleaner' ? 'contained' : 'outlined'}
|
|
||||||
onClick={() => setCurrentPage('storageCleaner')}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
>
|
|
||||||
存储清理
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
{currentPage === 'timestamp' && <TimestampPage />}
|
{currentPage === 'timestamp' && <TimestampPage />}
|
||||||
{currentPage === 'storageCleaner' && <StorageCleanerPage />}
|
{currentPage === 'storageCleaner' && <StorageCleanerPage />}
|
||||||
|
|||||||
Vendored
+4
@@ -1,4 +1,8 @@
|
|||||||
|
export type PageType = 'timestamp' | 'storageCleaner';
|
||||||
|
|
||||||
export interface StorageSchema {
|
export interface StorageSchema {
|
||||||
|
'app/currentRoute': PageType;
|
||||||
|
'app/visiblePages': PageType[];
|
||||||
'app/lastRoute': string;
|
'app/lastRoute': string;
|
||||||
'app/theme': string;
|
'app/theme': string;
|
||||||
'storageCleaner/preferences': StorageCleanerPreferences;
|
'storageCleaner/preferences': StorageCleanerPreferences;
|
||||||
|
|||||||
Reference in New Issue
Block a user