mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
refactor(frontend): style files stats tab with native shadcn
refactor(frontend): style files stats tab with native shadcn charts
This commit is contained in:
@@ -4,38 +4,26 @@ import * as React from "react"
|
|||||||
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"
|
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"
|
||||||
import {AnimatePresence, motion} from "motion/react"
|
import {AnimatePresence, motion} from "motion/react"
|
||||||
import {
|
import {
|
||||||
|
Database,
|
||||||
Download,
|
Download,
|
||||||
Eye,
|
Eye,
|
||||||
FileArchive,
|
FileArchive,
|
||||||
FileAudio,
|
FileAudio,
|
||||||
FileImage,
|
FileImage,
|
||||||
|
Files,
|
||||||
FileText,
|
FileText,
|
||||||
FileVideo,
|
FileVideo,
|
||||||
|
FolderOpen,
|
||||||
|
HardDrive,
|
||||||
Loader2,
|
Loader2,
|
||||||
Search,
|
Search,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
TrendingUp,
|
||||||
Upload,
|
Upload,
|
||||||
X,
|
X,
|
||||||
HardDrive,
|
|
||||||
Database,
|
|
||||||
TrendingUp,
|
|
||||||
Files,
|
|
||||||
Info,
|
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import {toast} from "sonner"
|
import {toast} from "sonner"
|
||||||
import {
|
import {Area, AreaChart, CartesianGrid, Cell, Pie, PieChart, XAxis, YAxis,} from "recharts"
|
||||||
Area,
|
|
||||||
AreaChart,
|
|
||||||
CartesianGrid,
|
|
||||||
Cell,
|
|
||||||
Legend,
|
|
||||||
Pie,
|
|
||||||
PieChart,
|
|
||||||
ResponsiveContainer,
|
|
||||||
Tooltip as RechartsTooltip,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
} from "recharts"
|
|
||||||
|
|
||||||
import {Button} from "@/components/ui/button"
|
import {Button} from "@/components/ui/button"
|
||||||
import {Input} from "@/components/ui/input"
|
import {Input} from "@/components/ui/input"
|
||||||
@@ -55,6 +43,14 @@ import {
|
|||||||
import {Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle} from "@/components/ui/sheet"
|
import {Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle} from "@/components/ui/sheet"
|
||||||
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"
|
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"
|
||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"
|
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/components/ui/chart"
|
||||||
import {formatFileSize, getFileUrl, UploadService} from "@/lib/services/upload/upload.service"
|
import {formatFileSize, getFileUrl, UploadService} from "@/lib/services/upload/upload.service"
|
||||||
import type {Upload as UploadRecord} from "@/lib/services/upload/types"
|
import type {Upload as UploadRecord} from "@/lib/services/upload/types"
|
||||||
|
|
||||||
@@ -79,14 +75,49 @@ function formatDate(dateStr: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const PIE_COLORS = [
|
const categoryMap: Record<string, string> = {
|
||||||
"#3b82f6", // Blue (图片)
|
"图片": "images",
|
||||||
"#10b981", // Emerald (视频)
|
"视频": "videos",
|
||||||
"#8b5cf6", // Purple (音频)
|
"音频": "audio",
|
||||||
"#f59e0b", // Amber (文档)
|
"文档": "documents",
|
||||||
"#ec4899", // Pink (压缩包)
|
"压缩包": "archives",
|
||||||
"#64748b", // Slate (其他)
|
"其他": "others",
|
||||||
]
|
}
|
||||||
|
|
||||||
|
const statsChartConfig = {
|
||||||
|
images: {
|
||||||
|
label: "图片",
|
||||||
|
color: "hsl(var(--chart-1))",
|
||||||
|
},
|
||||||
|
videos: {
|
||||||
|
label: "视频",
|
||||||
|
color: "hsl(var(--chart-2))",
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
label: "音频",
|
||||||
|
color: "hsl(var(--chart-3))",
|
||||||
|
},
|
||||||
|
documents: {
|
||||||
|
label: "文档",
|
||||||
|
color: "hsl(var(--chart-4))",
|
||||||
|
},
|
||||||
|
archives: {
|
||||||
|
label: "压缩包",
|
||||||
|
color: "hsl(var(--chart-5))",
|
||||||
|
},
|
||||||
|
others: {
|
||||||
|
label: "其他",
|
||||||
|
color: "hsl(var(--chart-6))",
|
||||||
|
},
|
||||||
|
count: {
|
||||||
|
label: "新增文件数",
|
||||||
|
color: "hsl(var(--primary))",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
label: "新增大小",
|
||||||
|
color: "hsl(var(--chart-2))",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
/* ─── 文件管理主组件 ────────────────────────────────────── */
|
/* ─── 文件管理主组件 ────────────────────────────────────── */
|
||||||
|
|
||||||
@@ -216,38 +247,28 @@ export function FilesMain() {
|
|||||||
if (!stats?.categories) return []
|
if (!stats?.categories) return []
|
||||||
return stats.categories
|
return stats.categories
|
||||||
.filter((c) => c.count > 0)
|
.filter((c) => c.count > 0)
|
||||||
.map((c) => ({ name: c.name, value: c.count }))
|
.map((c) => {
|
||||||
|
const key = categoryMap[c.name] || "others"
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
value: c.count,
|
||||||
|
fill: `var(--color-${key})`,
|
||||||
|
}
|
||||||
|
})
|
||||||
}, [stats?.categories])
|
}, [stats?.categories])
|
||||||
|
|
||||||
const categorySizeData = React.useMemo(() => {
|
const categorySizeData = React.useMemo(() => {
|
||||||
if (!stats?.categories) return []
|
if (!stats?.categories) return []
|
||||||
return stats.categories
|
return stats.categories
|
||||||
.filter((c) => c.size > 0)
|
.filter((c) => c.size > 0)
|
||||||
.map((c) => ({ name: c.name, value: c.size }))
|
.map((c) => {
|
||||||
}, [stats?.categories])
|
const key = categoryMap[c.name] || "others"
|
||||||
|
return {
|
||||||
// 计算最大占用与最大数量文件类型
|
name: key,
|
||||||
const maxStats = React.useMemo(() => {
|
value: c.size,
|
||||||
if (!stats?.categories || stats.categories.length === 0) {
|
fill: `var(--color-${key})`,
|
||||||
return { maxCountName: "无", maxCount: 0, maxSizeName: "无", 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
|
|
||||||
}
|
|
||||||
if (cat.size > maxSize) {
|
|
||||||
maxSize = cat.size
|
|
||||||
maxSizeName = cat.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return { maxCountName, maxCount, maxSizeName, maxSize }
|
|
||||||
}, [stats?.categories])
|
}, [stats?.categories])
|
||||||
|
|
||||||
// 近 7 天趋势总新增统计
|
// 近 7 天趋势总新增统计
|
||||||
@@ -271,14 +292,18 @@ export function FilesMain() {
|
|||||||
>
|
>
|
||||||
{/* 顶部标题区 */}
|
{/* 顶部标题区 */}
|
||||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b pb-5">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b pb-5">
|
||||||
<div>
|
<div className="flex items-center gap-3 pb-2">
|
||||||
<h1 className="text-xl font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-muted-foreground bg-clip-text text-transparent">
|
<div className="p-2 bg-primary/10 rounded-lg text-primary">
|
||||||
文件管理
|
<FolderOpen className="size-5" />
|
||||||
</h1>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<div>
|
||||||
管理您上传的所有文件,支持下载、数据统计与批量操作
|
<h1 className="text-2xl font-bold tracking-tight">文件管理</h1>
|
||||||
</p>
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
管理您上传的所有文件,支持下载、数据统计与批量操作
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full space-y-6">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full space-y-6">
|
||||||
@@ -378,20 +403,16 @@ export function FilesMain() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pl-2 pr-4 pt-2">
|
<CardContent className="pl-2 pr-4 pt-2">
|
||||||
<div className="h-[280px] w-full">
|
<div className="h-[280px] w-full">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ChartContainer config={statsChartConfig} className="w-full h-full">
|
||||||
<AreaChart data={trendData} margin={{ top: 10, right: 10, left: 10, bottom: 0 }}>
|
<AreaChart data={trendData} margin={{ top: 10, right: 10, left: 10, bottom: 0 }}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="colorTrend" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop
|
<stop offset="5%" stopColor="var(--color-count)" stopOpacity={0.3} />
|
||||||
offset="5%"
|
<stop offset="95%" stopColor="var(--color-count)" stopOpacity={0.01} />
|
||||||
stopColor={trendMetric === "count" ? "#6366f1" : "#10b981"}
|
</linearGradient>
|
||||||
stopOpacity={0.3}
|
<linearGradient id="colorSize" x1="0" y1="0" x2="0" y2="1">
|
||||||
/>
|
<stop offset="5%" stopColor="var(--color-size)" stopOpacity={0.3} />
|
||||||
<stop
|
<stop offset="95%" stopColor="var(--color-size)" stopOpacity={0.01} />
|
||||||
offset="95%"
|
|
||||||
stopColor={trendMetric === "count" ? "#6366f1" : "#10b981"}
|
|
||||||
stopOpacity={0.01}
|
|
||||||
/>
|
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border)/40)" />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border)/40)" />
|
||||||
@@ -409,38 +430,42 @@ export function FilesMain() {
|
|||||||
style={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
|
style={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
|
||||||
tickFormatter={(v) => (trendMetric === "size" ? formatFileSize(v) : v.toString())}
|
tickFormatter={(v) => (trendMetric === "size" ? formatFileSize(v) : v.toString())}
|
||||||
/>
|
/>
|
||||||
<RechartsTooltip
|
<ChartTooltip
|
||||||
content={({ active, payload }) => {
|
cursor={false}
|
||||||
if (active && payload && payload.length) {
|
content={
|
||||||
const data = payload[0]
|
<ChartTooltipContent
|
||||||
return (
|
hideLabel
|
||||||
<div className="bg-background/95 backdrop-blur-sm border border-border/50 rounded-lg p-2.5 shadow-xl text-xs space-y-1">
|
formatter={(value) => {
|
||||||
<p className="text-[10px] text-muted-foreground font-medium">{data.payload.date}</p>
|
const label = trendMetric === "count" ? "新增文件数" : "新增大小"
|
||||||
<div className="flex items-center gap-2">
|
const formattedValue = trendMetric === "size" ? formatFileSize(Number(value)) : `${value} 个`
|
||||||
<span className="size-2 rounded-full" style={{ backgroundColor: data.color }} />
|
return (
|
||||||
<span className="font-medium text-foreground">
|
<>
|
||||||
{trendMetric === "count" ? "新增文件数: " : "新增大小: "}
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="font-mono font-semibold text-foreground/90">
|
<span
|
||||||
{trendMetric === "count" ? `${data.value} 个` : formatFileSize(data.value as number)}
|
className="size-2 rounded-full animate-pulse"
|
||||||
</span>
|
style={{ backgroundColor: trendMetric === "count" ? "var(--color-count)" : "var(--color-size)" }}
|
||||||
|
/>
|
||||||
|
<span className="text-muted-foreground">{label}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-foreground font-mono font-medium tabular-nums ml-auto">
|
||||||
|
{formattedValue}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</>
|
||||||
</div>
|
)
|
||||||
)
|
}}
|
||||||
}
|
/>
|
||||||
return null
|
}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Area
|
<Area
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
stroke={trendMetric === "count" ? "#6366f1" : "#10b981"}
|
stroke={trendMetric === "count" ? "var(--color-count)" : "var(--color-size)"}
|
||||||
strokeWidth={2.5}
|
strokeWidth={2.5}
|
||||||
fillOpacity={1}
|
fillOpacity={1}
|
||||||
fill="url(#colorTrend)"
|
fill={trendMetric === "count" ? "url(#colorCount)" : "url(#colorSize)"}
|
||||||
/>
|
/>
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ChartContainer>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -458,7 +483,7 @@ export function FilesMain() {
|
|||||||
<div className="h-[240px] flex items-center justify-center text-xs text-muted-foreground">暂无分类数据</div>
|
<div className="h-[240px] flex items-center justify-center text-xs text-muted-foreground">暂无分类数据</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-[240px] w-full">
|
<div className="h-[240px] w-full">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ChartContainer config={statsChartConfig} className="w-full h-full">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
data={categoryCountData}
|
data={categoryCountData}
|
||||||
@@ -470,55 +495,41 @@ export function FilesMain() {
|
|||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
{categoryCountData.map((entry, index) => (
|
{categoryCountData.map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={PIE_COLORS[index % PIE_COLORS.length]} />
|
<Cell key={`cell-${index}`} fill={entry.fill} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<RechartsTooltip
|
<ChartTooltip
|
||||||
content={({ active, payload }) => {
|
cursor={false}
|
||||||
if (active && payload && payload.length) {
|
content={
|
||||||
const data = payload[0].payload
|
<ChartTooltipContent
|
||||||
return (
|
hideLabel
|
||||||
<div className="bg-background/95 backdrop-blur-sm border border-border/50 rounded-lg p-2 shadow-xl text-xs font-medium space-y-1">
|
formatter={(value, name) => {
|
||||||
<div className="flex items-center gap-1.5">
|
const configObj = statsChartConfig[name as keyof typeof statsChartConfig]
|
||||||
<span className="size-2 rounded-full" style={{ backgroundColor: payload[0].color }} />
|
const label = configObj?.label || name
|
||||||
<span className="text-muted-foreground">{data.name}</span>
|
const color = configObj?.color || "hsl(var(--muted))"
|
||||||
</div>
|
return (
|
||||||
<div className="font-mono text-foreground font-semibold pl-3">
|
<>
|
||||||
{data.value} 个
|
<div className="flex items-center gap-1.5">
|
||||||
</div>
|
<span className="size-2 rounded-full" style={{ backgroundColor: color }} />
|
||||||
</div>
|
<span className="text-muted-foreground">{label}</span>
|
||||||
)
|
</div>
|
||||||
}
|
<span className="text-foreground font-mono font-medium tabular-nums ml-auto">
|
||||||
return null
|
{value} 个
|
||||||
}}
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<ChartLegend
|
||||||
verticalAlign="bottom"
|
content={<ChartLegendContent nameKey="name" />}
|
||||||
height={36}
|
className="flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] pt-4"
|
||||||
content={({ payload }) => {
|
|
||||||
if (!payload) return null
|
|
||||||
return (
|
|
||||||
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] text-muted-foreground pt-4">
|
|
||||||
{payload.map((entry: { value: string; color?: string }) => (
|
|
||||||
<div key={entry.value} className="flex items-center gap-1.5">
|
|
||||||
<span className="size-2 rounded-full" style={{ backgroundColor: entry.color }} />
|
|
||||||
<span>{entry.value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ChartContainer>
|
||||||
</div>
|
</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> 个文件。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -533,7 +544,7 @@ export function FilesMain() {
|
|||||||
<div className="h-[240px] flex items-center justify-center text-xs text-muted-foreground">暂无容量数据</div>
|
<div className="h-[240px] flex items-center justify-center text-xs text-muted-foreground">暂无容量数据</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-[240px] w-full">
|
<div className="h-[240px] w-full">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ChartContainer config={statsChartConfig} className="w-full h-full">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
data={categorySizeData}
|
data={categorySizeData}
|
||||||
@@ -545,55 +556,41 @@ export function FilesMain() {
|
|||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
{categorySizeData.map((entry, index) => (
|
{categorySizeData.map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={PIE_COLORS[index % PIE_COLORS.length]} />
|
<Cell key={`cell-${index}`} fill={entry.fill} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<RechartsTooltip
|
<ChartTooltip
|
||||||
content={({ active, payload }) => {
|
cursor={false}
|
||||||
if (active && payload && payload.length) {
|
content={
|
||||||
const data = payload[0].payload
|
<ChartTooltipContent
|
||||||
return (
|
hideLabel
|
||||||
<div className="bg-background/95 backdrop-blur-sm border border-border/50 rounded-lg p-2 shadow-xl text-xs font-medium space-y-1">
|
formatter={(value, name) => {
|
||||||
<div className="flex items-center gap-1.5">
|
const configObj = statsChartConfig[name as keyof typeof statsChartConfig]
|
||||||
<span className="size-2 rounded-full" style={{ backgroundColor: payload[0].color }} />
|
const label = configObj?.label || name
|
||||||
<span className="text-muted-foreground">{data.name}</span>
|
const color = configObj?.color || "hsl(var(--muted))"
|
||||||
</div>
|
return (
|
||||||
<div className="font-mono text-foreground font-semibold pl-3">
|
<>
|
||||||
{formatFileSize(data.value)}
|
<div className="flex items-center gap-1.5">
|
||||||
</div>
|
<span className="size-2 rounded-full" style={{ backgroundColor: color }} />
|
||||||
</div>
|
<span className="text-muted-foreground">{label}</span>
|
||||||
)
|
</div>
|
||||||
}
|
<span className="text-foreground font-mono font-medium tabular-nums ml-auto">
|
||||||
return null
|
{formatFileSize(Number(value))}
|
||||||
}}
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<ChartLegend
|
||||||
verticalAlign="bottom"
|
content={<ChartLegendContent nameKey="name" />}
|
||||||
height={36}
|
className="flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] pt-4"
|
||||||
content={({ payload }) => {
|
|
||||||
if (!payload) return null
|
|
||||||
return (
|
|
||||||
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-[11px] text-muted-foreground pt-4">
|
|
||||||
{payload.map((entry: { value: string; color?: string }) => (
|
|
||||||
<div key={entry.value} className="flex items-center gap-1.5">
|
|
||||||
<span className="size-2 rounded-full" style={{ backgroundColor: entry.color }} />
|
|
||||||
<span>{entry.value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ChartContainer>
|
||||||
</div>
|
</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> 存储容量。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user