用 Tailwind CSS 重写 Dashboard/index.tsx 和 ToolCard.tsx
This commit is contained in:
+56
-112
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user