用 Tailwind CSS 重写 Dashboard/index.tsx 和 ToolCard.tsx

This commit is contained in:
雨霖铃
2026-05-21 23:32:19 +08:00
parent 6bd05aa6f1
commit 7f9bf2334c
2 changed files with 58 additions and 116 deletions
+56 -112
View File
@@ -4,36 +4,29 @@
* 用于在仪表盘中展示各个工具功能的卡片组件,支持图标、标题、描述、
* 快照内容展示,具备悬停动画效果。
*/
import { alpha, Box, Card, CardActionArea, Stack, Typography, useTheme } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import type { SvgIconProps } from '@mui/material/SvgIcon';
import { ChevronRight } from 'lucide-react';
import type { ComponentType } from 'react';
import type { SvgIconProps } from '@mui/material/SvgIcon';
import type { PaletteColorKey } from '@/config/features';
/**
* ToolCard 组件属性接口
*/
const PALETTE_COLORS: Record<PaletteColorKey, string> = {
primary: '#1976d2',
success: '#2e7d32',
warning: '#e65100',
error: '#c62828',
secondary: '#9c27b0',
info: '#0288d1',
};
interface ToolCardProps {
/** 工具卡片标题 */
title: string;
/** 工具卡片描述文本(可选) */
description?: string;
/** 快照内容,用于在卡片底部展示额外信息(可选) */
snapshot?: React.ReactNode;
/** 主题色键,映射到 theme.palette[key].main */
colorKey: PaletteColorKey;
/** 图标组件引用 */
icon: ComponentType<SvgIconProps>;
/** 卡片点击事件处理函数 */
onClick: () => void;
}
/**
* ToolCard 组件
*
* @param props - ToolCardProps 属性对象
* @returns 工具卡片 JSX 元素
*/
export default function ToolCard({
title,
description,
@@ -42,120 +35,71 @@ export default function ToolCard({
icon: IconComponent,
onClick,
}: ToolCardProps) {
const theme = useTheme();
const colorCode = theme.palette[colorKey].main;
const colorCode = PALETTE_COLORS[colorKey];
return (
<Card
elevation={0}
sx={{
position: 'relative',
borderRadius: 4,
border: '1px solid',
borderColor: 'divider',
height: '100%',
boxSizing: 'border-box',
transition:
'border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
borderColor: colorCode,
transform: 'translateY(-4px)',
boxShadow: `0 12px 24px -10px ${alpha(colorCode, 0.2)}`,
},
<div
className="group relative rounded-2xl border border-gray-200 h-full box-border transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] hover:-translate-y-1"
style={{
borderColor: undefined,
['--hover-color' as string]: colorCode,
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = colorCode;
e.currentTarget.style.boxShadow = `0 12px 24px -10px ${colorCode}33`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = '';
e.currentTarget.style.boxShadow = '';
}}
>
<CardActionArea
<button
type="button"
tabIndex={0}
onClick={onClick}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
justifyContent: 'flex-start',
p: 2.5,
gap: 1.5,
'&:hover .arrow-icon': {
transform: 'translateX(4px)',
color: colorCode,
},
}}
className="h-full flex flex-col items-stretch justify-start p-5 gap-3 w-full text-left cursor-pointer bg-transparent border-none"
>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" width="100%">
<Stack direction="row" spacing={1.5} alignItems="center">
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 40,
height: 40,
borderRadius: 3,
bgcolor: alpha(colorCode, 0.07),
<div className="flex justify-between items-start w-full">
<div className="flex gap-3 items-center">
<div
className="flex items-center justify-center w-10 h-10 rounded-xl"
style={{
backgroundColor: `${colorCode}11`,
color: colorCode,
}}
>
<IconComponent sx={{ fontSize: 20 }} />
</Box>
<Box>
<Typography
variant="subtitle1"
sx={{
fontWeight: 700,
lineHeight: 1.2,
color: 'text.primary',
display: 'flex',
alignItems: 'center',
gap: 0.5,
}}
>
</div>
<div>
<span className="font-bold text-sm leading-tight text-gray-900 flex items-center gap-1">
{title}
</Typography>
</span>
{description && (
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontWeight: 500,
mt: 0.5,
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
}}
>
<span className="block text-xs text-gray-500 font-medium mt-1 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]">
{description}
</Typography>
</span>
)}
</Box>
</Stack>
<ArrowForwardIosIcon
className="arrow-icon"
sx={{
fontSize: 12,
color: 'text.disabled',
mt: 0.5,
transition: 'all 0.3s ease',
</div>
</div>
<ChevronRight
className="w-3 h-3 text-gray-300 mt-1 transition-all duration-300 ease-in-out group-hover:translate-x-1"
style={{ color: undefined }}
onMouseEnter={(e) => {
e.currentTarget.style.color = colorCode;
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '';
}}
/>
</Stack>
</div>
{snapshot != null && (
<Box
sx={{
mt: 'auto',
pt: 1.5,
borderTop: '1px dashed',
borderColor: 'divider',
width: '100%',
}}
>
<div className="mt-auto pt-4 border-t border-dashed border-gray-200 w-full">
{snapshot}
</Box>
</div>
)}
</CardActionArea>
</Card>
</button>
</div>
);
}
+2 -4
View File
@@ -1,11 +1,9 @@
import { Box } from '@mui/material';
import { useRouter } from '@/providers/RouterProvider';
import ToolCard from '@/pages/Dashboard/ToolCard';
import { getFeatureByKey } from '@/config/features';
import type { PageType } from '@/types/storage';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { dashboardPageStyles } from '@/config/pageTheme';
export default function DashboardPage() {
const { navigateTo, visiblePages, pageOrder } = useRouter();
@@ -14,7 +12,7 @@ export default function DashboardPage() {
const visibleSet = useMemo(() => new Set(visiblePages), [visiblePages]);
return (
<Box sx={dashboardPageStyles.GRID_CONTAINER}>
<div className="grid grid-cols-1 sm:grid-cols-[repeat(auto-fill,minmax(300px,1fr))] auto-rows-fr gap-2 p-2">
{pageOrder.map((key) => {
if (!visibleSet.has(key as PageType)) return null;
@@ -32,6 +30,6 @@ export default function DashboardPage() {
/>
);
})}
</Box>
</div>
);
}