init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
139
landingpage/components/StatsSection.tsx
Normal file
139
landingpage/components/StatsSection.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { Card, CardBody, Avatar, AvatarGroup } from '@heroui/react';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
import { useTheme } from '@/contexts/ThemeContext';
|
||||
import { useCountUp } from '@/hooks/useCountUp';
|
||||
|
||||
// 格式化数字 - 确保始终显示正确的单位
|
||||
function formatNumber(num: number, prefix: string = '', suffix: string = '', forceUnit: 'M' | 'K' | '' = '') {
|
||||
if (forceUnit === 'M') {
|
||||
return `${prefix}${Math.floor(num)}${suffix}`;
|
||||
} else if (forceUnit === 'K') {
|
||||
return `${prefix}${Math.floor(num)}${suffix}`;
|
||||
} else if (num >= 1000000) {
|
||||
return `${prefix}${Math.floor(num / 1000000)}M${suffix}`;
|
||||
} else if (num >= 1000) {
|
||||
return `${prefix}${Math.floor(num / 1000)}K${suffix}`;
|
||||
}
|
||||
return `${prefix}${num.toLocaleString()}${suffix}`;
|
||||
}
|
||||
|
||||
export default function StatsSection() {
|
||||
const { t } = useLanguage();
|
||||
const { theme } = useTheme();
|
||||
const tvl = useCountUp(485, 1500, 0.80);
|
||||
const apy = useCountUp(515, 1500, 0.85);
|
||||
const yield_ = useCountUp(45, 1500, 0.75);
|
||||
const users = useCountUp(23928, 1800, 0.80);
|
||||
|
||||
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
return (
|
||||
<section
|
||||
className="w-full overflow-x-clip border-b transition-colors bg-bg-base border-border-normal flex-shrink-0 flex justify-center"
|
||||
style={{ paddingLeft: 'clamp(16px, 4vw, 80px)', paddingRight: 'clamp(16px, 4vw, 80px)' }}
|
||||
>
|
||||
<div className="w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] grid grid-cols-2 md:grid-cols-4">
|
||||
|
||||
{/* TVL */}
|
||||
<Card
|
||||
className="rounded-none border-r border-b md:border-b-0 transition-colors bg-bg-base border-border-normal"
|
||||
shadow="none"
|
||||
>
|
||||
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:px-0">
|
||||
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
|
||||
{t('stats.tvl')}
|
||||
</p>
|
||||
<div
|
||||
ref={tvl.elementRef}
|
||||
className="font-extrabold font-domine transition-colors text-text-primary"
|
||||
style={{ fontSize: 'clamp(20px, 2.5vw, 48px)', lineHeight: '110%', letterSpacing: '-0.01em' }}
|
||||
>
|
||||
{formatNumber(tvl.count, '$', 'M+', 'M')}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* APY */}
|
||||
<Card
|
||||
className={`rounded-none border-b md:border-b-0 md:border-r transition-colors ${
|
||||
isDark ? 'bg-[#0a0a0a] border-[#27272a]' : 'bg-white border-[#e5e7eb]'
|
||||
}`}
|
||||
shadow="none"
|
||||
>
|
||||
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:px-8">
|
||||
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
|
||||
{t('stats.apy')}
|
||||
</p>
|
||||
<div
|
||||
ref={apy.elementRef}
|
||||
className="font-extrabold font-domine transition-colors"
|
||||
style={{ fontSize: 'clamp(20px, 2.5vw, 48px)', lineHeight: '110%', letterSpacing: '-0.01em', color: '#059669' }}
|
||||
>
|
||||
{(apy.count / 100).toFixed(2)}%
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* Yield */}
|
||||
<Card
|
||||
className={`rounded-none border-r transition-colors ${
|
||||
isDark ? 'bg-[#0a0a0a] border-[#27272a]' : 'bg-white border-[#e5e7eb]'
|
||||
}`}
|
||||
shadow="none"
|
||||
>
|
||||
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:px-8">
|
||||
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
|
||||
{t('stats.yield')}
|
||||
</p>
|
||||
<div
|
||||
ref={yield_.elementRef}
|
||||
className="font-extrabold font-domine transition-colors text-text-primary"
|
||||
style={{ fontSize: 'clamp(20px, 2.5vw, 48px)', lineHeight: '110%', letterSpacing: '-0.01em' }}
|
||||
>
|
||||
{formatNumber(yield_.count, '$', 'M', 'M')}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* Users */}
|
||||
<Card
|
||||
className="rounded-none transition-colors bg-bg-base overflow-hidden"
|
||||
shadow="none"
|
||||
>
|
||||
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:pl-8 overflow-hidden">
|
||||
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
|
||||
{t('stats.users')}
|
||||
</p>
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<div
|
||||
ref={users.elementRef}
|
||||
className="text-xl md:text-2xl xl:text-4xl font-extrabold font-domine transition-colors text-text-primary"
|
||||
style={{ lineHeight: '110%', letterSpacing: '-0.01em' }}
|
||||
>
|
||||
{users.count.toLocaleString()}+
|
||||
</div>
|
||||
<motion.div
|
||||
className="hidden lg:block"
|
||||
initial={{ y: '3rem', opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.7, ease: 'easeOut', delay: 0.5 }}
|
||||
>
|
||||
<AvatarGroup max={4}>
|
||||
<Avatar src="/image-230.png" size="sm" />
|
||||
<Avatar src="/image-240.png" size="sm" />
|
||||
<Avatar src="/image-250.png" size="sm" />
|
||||
<Avatar src="/image-251.png" size="sm" />
|
||||
</AvatarGroup>
|
||||
</motion.div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user