diff --git a/CLAUDE.md b/CLAUDE.md index aaff618..93a0775 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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: [''] // 访问所有网站 - 使用 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`)以避免类型检查 ### 项目历史 diff --git a/entrypoints/popup/App.css b/entrypoints/popup/App.css index 873141c..cabb953 100644 --- a/entrypoints/popup/App.css +++ b/entrypoints/popup/App.css @@ -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; +} diff --git a/entrypoints/popup/App.tsx b/entrypoints/popup/App.tsx index 62d9764..029b2e7 100644 --- a/entrypoints/popup/App.tsx +++ b/entrypoints/popup/App.tsx @@ -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; function App() { const [currentPage, setCurrentPage] = useState('timestamp'); + const [visiblePages, setVisiblePages] = useState(['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 ( + + + + ); + }; + + if (!isLoaded) { + return
Loading...
; + } return (
- - - + + {(Object.keys(PAGE_CONFIG) as PageType[]) + .filter((key) => visiblePages.includes(key)) + .map((key) => )} {currentPage === 'timestamp' && } {currentPage === 'storageCleaner' && } diff --git a/types/storage.d.ts b/types/storage.d.ts index fad95bb..823a52c 100644 --- a/types/storage.d.ts +++ b/types/storage.d.ts @@ -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;