feat(Dashboard): 重构仪表板功能组件和逻辑
- 新增 dashboardFeatures.ts 和 useDashboard.ts 文件,封装仪表板功能的解析和状态管理逻辑。 - 更新 Dashboard 组件,使用 useDashboard 钩子简化可见工具和最近使用工具的处理。 - 添加仪表板功能的单元测试,确保功能正确性。 - 更新 README.md,移除不再使用的 ToolCard 组件说明。
This commit is contained in:
@@ -1,28 +1,11 @@
|
||||
import { useRouter } from '@/providers/RouterProvider';
|
||||
import { getFeatureByKey } from '@/config/features';
|
||||
import type { PageType } from '@/types/storage';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useDashboard } from './useDashboard';
|
||||
|
||||
export default function Index() {
|
||||
const { navigateTo, visiblePages, pageOrder, recentlyUsedTools } = useRouter();
|
||||
|
||||
const visibleSet = new Set<string>(visiblePages);
|
||||
|
||||
const visibleFeatures = pageOrder
|
||||
.filter((key) => visibleSet.has(key))
|
||||
.map((key) => ({ key, feature: getFeatureByKey(key) }))
|
||||
.filter((item) => item.feature?.themeColorKey && item.feature.icon != null);
|
||||
|
||||
const recentFeatures = recentlyUsedTools
|
||||
.filter((key) => visibleSet.has(key))
|
||||
.map((key) => ({ key, feature: getFeatureByKey(key) }))
|
||||
.filter((item) => item.feature?.themeColorKey && item.feature.icon != null);
|
||||
|
||||
const showRecent = recentFeatures.length > 0;
|
||||
const { visibleFeatures, recentFeatures, showRecent, navigateTo } = useDashboard();
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-4 p-3.5 w-full h-auto select-none')}>
|
||||
{/* 最近使用 */}
|
||||
{showRecent && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider">
|
||||
@@ -30,12 +13,12 @@ export default function Index() {
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{recentFeatures.map(({ key, feature }) => {
|
||||
const IconComponent = feature!.icon!;
|
||||
const IconComponent = feature.icon;
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
onClick={() => navigateTo(key as PageType)}
|
||||
onClick={() => navigateTo(key)}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium',
|
||||
'border border-border/60 bg-card text-card-foreground',
|
||||
@@ -44,7 +27,7 @@ export default function Index() {
|
||||
)}
|
||||
>
|
||||
<IconComponent className="h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
{feature!.label}
|
||||
{feature.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -52,40 +35,45 @@ export default function Index() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 全部工具 — 紧凑 Grid */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider">
|
||||
{'全部工具'}
|
||||
</h3>
|
||||
<div className={cn('grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-2')}>
|
||||
{visibleFeatures.map(({ key, feature }) => {
|
||||
const IconComponent = feature!.icon!;
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
onClick={() => navigateTo(key as PageType)}
|
||||
className={cn(
|
||||
'group flex flex-col items-center justify-center gap-1.5',
|
||||
'py-3 px-2 rounded-xl border border-border/50 bg-card',
|
||||
'hover:bg-muted/40 hover:border-primary/30',
|
||||
'transition-colors cursor-pointer',
|
||||
)}
|
||||
>
|
||||
<IconComponent
|
||||
{visibleFeatures.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground py-4 text-center">{'没有可用的工具'}</p>
|
||||
) : (
|
||||
<div
|
||||
className={cn('grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-2')}
|
||||
>
|
||||
{visibleFeatures.map(({ key, feature }) => {
|
||||
const IconComponent = feature.icon;
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
onClick={() => navigateTo(key)}
|
||||
className={cn(
|
||||
'h-5 w-5 text-muted-foreground/70',
|
||||
'group-hover:text-foreground',
|
||||
'transition-colors',
|
||||
'group flex flex-col items-center justify-center gap-1.5',
|
||||
'py-3 px-2 rounded-xl border border-border/50 bg-card',
|
||||
'hover:bg-muted/40 hover:border-primary/30',
|
||||
'transition-colors cursor-pointer',
|
||||
)}
|
||||
/>
|
||||
<span className="text-[11px] font-medium text-muted-foreground/80 group-hover:text-foreground leading-tight text-center truncate w-full transition-colors">
|
||||
{feature!.label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
>
|
||||
<IconComponent
|
||||
className={cn(
|
||||
'h-5 w-5 text-muted-foreground/70',
|
||||
'group-hover:text-foreground',
|
||||
'transition-colors',
|
||||
)}
|
||||
/>
|
||||
<span className="text-[11px] font-medium text-muted-foreground/80 group-hover:text-foreground leading-tight text-center truncate w-full transition-colors">
|
||||
{feature.label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user