✨ feat(dashboard): Optimization
This commit is contained in:
parent
2992824353
commit
5b3f4b493f
@ -1,8 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { formatBytes } from '@repo/ui/utils';
|
||||
import { Badge } from '@shadcn/ui/badge';
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@shadcn/ui/card';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { formatBytes, unitConversion } from '@repo/ui/utils';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@shadcn/ui/card';
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
@ -18,13 +25,17 @@ import {
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Label,
|
||||
LabelList,
|
||||
Pie,
|
||||
PieChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from '@shadcn/ui/lib/recharts';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@shadcn/ui/select';
|
||||
import { Separator } from '@shadcn/ui/separator';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@shadcn/ui/table';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@shadcn/ui/tabs';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
|
||||
const UserStatisticsConfig = {
|
||||
register: {
|
||||
@ -52,68 +63,140 @@ const IncomeStatisticsConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
// Sample data - replace with actual data
|
||||
const trafficData = {
|
||||
nodes: {
|
||||
today: [
|
||||
{ name: 'Node 1', traffic: 1000, type: 'Trojan', address: '127.0.0.1:443' },
|
||||
{ name: 'Node 2', traffic: 800, type: 'Trojan', address: '127.0.0.1:444' },
|
||||
{ name: 'Node 3', traffic: 600, type: 'Trojan', address: '127.0.0.1:445' },
|
||||
{ name: 'Node 4', traffic: 400, type: 'Trojan', address: '127.0.0.1:446' },
|
||||
{ name: 'Node 5', traffic: 200, type: 'Trojan', address: '127.0.0.1:447' },
|
||||
{ name: 'Node 6', traffic: 1000, type: 'Trojan', address: '127.0.0.1:443' },
|
||||
{ name: 'Node 7', traffic: 800, type: 'Trojan', address: '127.0.0.1:444' },
|
||||
{ name: 'Node 8', traffic: 600, type: 'Trojan', address: '127.0.0.1:445' },
|
||||
{ name: 'Node 9', traffic: 400, type: 'Trojan', address: '127.0.0.1:446' },
|
||||
{ name: 'Node 10', traffic: 200, type: 'Trojan', address: '127.0.0.1:447' },
|
||||
],
|
||||
yesterday: [
|
||||
{ name: 'Node 1', traffic: 900, type: 'Trojan', address: '127.0.0.1:443' },
|
||||
{ name: 'Node 2', traffic: 750, type: 'Trojan', address: '127.0.0.1:444' },
|
||||
{ name: 'Node 3', traffic: 550, type: 'Trojan', address: '127.0.0.1:445' },
|
||||
{ name: 'Node 4', traffic: 350, type: 'Trojan', address: '127.0.0.1:446' },
|
||||
{ name: 'Node 5', traffic: 150, type: 'Trojan', address: '127.0.0.1:447' },
|
||||
],
|
||||
},
|
||||
users: {
|
||||
today: [
|
||||
{ name: 'olivia.martin@email.com', traffic: 100, email: 'olivia.martin@email.com' },
|
||||
{ name: 'jackson.lee@email.com', traffic: 90, email: 'jackson.lee@email.com' },
|
||||
{ name: 'isabella.nguyen@email.com', traffic: 80, email: 'isabella.nguyen@email.com' },
|
||||
{ name: 'william.chen@email.com', traffic: 70, email: 'william.chen@email.com' },
|
||||
{ name: 'sophia.rodriguez@email.com', traffic: 60, email: 'sophia.rodriguez@email.com' },
|
||||
{ name: 'olivia.martin@email.com', traffic: 100, email: 'olivia.martin@email.com' },
|
||||
{ name: 'jackson.lee@email.com', traffic: 90, email: 'jackson.lee@email.com' },
|
||||
{ name: 'isabella.nguyen@email.com', traffic: 80, email: 'isabella.nguyen@email.com' },
|
||||
{ name: 'william.chen@email.com', traffic: 70, email: 'william.chen@email.com' },
|
||||
{ name: 'sophia.rodriguez@email.com', traffic: 60, email: 'sophia.rodriguez@email.com' },
|
||||
],
|
||||
yesterday: [
|
||||
{ name: 'olivia.martin@email.com', traffic: 95, email: 'olivia.martin@email.com' },
|
||||
{ name: 'jackson.lee@email.com', traffic: 85, email: 'jackson.lee@email.com' },
|
||||
{ name: 'isabella.nguyen@email.com', traffic: 75, email: 'isabella.nguyen@email.com' },
|
||||
{ name: 'william.chen@email.com', traffic: 65, email: 'william.chen@email.com' },
|
||||
{ name: 'sophia.rodriguez@email.com', traffic: 55, email: 'sophia.rodriguez@email.com' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function Dashboard() {
|
||||
const locale = useLocale();
|
||||
return (
|
||||
<div className='flex flex-1 flex-col gap-4'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>统计</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='grid grid-cols-2 gap-4 lg:grid-cols-4'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>在线IP数</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>666</div>
|
||||
</div>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>在线节点数</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>99</div>
|
||||
</div>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>离线节点数</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>1</div>
|
||||
</div>
|
||||
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>待处理工单</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>1</div>
|
||||
</div>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>今日上传流量</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>
|
||||
{formatBytes(99999999999999)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>今日下载流量</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>
|
||||
{formatBytes(99999999999999)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>总上传流量</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>
|
||||
{formatBytes(99999999999999)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-sm'>总下载流量</div>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>
|
||||
{formatBytes(99999999999999)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className='grid gap-4 lg:grid-cols-3'>
|
||||
<Card>
|
||||
<CardHeader className='items-center'>
|
||||
<CardTitle>今日收入统计</CardTitle>
|
||||
const [dataType, setDataType] = useState<string | 'nodes' | 'users'>('nodes');
|
||||
const [timeFrame, setTimeFrame] = useState<string | 'today' | 'yesterday'>('today');
|
||||
|
||||
const currentData = trafficData[dataType][timeFrame];
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 flex-col gap-3'>
|
||||
<div className='grid grid-cols-2 gap-3 md:grid-cols-4 lg:grid-cols-8'>
|
||||
{[
|
||||
{
|
||||
title: '在线IP数',
|
||||
value: '666',
|
||||
icon: 'uil:network-wired',
|
||||
onClick: () => console.log('在线IP数 clicked'),
|
||||
},
|
||||
{
|
||||
title: '在线节点数',
|
||||
value: '99',
|
||||
icon: 'uil:server-network',
|
||||
onClick: () => console.log('在线节点数 clicked'),
|
||||
},
|
||||
{
|
||||
title: '离线节点数',
|
||||
value: '1',
|
||||
icon: 'uil:server-network-alt',
|
||||
onClick: () => console.log('离线节点数 clicked'),
|
||||
},
|
||||
{
|
||||
title: '待处理工单',
|
||||
value: '1',
|
||||
icon: 'uil:clipboard-notes',
|
||||
onClick: () => console.log('待处理工单 clicked'),
|
||||
},
|
||||
{
|
||||
title: '今日上传流量',
|
||||
value: formatBytes(99999999999999),
|
||||
icon: 'uil:arrow-up',
|
||||
onClick: () => console.log('今日上传流量 clicked'),
|
||||
},
|
||||
{
|
||||
title: '今日下载流量',
|
||||
value: formatBytes(99999999999999),
|
||||
icon: 'uil:arrow-down',
|
||||
onClick: () => console.log('今日下载流量 clicked'),
|
||||
},
|
||||
{
|
||||
title: '总上传流量',
|
||||
value: formatBytes(99999999999999),
|
||||
icon: 'uil:cloud-upload',
|
||||
onClick: () => console.log('总上传流量 clicked'),
|
||||
},
|
||||
{
|
||||
title: '总下载流量',
|
||||
value: formatBytes(99999999999999),
|
||||
icon: 'uil:cloud-download',
|
||||
onClick: () => console.log('总下载流量 clicked'),
|
||||
},
|
||||
].map((item, index) => (
|
||||
<Card key={index} onClick={item.onClick} className='cursor-pointer'>
|
||||
<CardHeader className='flex flex-row items-center justify-between'>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
<CardDescription>
|
||||
<Icon icon={item.icon} className='text-2xl' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer
|
||||
config={IncomeStatisticsConfig}
|
||||
className='mx-auto aspect-square max-h-[213px]'
|
||||
>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>{item.value}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className='grid gap-3 md:grid-cols-2 lg:grid-cols-3'>
|
||||
<Tabs defaultValue='today'>
|
||||
<Card>
|
||||
<CardHeader className='flex !flex-row items-center justify-between'>
|
||||
<CardTitle>收入统计</CardTitle>
|
||||
<TabsList>
|
||||
<TabsTrigger value='today'>今日</TabsTrigger>
|
||||
<TabsTrigger value='month'>本月</TabsTrigger>
|
||||
<TabsTrigger value='total'>总计</TabsTrigger>
|
||||
</TabsList>
|
||||
</CardHeader>
|
||||
<TabsContent value='today'>
|
||||
<CardContent>
|
||||
<ChartContainer config={IncomeStatisticsConfig} className='mx-auto max-h-80'>
|
||||
<PieChart>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
@ -161,7 +244,7 @@ export default function Dashboard() {
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className='flex flex-row border-t p-4'>
|
||||
<CardFooter className='flex h-20 flex-row border-t p-4'>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-xs'>总收入</div>
|
||||
@ -183,14 +266,10 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>本月收入统计</CardTitle>
|
||||
</CardHeader>
|
||||
</TabsContent>
|
||||
<TabsContent value='month'>
|
||||
<CardContent>
|
||||
<ChartContainer config={IncomeStatisticsConfig}>
|
||||
<ChartContainer config={IncomeStatisticsConfig} className='max-h-80 w-full'>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={[
|
||||
@ -222,7 +301,7 @@ export default function Dashboard() {
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className='flex flex-row border-t p-4'>
|
||||
<CardFooter className='flex h-20 flex-row border-t p-4'>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-xs'>总收入</div>
|
||||
@ -244,14 +323,10 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>收入统计</CardTitle>
|
||||
</CardHeader>
|
||||
</TabsContent>
|
||||
<TabsContent value='total'>
|
||||
<CardContent>
|
||||
<ChartContainer config={IncomeStatisticsConfig}>
|
||||
<ChartContainer config={IncomeStatisticsConfig} className='max-h-80 w-full'>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={[
|
||||
@ -279,7 +354,10 @@ export default function Dashboard() {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator='dot' />} />
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator='dot' />}
|
||||
/>
|
||||
<Area
|
||||
dataKey='new_purchase'
|
||||
type='natural'
|
||||
@ -300,7 +378,7 @@ export default function Dashboard() {
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className='flex flex-row border-t p-4'>
|
||||
<CardFooter className='flex h-20 flex-row border-t p-4'>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-xs'>总收入</div>
|
||||
@ -308,17 +386,22 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</TabsContent>
|
||||
</Card>
|
||||
|
||||
</Tabs>
|
||||
<Tabs defaultValue='today'>
|
||||
<Card>
|
||||
<CardHeader className='items-center'>
|
||||
<CardTitle>今日用户统计</CardTitle>
|
||||
<CardHeader className='flex !flex-row items-center justify-between'>
|
||||
<CardTitle>用户统计</CardTitle>
|
||||
<TabsList>
|
||||
<TabsTrigger value='today'>今日</TabsTrigger>
|
||||
<TabsTrigger value='month'>本月</TabsTrigger>
|
||||
<TabsTrigger value='total'>总计</TabsTrigger>
|
||||
</TabsList>
|
||||
</CardHeader>
|
||||
<TabsContent value='today'>
|
||||
<CardContent>
|
||||
<ChartContainer
|
||||
config={UserStatisticsConfig}
|
||||
className='mx-auto aspect-square max-h-[213px]'
|
||||
>
|
||||
<ChartContainer config={UserStatisticsConfig} className='mx-auto max-h-80'>
|
||||
<PieChart>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
@ -371,7 +454,7 @@ export default function Dashboard() {
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className='flex flex-row border-t p-4'>
|
||||
<CardFooter className='flex h-20 flex-row border-t p-4'>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
@ -395,14 +478,10 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>本月用户统计</CardTitle>
|
||||
</CardHeader>
|
||||
</TabsContent>
|
||||
<TabsContent value='month'>
|
||||
<CardContent>
|
||||
<ChartContainer config={UserStatisticsConfig}>
|
||||
<ChartContainer config={UserStatisticsConfig} className='max-h-80 w-full'>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={[
|
||||
@ -435,7 +514,7 @@ export default function Dashboard() {
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className='flex flex-row border-t p-4'>
|
||||
<CardFooter className='flex h-20 flex-row border-t p-4'>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
@ -459,14 +538,10 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>用户统计</CardTitle>
|
||||
</CardHeader>
|
||||
</TabsContent>
|
||||
<TabsContent value='total'>
|
||||
<CardContent>
|
||||
<ChartContainer config={UserStatisticsConfig}>
|
||||
<ChartContainer config={UserStatisticsConfig} className='max-h-80 w-full'>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={[
|
||||
@ -494,7 +569,10 @@ export default function Dashboard() {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator='dot' />} />
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator='dot' />}
|
||||
/>
|
||||
<Area
|
||||
dataKey='register'
|
||||
type='natural'
|
||||
@ -523,7 +601,7 @@ export default function Dashboard() {
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className='flex flex-row border-t p-4'>
|
||||
<CardFooter className='flex h-20 flex-row border-t p-4'>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='grid flex-1 auto-rows-min gap-0.5'>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
@ -533,60 +611,146 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</TabsContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className='grid gap-4 lg:grid-cols-2 xl:grid-cols-3'>
|
||||
<Card className='xl:col-span-2'>
|
||||
<CardHeader>
|
||||
<CardTitle>今日节点流量排行</CardTitle>
|
||||
</Tabs>
|
||||
<Card>
|
||||
<CardHeader className='flex !flex-row items-center justify-between'>
|
||||
<CardTitle>流量排行</CardTitle>
|
||||
<Tabs value={timeFrame} onValueChange={setTimeFrame}>
|
||||
<TabsList>
|
||||
<TabsTrigger value='today'>今日</TabsTrigger>
|
||||
<TabsTrigger value='yesterday'>昨日</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>节点</TableHead>
|
||||
<TableHead className='text-right'>流量</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{new Array(10)
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((item, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>
|
||||
<Badge>Trojan</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className='font-medium'>节点名称</div>
|
||||
<div className='text-muted-foreground hidden text-sm md:inline'>
|
||||
127.0.0.1:443
|
||||
<div className='mb-6 flex items-center justify-between'>
|
||||
<h4 className='font-semibold'>{dataType === 'nodes' ? '节点流量' : '用户流量'}</h4>
|
||||
<Select onValueChange={setDataType} defaultValue='nodes'>
|
||||
<SelectTrigger className='w-28'>
|
||||
<SelectValue placeholder='选择类型' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='nodes'>节点</SelectItem>
|
||||
<SelectItem value='users'>用户</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>1,000 GB</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<ChartContainer
|
||||
config={{
|
||||
traffic: {
|
||||
label: '流量',
|
||||
color: 'hsl(var(--primary))',
|
||||
},
|
||||
type: {
|
||||
label: '类型',
|
||||
color: 'hsl(var(--muted))',
|
||||
},
|
||||
email: {
|
||||
label: '邮箱',
|
||||
color: 'hsl(var(--muted))',
|
||||
},
|
||||
label: {
|
||||
color: 'hsl(var(--foreground))',
|
||||
},
|
||||
}}
|
||||
className='max-h-80'
|
||||
>
|
||||
<BarChart data={currentData} layout='vertical' height={400}>
|
||||
<CartesianGrid strokeDasharray='3 3' />
|
||||
<XAxis
|
||||
type='number'
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => formatBytes(unitConversion('gbToBytes', value) || 0)}
|
||||
/>
|
||||
<YAxis
|
||||
type='category'
|
||||
dataKey='name'
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
interval={0}
|
||||
tickMargin={0}
|
||||
width={15}
|
||||
tickFormatter={(value, index) => String(index + 1)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
label={false}
|
||||
labelFormatter={(label) =>
|
||||
dataType === 'nodes' ? `节点: ${label}` : `用户: ${label}`
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar dataKey='traffic' fill='hsl(var(--primary))' radius={[0, 4, 4, 0]}>
|
||||
<LabelList
|
||||
dataKey='name'
|
||||
position='insideLeft'
|
||||
offset={8}
|
||||
className='fill-[--color-label]'
|
||||
fontSize={12}
|
||||
/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
</div>
|
||||
<div className='grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4'>
|
||||
{[
|
||||
{
|
||||
title: '广告位1',
|
||||
value: '广告内容1',
|
||||
onClick: () => console.log('广告位1 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位2',
|
||||
value: '广告内容2',
|
||||
onClick: () => console.log('广告位2 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位3',
|
||||
value: '广告内容3',
|
||||
onClick: () => console.log('广告位3 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位4',
|
||||
value: '广告内容4',
|
||||
onClick: () => console.log('广告位4 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位5',
|
||||
value: '广告内容5',
|
||||
onClick: () => console.log('广告位5 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位6',
|
||||
value: '广告内容6',
|
||||
onClick: () => console.log('广告位6 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位7',
|
||||
value: '广告内容7',
|
||||
onClick: () => console.log('广告位7 clicked'),
|
||||
},
|
||||
{
|
||||
title: '广告位8',
|
||||
value: '广告内容8',
|
||||
onClick: () => console.log('广告位8 clicked'),
|
||||
},
|
||||
].map((item, index) => (
|
||||
<Card key={index} onClick={item.onClick} className='cursor-pointer'>
|
||||
<CardHeader>
|
||||
<CardTitle>今日用户流量排行</CardTitle>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='grid gap-4'>
|
||||
{new Array(15)
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((item, index) => (
|
||||
<div className='flex items-center gap-4' key={index}>
|
||||
<div className='text-sm font-medium leading-none'>olivia.martin@email.com</div>
|
||||
<div className='ml-auto font-medium'>100 GB</div>
|
||||
</div>
|
||||
))}
|
||||
<CardContent>
|
||||
<div className='text-xl font-bold tabular-nums leading-none'>{item.value}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user