This commit is contained in:
ryan
2026-07-13 15:17:45 +08:00
parent 08e61ce833
commit e0eb4e5975
334 changed files with 17212 additions and 12321 deletions
@@ -1,12 +1,35 @@
"use client"
'use client';
import * as React from "react"
import {useQuery} from "@tanstack/react-query"
import {Database, Files, HardDrive, Info, Loader2, TrendingUp, Upload,} from "lucide-react"
import {Area, AreaChart, CartesianGrid, Cell, Pie, PieChart, XAxis, YAxis,} from "recharts"
import * as React from 'react';
import { useQuery } from '@tanstack/react-query';
import {
Database,
Files,
HardDrive,
Info,
Loader2,
TrendingUp,
Upload,
} from 'lucide-react';
import {
Area,
AreaChart,
CartesianGrid,
Cell,
Pie,
PieChart,
XAxis,
YAxis,
} from 'recharts';
import {Button} from "@/components/ui/button"
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import {
ChartConfig,
ChartContainer,
@@ -14,265 +37,322 @@ import {
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
import services, {formatFileSize} from "@/lib/services"
} from '@/components/ui/chart';
import services, { formatFileSize } from '@/lib/services';
const categoryMap: Record<string, string> = {
"图片": "images",
"视频": "videos",
"音频": "audio",
"文档": "documents",
"压缩包": "archives",
"其他": "others",
}
图片: 'images',
视频: 'videos',
音频: 'audio',
文档: 'documents',
压缩包: 'archives',
其他: 'others',
};
const statsChartConfig = {
images: {
label: "图片",
color: "hsl(var(--chart-1))",
label: '图片',
color: 'hsl(var(--chart-1))',
},
videos: {
label: "视频",
color: "hsl(var(--chart-2))",
label: '视频',
color: 'hsl(var(--chart-2))',
},
audio: {
label: "音频",
color: "hsl(var(--chart-3))",
label: '音频',
color: 'hsl(var(--chart-3))',
},
documents: {
label: "文档",
color: "hsl(var(--chart-4))",
label: '文档',
color: 'hsl(var(--chart-4))',
},
archives: {
label: "压缩包",
color: "hsl(var(--chart-5))",
label: '压缩包',
color: 'hsl(var(--chart-5))',
},
others: {
label: "其他",
color: "hsl(var(--chart-6))",
label: '其他',
color: 'hsl(var(--chart-6))',
},
count: {
label: "新增文件数",
color: "hsl(var(--primary))",
label: '新增文件数',
color: 'hsl(var(--primary))',
},
size: {
label: "新增大小",
color: "hsl(var(--chart-2))",
label: '新增大小',
color: 'hsl(var(--chart-2))',
},
} satisfies ChartConfig
} satisfies ChartConfig;
export function FileStats() {
const [trendMetric, setTrendMetric] = React.useState<"count" | "size">("count")
const [trendMetric, setTrendMetric] = React.useState<'count' | 'size'>(
'count',
);
const statsQuery = useQuery({
queryKey: ["files", "stats"],
queryKey: ['files', 'stats'],
queryFn: () => services.adminUpload.getFileStats(),
staleTime: 0,
refetchOnMount: "always",
})
refetchOnMount: 'always',
});
const stats = statsQuery.data
const stats = statsQuery.data;
const trendData = React.useMemo(() => {
if (!stats?.trend) return []
if (!stats?.trend) return [];
return stats.trend.map((item) => ({
...item,
formattedDate: item.date.substring(5), // YYYY-MM-DD -> MM-DD
value: trendMetric === "count" ? item.count : item.size,
}))
}, [stats?.trend, trendMetric])
value: trendMetric === 'count' ? item.count : item.size,
}));
}, [stats?.trend, trendMetric]);
const categoryCountData = React.useMemo(() => {
if (!stats?.categories) return []
if (!stats?.categories) return [];
return stats.categories
.filter((c) => c.count > 0)
.map((c) => {
const key = categoryMap[c.name] || "others"
const key = categoryMap[c.name] || 'others';
return {
name: key,
value: c.count,
fill: `var(--color-${key})`,
}
})
}, [stats?.categories])
};
});
}, [stats?.categories]);
const categorySizeData = React.useMemo(() => {
if (!stats?.categories) return []
if (!stats?.categories) return [];
return stats.categories
.filter((c) => c.size > 0)
.map((c) => {
const key = categoryMap[c.name] || "others"
const key = categoryMap[c.name] || 'others';
return {
name: key,
value: c.size,
fill: `var(--color-${key})`,
}
})
}, [stats?.categories])
};
});
}, [stats?.categories]);
const maxStats = React.useMemo(() => {
if (!stats?.categories || stats.categories.length === 0) {
return { maxCountName: "无", maxCount: 0, maxSizeName: "无", maxSize: 0 }
return { maxCountName: '无', maxCount: 0, maxSizeName: '无', maxSize: 0 };
}
let maxCountName = "无"
let maxCount = 0
let maxSizeName = "无"
let maxSize = 0
let maxCountName = '无';
let maxCount = 0;
let maxSizeName = '无';
let maxSize = 0;
stats.categories.forEach((cat) => {
if (cat.count > maxCount) {
maxCount = cat.count
maxCountName = cat.name
maxCount = cat.count;
maxCountName = cat.name;
}
if (cat.size > maxSize) {
maxSize = cat.size
maxSizeName = cat.name
maxSize = cat.size;
maxSizeName = cat.name;
}
})
});
return { maxCountName, maxCount, maxSizeName, maxSize }
}, [stats?.categories])
return { maxCountName, maxCount, maxSizeName, maxSize };
}, [stats?.categories]);
const trendSummary = React.useMemo(() => {
if (!stats?.trend) return { count: 0, size: 0 }
if (!stats?.trend) return { count: 0, size: 0 };
return stats.trend.reduce(
(acc, curr) => ({
count: acc.count + curr.count,
size: acc.size + curr.size,
}),
{ count: 0, size: 0 }
)
}, [stats?.trend])
{ count: 0, size: 0 },
);
}, [stats?.trend]);
if (statsQuery.isLoading) {
return (
<div className="flex items-center justify-center py-32">
<Loader2 className="size-8 animate-spin text-sky-500" />
<div className='flex items-center justify-center py-32'>
<Loader2 className='size-8 animate-spin text-sky-500' />
</div>
)
);
}
if (!stats || stats.total_count === 0) {
return (
<div className="flex flex-col items-center justify-center py-24 border border-dashed rounded-xl bg-card text-muted-foreground gap-4">
<Upload className="size-14 text-muted-foreground/25" />
<div className="text-center space-y-1">
<p className="font-medium">暂无上传数据</p>
<p className="text-xs text-muted-foreground">上传文件后即可在此查看容量与文件分布分析。</p>
<div className='flex flex-col items-center justify-center py-24 border border-dashed rounded-xl bg-card text-muted-foreground gap-4'>
<Upload className='size-14 text-muted-foreground/25' />
<div className='text-center space-y-1'>
<p className='font-medium'>暂无上传数据</p>
<p className='text-xs text-muted-foreground'>
上传文件后即可在此查看容量与文件分布分析。
</p>
</div>
</div>
)
);
}
return (
<div className="space-y-6">
<div className='space-y-6'>
{/* 四个指标卡片 */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Card className="bg-card/25 border-border/40 hover:shadow-md transition-shadow">
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<CardTitle className="text-xs font-semibold tracking-tight text-muted-foreground">总文件数</CardTitle>
<Files className="size-4 text-sky-500" />
<div className='grid gap-4 sm:grid-cols-2 lg:grid-cols-4'>
<Card className='bg-card/25 border-border/40 hover:shadow-md transition-shadow'>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-xs font-semibold tracking-tight text-muted-foreground'>
总文件数
</CardTitle>
<Files className='size-4 text-sky-500' />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold font-mono text-sky-500">
{stats.total_count.toLocaleString()}{" "}
<span className="text-xs font-normal text-muted-foreground">个</span>
<div className='text-2xl font-bold font-mono text-sky-500'>
{stats.total_count.toLocaleString()}{' '}
<span className='text-xs font-normal text-muted-foreground'>
个
</span>
</div>
<p className="text-[10px] text-muted-foreground mt-1">您上传的可用文件数量汇总</p>
<p className='text-[10px] text-muted-foreground mt-1'>
您上传的可用文件数量汇总
</p>
</CardContent>
</Card>
<Card className="bg-card/25 border-border/40 hover:shadow-md transition-shadow">
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<CardTitle className="text-xs font-semibold tracking-tight text-muted-foreground">占用存储容量</CardTitle>
<Database className="size-4 text-emerald-500" />
<Card className='bg-card/25 border-border/40 hover:shadow-md transition-shadow'>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-xs font-semibold tracking-tight text-muted-foreground'>
占用存储容量
</CardTitle>
<Database className='size-4 text-emerald-500' />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold font-mono text-emerald-500">{formatFileSize(stats.total_size)}</div>
<p className="text-[10px] text-muted-foreground mt-1">文件实际在磁盘/S3中占用的总空间</p>
</CardContent>
</Card>
<Card className="bg-card/25 border-border/40 hover:shadow-md transition-shadow">
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<CardTitle className="text-xs font-semibold tracking-tight text-muted-foreground">近 7 天新增文件</CardTitle>
<TrendingUp className="size-4 text-indigo-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold font-mono text-indigo-500">
+{trendSummary.count.toLocaleString()}{" "}
<span className="text-xs font-normal text-muted-foreground">个</span>
<div className='text-2xl font-bold font-mono text-emerald-500'>
{formatFileSize(stats.total_size)}
</div>
<p className="text-[10px] text-muted-foreground mt-1">最近 7 天上传成功的文件总数</p>
<p className='text-[10px] text-muted-foreground mt-1'>
文件实际在磁盘/S3中占用的总空间
</p>
</CardContent>
</Card>
<Card className="bg-card/25 border-border/40 hover:shadow-md transition-shadow">
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<CardTitle className="text-xs font-semibold tracking-tight text-muted-foreground">近 7 天新增大小</CardTitle>
<HardDrive className="size-4 text-amber-500" />
<Card className='bg-card/25 border-border/40 hover:shadow-md transition-shadow'>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-xs font-semibold tracking-tight text-muted-foreground'>
近 7 天新增文件
</CardTitle>
<TrendingUp className='size-4 text-indigo-500' />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold font-mono text-amber-500">+{formatFileSize(trendSummary.size)}</div>
<p className="text-[10px] text-muted-foreground mt-1">最近 7 天上传所消耗的存储带宽</p>
<div className='text-2xl font-bold font-mono text-indigo-500'>
+{trendSummary.count.toLocaleString()}{' '}
<span className='text-xs font-normal text-muted-foreground'>
个
</span>
</div>
<p className='text-[10px] text-muted-foreground mt-1'>
最近 7 天上传成功的文件总数
</p>
</CardContent>
</Card>
<Card className='bg-card/25 border-border/40 hover:shadow-md transition-shadow'>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-xs font-semibold tracking-tight text-muted-foreground'>
近 7 天新增大小
</CardTitle>
<HardDrive className='size-4 text-amber-500' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold font-mono text-amber-500'>
+{formatFileSize(trendSummary.size)}
</div>
<p className='text-[10px] text-muted-foreground mt-1'>
最近 7 天上传所消耗的存储带宽
</p>
</CardContent>
</Card>
</div>
{/* 7 天新增趋势折线图 */}
<Card className="bg-card/20 border-border/40">
<CardHeader className="flex flex-row items-center justify-between">
<Card className='bg-card/20 border-border/40'>
<CardHeader className='flex flex-row items-center justify-between'>
<div>
<CardTitle className="text-sm font-bold">最近 7 天新增趋势</CardTitle>
<CardDescription className="text-xs">按日统计近一周期内的上传波动</CardDescription>
<CardTitle className='text-sm font-bold'>
最近 7 天新增趋势
</CardTitle>
<CardDescription className='text-xs'>
按日统计近一周期内的上传波动
</CardDescription>
</div>
<div className="flex items-center gap-1 border rounded-lg p-0.5 bg-muted/30">
<div className='flex items-center gap-1 border rounded-lg p-0.5 bg-muted/30'>
<Button
size="sm"
variant={trendMetric === "count" ? "secondary" : "ghost"}
className="h-7 text-xs px-2.5 rounded-md"
onClick={() => setTrendMetric("count")}
size='sm'
variant={trendMetric === 'count' ? 'secondary' : 'ghost'}
className='h-7 text-xs px-2.5 rounded-md'
onClick={() => setTrendMetric('count')}
>
新增数量 (个)
</Button>
<Button
size="sm"
variant={trendMetric === "size" ? "secondary" : "ghost"}
className="h-7 text-xs px-2.5 rounded-md"
onClick={() => setTrendMetric("size")}
size='sm'
variant={trendMetric === 'size' ? 'secondary' : 'ghost'}
className='h-7 text-xs px-2.5 rounded-md'
onClick={() => setTrendMetric('size')}
>
新增大小 (MB)
</Button>
</div>
</CardHeader>
<CardContent className="pl-2 pr-4 pt-2">
<div className="h-[280px] w-full">
<ChartContainer config={statsChartConfig} className="w-full h-full">
<AreaChart data={trendData} margin={{ top: 10, right: 10, left: 10, bottom: 0 }}>
<CardContent className='pl-2 pr-4 pt-2'>
<div className='h-[280px] w-full'>
<ChartContainer config={statsChartConfig} className='w-full h-full'>
<AreaChart
data={trendData}
margin={{ top: 10, right: 10, left: 10, bottom: 0 }}
>
<defs>
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-count)" stopOpacity={0.3} />
<stop offset="95%" stopColor="var(--color-count)" stopOpacity={0.01} />
<linearGradient id='colorCount' x1='0' y1='0' x2='0' y2='1'>
<stop
offset='5%'
stopColor='var(--color-count)'
stopOpacity={0.3}
/>
<stop
offset='95%'
stopColor='var(--color-count)'
stopOpacity={0.01}
/>
</linearGradient>
<linearGradient id="colorSize" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-size)" stopOpacity={0.3} />
<stop offset="95%" stopColor="var(--color-size)" stopOpacity={0.01} />
<linearGradient id='colorSize' x1='0' y1='0' x2='0' y2='1'>
<stop
offset='5%'
stopColor='var(--color-size)'
stopOpacity={0.3}
/>
<stop
offset='95%'
stopColor='var(--color-size)'
stopOpacity={0.01}
/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border)/40)" />
<CartesianGrid
strokeDasharray='3 3'
vertical={false}
stroke='hsl(var(--border)/40)'
/>
<XAxis
dataKey="formattedDate"
dataKey='formattedDate'
tickLine={false}
axisLine={false}
tickMargin={8}
style={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
style={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }}
/>
<YAxis
tickLine={false}
axisLine={false}
tickMargin={8}
style={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
tickFormatter={(v) => (trendMetric === "size" ? formatFileSize(v) : v.toString())}
style={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }}
tickFormatter={(v) =>
trendMetric === 'size' ? formatFileSize(v) : v.toString()
}
/>
<ChartTooltip
cursor={false}
@@ -280,37 +360,52 @@ export function FileStats() {
<ChartTooltipContent
hideLabel
formatter={(value) => {
const label = trendMetric === "count" ? "新增文件数" : "新增大小"
const label =
trendMetric === 'count' ? '新增文件数' : '新增大小';
const formattedValue =
trendMetric === "size" ? formatFileSize(Number(value)) : `${value} 个`
trendMetric === 'size'
? formatFileSize(Number(value))
: `${value} 个`;
return (
<>
<div className="flex items-center gap-1.5">
<div className='flex items-center gap-1.5'>
<span
className="size-2 rounded-full animate-pulse"
className='size-2 rounded-full animate-pulse'
style={{
backgroundColor:
trendMetric === "count" ? "var(--color-count)" : "var(--color-size)",
trendMetric === 'count'
? 'var(--color-count)'
: 'var(--color-size)',
}}
/>
<span className="text-muted-foreground">{label}</span>
<span className='text-muted-foreground'>
{label}
</span>
</div>
<span className="text-foreground font-mono font-medium tabular-nums ml-auto">
<span className='text-foreground font-mono font-medium tabular-nums ml-auto'>
{formattedValue}
</span>
</>
)
);
}}
/>
}
/>
<Area
type="monotone"
dataKey="value"
stroke={trendMetric === "count" ? "var(--color-count)" : "var(--color-size)"}
type='monotone'
dataKey='value'
stroke={
trendMetric === 'count'
? 'var(--color-count)'
: 'var(--color-size)'
}
strokeWidth={2.5}
fillOpacity={1}
fill={trendMetric === "count" ? "url(#colorCount)" : "url(#colorSize)"}
fill={
trendMetric === 'count'
? 'url(#colorCount)'
: 'url(#colorSize)'
}
/>
</AreaChart>
</ChartContainer>
@@ -319,30 +414,37 @@ export function FileStats() {
</Card>
{/* 两个饼图分布 */}
<div className="grid gap-6 md:grid-cols-2">
<div className='grid gap-6 md:grid-cols-2'>
{/* 饼图 1: 格式数量分布 */}
<Card className="bg-card/20 border-border/40 flex flex-col">
<Card className='bg-card/20 border-border/40 flex flex-col'>
<CardHeader>
<CardTitle className="text-sm font-bold">文件格式数量分布</CardTitle>
<CardDescription className="text-xs">各种格式类型的文件个数占比</CardDescription>
<CardTitle className='text-sm font-bold'>
文件格式数量分布
</CardTitle>
<CardDescription className='text-xs'>
各种格式类型的文件个数占比
</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-between gap-4">
<CardContent className='flex-1 flex flex-col justify-between gap-4'>
{categoryCountData.length === 0 ? (
<div className="h-[240px] flex items-center justify-center text-xs text-muted-foreground">
<div className='h-[240px] flex items-center justify-center text-xs text-muted-foreground'>
暂无分类数据
</div>
) : (
<div className="h-[240px] w-full">
<ChartContainer config={statsChartConfig} className="w-full h-full">
<div className='h-[240px] w-full'>
<ChartContainer
config={statsChartConfig}
className='w-full h-full'
>
<PieChart>
<Pie
data={categoryCountData}
cx="50%"
cy="50%"
cx='50%'
cy='50%'
innerRadius={60}
outerRadius={80}
paddingAngle={3}
dataKey="value"
dataKey='value'
>
{categoryCountData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
@@ -354,66 +456,88 @@ export function FileStats() {
<ChartTooltipContent
hideLabel
formatter={(value, name) => {
const configObj = statsChartConfig[name as keyof typeof statsChartConfig]
const label = configObj?.label || name
const color = configObj?.color || "hsl(var(--muted))"
const configObj =
statsChartConfig[
name as keyof typeof statsChartConfig
];
const label = configObj?.label || name;
const color =
configObj?.color || 'hsl(var(--muted))';
return (
<>
<div className="flex items-center gap-1.5">
<span className="size-2 rounded-full" style={{ backgroundColor: color }} />
<span className="text-muted-foreground">{label}</span>
<div className='flex items-center gap-1.5'>
<span
className='size-2 rounded-full'
style={{ backgroundColor: color }}
/>
<span className='text-muted-foreground'>
{label}
</span>
</div>
<span className="text-foreground font-mono font-medium tabular-nums ml-auto">
<span className='text-foreground font-mono font-medium tabular-nums ml-auto'>
{value} 个
</span>
</>
)
);
}}
/>
}
/>
<ChartLegend
content={<ChartLegendContent nameKey="name" />}
className="flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] pt-4"
content={<ChartLegendContent nameKey='name' />}
className='flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] pt-4'
/>
</PieChart>
</ChartContainer>
</div>
)}
<div className="bg-sky-500/5 border border-sky-500/10 rounded-lg p-3 flex items-start gap-2.5 text-xs text-sky-600 dark:text-sky-400">
<Info className="size-4 shrink-0 mt-0.5" />
<p className="leading-normal">
文件类型中数量最多的是{" "}
<strong className="font-bold">「{maxStats.maxCountName}」</strong>,累计上传了{" "}
<strong className="font-mono font-bold">{maxStats.maxCount}</strong> 个文件。
<div className='bg-sky-500/5 border border-sky-500/10 rounded-lg p-3 flex items-start gap-2.5 text-xs text-sky-600 dark:text-sky-400'>
<Info className='size-4 shrink-0 mt-0.5' />
<p className='leading-normal'>
文件类型中数量最多的是{' '}
<strong className='font-bold'>
「{maxStats.maxCountName}」
</strong>
,累计上传了{' '}
<strong className='font-mono font-bold'>
{maxStats.maxCount}
</strong>{' '}
个文件。
</p>
</div>
</CardContent>
</Card>
{/* 饼图 2: 格式容量分布 */}
<Card className="bg-card/20 border-border/40 flex flex-col">
<Card className='bg-card/20 border-border/40 flex flex-col'>
<CardHeader>
<CardTitle className="text-sm font-bold">文件占用容量分布</CardTitle>
<CardDescription className="text-xs">各种格式类型的文件大小占比</CardDescription>
<CardTitle className='text-sm font-bold'>
文件占用容量分布
</CardTitle>
<CardDescription className='text-xs'>
各种格式类型的文件大小占比
</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-between gap-4">
<CardContent className='flex-1 flex flex-col justify-between gap-4'>
{categorySizeData.length === 0 ? (
<div className="h-[240px] flex items-center justify-center text-xs text-muted-foreground">
<div className='h-[240px] flex items-center justify-center text-xs text-muted-foreground'>
暂无容量数据
</div>
) : (
<div className="h-[240px] w-full">
<ChartContainer config={statsChartConfig} className="w-full h-full">
<div className='h-[240px] w-full'>
<ChartContainer
config={statsChartConfig}
className='w-full h-full'
>
<PieChart>
<Pie
data={categorySizeData}
cx="50%"
cy="50%"
cx='50%'
cy='50%'
innerRadius={60}
outerRadius={80}
paddingAngle={3}
dataKey="value"
dataKey='value'
>
{categorySizeData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
@@ -425,38 +549,52 @@ export function FileStats() {
<ChartTooltipContent
hideLabel
formatter={(value, name) => {
const configObj = statsChartConfig[name as keyof typeof statsChartConfig]
const label = configObj?.label || name
const color = configObj?.color || "hsl(var(--muted))"
const configObj =
statsChartConfig[
name as keyof typeof statsChartConfig
];
const label = configObj?.label || name;
const color =
configObj?.color || 'hsl(var(--muted))';
return (
<>
<div className="flex items-center gap-1.5">
<span className="size-2 rounded-full" style={{ backgroundColor: color }} />
<span className="text-muted-foreground">{label}</span>
<div className='flex items-center gap-1.5'>
<span
className='size-2 rounded-full'
style={{ backgroundColor: color }}
/>
<span className='text-muted-foreground'>
{label}
</span>
</div>
<span className="text-foreground font-mono font-medium tabular-nums ml-auto">
<span className='text-foreground font-mono font-medium tabular-nums ml-auto'>
{formatFileSize(Number(value))}
</span>
</>
)
);
}}
/>
}
/>
<ChartLegend
content={<ChartLegendContent nameKey="name" />}
className="flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] pt-4"
content={<ChartLegendContent nameKey='name' />}
className='flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] pt-4'
/>
</PieChart>
</ChartContainer>
</div>
)}
<div className="bg-emerald-500/5 border border-emerald-500/10 rounded-lg p-3 flex items-start gap-2.5 text-xs text-emerald-600 dark:text-emerald-400">
<Info className="size-4 shrink-0 mt-0.5" />
<p className="leading-normal">
文件类型中占用存储最大的是{" "}
<strong className="font-bold">「{maxStats.maxSizeName}」</strong>,共消耗了{" "}
<strong className="font-mono font-bold">{formatFileSize(maxStats.maxSize)}</strong>{" "}
<div className='bg-emerald-500/5 border border-emerald-500/10 rounded-lg p-3 flex items-start gap-2.5 text-xs text-emerald-600 dark:text-emerald-400'>
<Info className='size-4 shrink-0 mt-0.5' />
<p className='leading-normal'>
文件类型中占用存储最大的是{' '}
<strong className='font-bold'>
「{maxStats.maxSizeName}」
</strong>
,共消耗了{' '}
<strong className='font-mono font-bold'>
{formatFileSize(maxStats.maxSize)}
</strong>{' '}
存储容量。
</p>
</div>
@@ -464,5 +602,5 @@ export function FileStats() {
</Card>
</div>
</div>
)
);
}