refactor: 统一功能组件结构,简化特性配置
This commit is contained in:
@@ -677,7 +677,7 @@ src/pages/StorageCleaner/useStorageCleaner.ts — 页面级 Hook
|
||||
label: '时间戳转换',
|
||||
description: '日期与时间戳互转',
|
||||
defaultVisible: true,
|
||||
components: { popup: TimestampPage, sidepanel: TimestampPage, tab: TimestampPage },
|
||||
component: TimestampPage,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -764,7 +764,7 @@ const [themeMode, setThemeMode, isInitialized] = useStorageState(
|
||||
Chrome Storage 读取是异步的。项目通过 `localStorage` 快照(键名 `snapshot/{storageKey}`)提供同步初始值,消除首屏闪烁。
|
||||
|
||||
| 模块 | 快照工具 | 防覆盖机制 |
|
||||
| ---- | -------- | ---------- |
|
||||
| ------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
|
||||
| `RouterProvider` | `syncSnapshot.ts` | `canPersistRef`(加载成功后才写入)、`hasUserNavigatedRef`(用户导航后不被 storage 覆盖) |
|
||||
| `useStorageState` | `syncSnapshot.ts` | `loadSucceededRef` 或 `userModifiedRef` 为 true 时才写入 |
|
||||
| `ThemeModeProvider` | `themeSnapshot.ts` | `hasUserSetMode`(用户切换主题后不被 storage 覆盖) |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FEATURES, getEntryPointType } from '@/config/features';
|
||||
import { FEATURES } from '@/config/features';
|
||||
import { useRouter } from '@/providers/RouterProvider';
|
||||
import { Suspense } from 'react';
|
||||
import PageErrorBoundary from '@/components/PageErrorBoundary';
|
||||
@@ -6,8 +6,6 @@ import PageSkeleton from '@/components/PageSkeleton';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
|
||||
const entryPointType = getEntryPointType();
|
||||
|
||||
export default function RouterContainer() {
|
||||
const { currentPage, isLoaded } = useRouter();
|
||||
|
||||
@@ -19,7 +17,7 @@ export default function RouterContainer() {
|
||||
}
|
||||
|
||||
const currentFeature = FEATURES.find((f) => f.key === currentPage);
|
||||
const MatchedComponent = currentFeature?.components?.[entryPointType];
|
||||
const MatchedComponent = currentFeature?.component;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -43,7 +41,7 @@ export default function RouterContainer() {
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-foreground">页面未找到</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1 max-w-[240px]">
|
||||
该功能在当前运行环境({entryPointType.toUpperCase()})下不可用或已被移除。
|
||||
该功能不存在或已被移除。
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('features 懒加载', () => {
|
||||
const { render, waitFor } = await import('@testing-library/react');
|
||||
const { FEATURES } = await import('@/config/features');
|
||||
|
||||
const DashboardPage = FEATURES.find((f) => f.key === 'dashboard')!.components.popup;
|
||||
const DashboardPage = FEATURES.find((f) => f.key === 'dashboard')!.component;
|
||||
|
||||
render(
|
||||
React.createElement(React.Suspense, { fallback: null }, React.createElement(DashboardPage)),
|
||||
|
||||
@@ -19,15 +19,13 @@ describe('features', () => {
|
||||
expect(feature).toHaveProperty('label');
|
||||
expect(feature).toHaveProperty('description');
|
||||
expect(feature).toHaveProperty('defaultVisible');
|
||||
expect(feature).toHaveProperty('components');
|
||||
expect(feature).toHaveProperty('component');
|
||||
expect(typeof feature.key).toBe('string');
|
||||
expect(typeof feature.label).toBe('string');
|
||||
expect(typeof feature.description).toBe('string');
|
||||
expect(typeof feature.defaultVisible).toBe('boolean');
|
||||
expect(typeof feature.components).toBe('object');
|
||||
expect(feature.components).toHaveProperty('popup');
|
||||
expect(feature.components).toHaveProperty('sidepanel');
|
||||
expect(feature.components).toHaveProperty('tab');
|
||||
expect(feature.component).toBeDefined();
|
||||
expect(['function', 'object']).toContain(typeof feature.component);
|
||||
|
||||
if (feature.key !== 'dashboard') {
|
||||
expect(feature).toHaveProperty('icon');
|
||||
|
||||
+11
-55
@@ -34,11 +34,7 @@ export interface FeatureConfig {
|
||||
themeColorKey?: PaletteColorKey;
|
||||
icon?: ComponentType<LucideProps>;
|
||||
defaultVisible: boolean;
|
||||
components: {
|
||||
popup: ComponentType;
|
||||
sidepanel: ComponentType;
|
||||
tab: ComponentType;
|
||||
};
|
||||
component: ComponentType;
|
||||
}
|
||||
|
||||
export const FEATURES: FeatureConfig[] = [
|
||||
@@ -47,11 +43,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
label: '仪表盘',
|
||||
description: '',
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: DashboardPage,
|
||||
sidepanel: DashboardPage,
|
||||
tab: DashboardPage,
|
||||
},
|
||||
component: DashboardPage,
|
||||
},
|
||||
{
|
||||
key: 'timestamp',
|
||||
@@ -60,11 +52,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'primary',
|
||||
icon: Clock,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: TimestampPage,
|
||||
sidepanel: TimestampPage,
|
||||
tab: TimestampPage,
|
||||
},
|
||||
component: TimestampPage,
|
||||
},
|
||||
{
|
||||
key: 'storageCleaner',
|
||||
@@ -73,11 +61,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'warning',
|
||||
icon: Database,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: StorageCleanerPage,
|
||||
sidepanel: StorageCleanerPage,
|
||||
tab: StorageCleanerPage,
|
||||
},
|
||||
component: StorageCleanerPage,
|
||||
},
|
||||
{
|
||||
key: 'qrCode',
|
||||
@@ -86,11 +70,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'success',
|
||||
icon: QrCode,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: QrCodePage,
|
||||
sidepanel: QrCodePage,
|
||||
tab: QrCodePage,
|
||||
},
|
||||
component: QrCodePage,
|
||||
},
|
||||
{
|
||||
key: 'textStatistics',
|
||||
@@ -99,11 +79,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'secondary',
|
||||
icon: FileText,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: TextStatisticsPage,
|
||||
sidepanel: TextStatisticsPage,
|
||||
tab: TextStatisticsPage,
|
||||
},
|
||||
component: TextStatisticsPage,
|
||||
},
|
||||
{
|
||||
key: 'jwt',
|
||||
@@ -112,11 +88,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'info',
|
||||
icon: Key,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: JwtPage,
|
||||
sidepanel: JwtPage,
|
||||
tab: JwtPage,
|
||||
},
|
||||
component: JwtPage,
|
||||
},
|
||||
{
|
||||
key: 'jsonTools',
|
||||
@@ -125,11 +97,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'primary',
|
||||
icon: GitCompareArrows,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: JsonToolsPage,
|
||||
sidepanel: JsonToolsPage,
|
||||
tab: JsonToolsPage,
|
||||
},
|
||||
component: JsonToolsPage,
|
||||
},
|
||||
{
|
||||
key: 'base64Converter',
|
||||
@@ -138,11 +106,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'info',
|
||||
icon: ArrowLeftRight,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: Base64ConverterPage,
|
||||
sidepanel: Base64ConverterPage,
|
||||
tab: Base64ConverterPage,
|
||||
},
|
||||
component: Base64ConverterPage,
|
||||
},
|
||||
{
|
||||
key: 'rightClickRestorer',
|
||||
@@ -151,11 +115,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'success',
|
||||
icon: MousePointerClick,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: RightClickRestorerPage,
|
||||
sidepanel: RightClickRestorerPage,
|
||||
tab: RightClickRestorerPage,
|
||||
},
|
||||
component: RightClickRestorerPage,
|
||||
},
|
||||
{
|
||||
key: 'testDataGenerator',
|
||||
@@ -164,11 +124,7 @@ export const FEATURES: FeatureConfig[] = [
|
||||
themeColorKey: 'warning',
|
||||
icon: FileSpreadsheet,
|
||||
defaultVisible: true,
|
||||
components: {
|
||||
popup: TestDataGeneratorPage,
|
||||
sidepanel: TestDataGeneratorPage,
|
||||
tab: TestDataGeneratorPage,
|
||||
},
|
||||
component: TestDataGeneratorPage,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user