用 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 { ChevronRight } from 'lucide-react';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import type { SvgIconProps } from '@mui/material/SvgIcon';
import type { ComponentType } from 'react'; import type { ComponentType } from 'react';
import type { SvgIconProps } from '@mui/material/SvgIcon';
import type { PaletteColorKey } from '@/config/features'; import type { PaletteColorKey } from '@/config/features';
/** const PALETTE_COLORS: Record<PaletteColorKey, string> = {
* ToolCard 组件属性接口 primary: '#1976d2',
*/ success: '#2e7d32',
warning: '#e65100',
error: '#c62828',
secondary: '#9c27b0',
info: '#0288d1',
};
interface ToolCardProps { interface ToolCardProps {
/** 工具卡片标题 */
title: string; title: string;
/** 工具卡片描述文本(可选) */
description?: string; description?: string;
/** 快照内容,用于在卡片底部展示额外信息(可选) */
snapshot?: React.ReactNode; snapshot?: React.ReactNode;
/** 主题色键,映射到 theme.palette[key].main */
colorKey: PaletteColorKey; colorKey: PaletteColorKey;
/** 图标组件引用 */
icon: ComponentType<SvgIconProps>; icon: ComponentType<SvgIconProps>;
/** 卡片点击事件处理函数 */
onClick: () => void; onClick: () => void;
} }
/**
* ToolCard 组件
*
* @param props - ToolCardProps 属性对象
* @returns 工具卡片 JSX 元素
*/
export default function ToolCard({ export default function ToolCard({
title, title,
description, description,
@@ -42,120 +35,71 @@ export default function ToolCard({
icon: IconComponent, icon: IconComponent,
onClick, onClick,
}: ToolCardProps) { }: ToolCardProps) {
const theme = useTheme(); const colorCode = PALETTE_COLORS[colorKey];
const colorCode = theme.palette[colorKey].main;
return ( return (
<Card <div
elevation={0} 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"
sx={{ style={{
position: 'relative', borderColor: undefined,
borderRadius: 4, ['--hover-color' as string]: colorCode,
border: '1px solid', }}
borderColor: 'divider', onMouseEnter={(e) => {
height: '100%', e.currentTarget.style.borderColor = colorCode;
boxSizing: 'border-box', e.currentTarget.style.boxShadow = `0 12px 24px -10px ${colorCode}33`;
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)', onMouseLeave={(e) => {
'&:hover': { e.currentTarget.style.borderColor = '';
borderColor: colorCode, e.currentTarget.style.boxShadow = '';
transform: 'translateY(-4px)',
boxShadow: `0 12px 24px -10px ${alpha(colorCode, 0.2)}`,
},
}} }}
> >
<CardActionArea <button
type="button"
tabIndex={0}
onClick={onClick} onClick={onClick}
sx={{ className="h-full flex flex-col items-stretch justify-start p-5 gap-3 w-full text-left cursor-pointer bg-transparent border-none"
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,
},
}}
> >
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" width="100%"> <div className="flex justify-between items-start w-full">
<Stack direction="row" spacing={1.5} alignItems="center"> <div className="flex gap-3 items-center">
<Box <div
sx={{ className="flex items-center justify-center w-10 h-10 rounded-xl"
display: 'flex', style={{
alignItems: 'center', backgroundColor: `${colorCode}11`,
justifyContent: 'center',
width: 40,
height: 40,
borderRadius: 3,
bgcolor: alpha(colorCode, 0.07),
color: colorCode, color: colorCode,
}} }}
> >
<IconComponent sx={{ fontSize: 20 }} /> <IconComponent sx={{ fontSize: 20 }} />
</Box> </div>
<Box> <div>
<Typography <span className="font-bold text-sm leading-tight text-gray-900 flex items-center gap-1">
variant="subtitle1"
sx={{
fontWeight: 700,
lineHeight: 1.2,
color: 'text.primary',
display: 'flex',
alignItems: 'center',
gap: 0.5,
}}
>
{title} {title}
</Typography> </span>
{description && ( {description && (
<Typography <span className="block text-xs text-gray-500 font-medium mt-1 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]">
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',
}}
>
{description} {description}
</Typography> </span>
)} )}
</Box> </div>
</Stack> </div>
<ArrowForwardIosIcon <ChevronRight
className="arrow-icon" className="w-3 h-3 text-gray-300 mt-1 transition-all duration-300 ease-in-out group-hover:translate-x-1"
sx={{ style={{ color: undefined }}
fontSize: 12, onMouseEnter={(e) => {
color: 'text.disabled', e.currentTarget.style.color = colorCode;
mt: 0.5, }}
transition: 'all 0.3s ease', onMouseLeave={(e) => {
e.currentTarget.style.color = '';
}} }}
/> />
</Stack> </div>
{snapshot != null && ( {snapshot != null && (
<Box <div className="mt-auto pt-4 border-t border-dashed border-gray-200 w-full">
sx={{
mt: 'auto',
pt: 1.5,
borderTop: '1px dashed',
borderColor: 'divider',
width: '100%',
}}
>
{snapshot} {snapshot}
</Box> </div>
)} )}
</CardActionArea> </button>
</Card> </div>
); );
} }
+2 -4
View File
@@ -1,11 +1,9 @@
import { Box } from '@mui/material';
import { useRouter } from '@/providers/RouterProvider'; import { useRouter } from '@/providers/RouterProvider';
import ToolCard from '@/pages/Dashboard/ToolCard'; import ToolCard from '@/pages/Dashboard/ToolCard';
import { getFeatureByKey } from '@/config/features'; import { getFeatureByKey } from '@/config/features';
import type { PageType } from '@/types/storage'; import type { PageType } from '@/types/storage';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { dashboardPageStyles } from '@/config/pageTheme';
export default function DashboardPage() { export default function DashboardPage() {
const { navigateTo, visiblePages, pageOrder } = useRouter(); const { navigateTo, visiblePages, pageOrder } = useRouter();
@@ -14,7 +12,7 @@ export default function DashboardPage() {
const visibleSet = useMemo(() => new Set(visiblePages), [visiblePages]); const visibleSet = useMemo(() => new Set(visiblePages), [visiblePages]);
return ( 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) => { {pageOrder.map((key) => {
if (!visibleSet.has(key as PageType)) return null; if (!visibleSet.has(key as PageType)) return null;
@@ -32,6 +30,6 @@ export default function DashboardPage() {
/> />
); );
})} })}
</Box> </div>
); );
} }