()
| 676 | } |
| 677 | |
| 678 | function StatsBlock() { |
| 679 | const dark = useTheme().palette.mode === 'dark'; |
| 680 | const [stats, setStats] = useState({programs: null, univs: null, records: null, posts: null}); |
| 681 | useEffect(() => { |
| 682 | let alive = true; |
| 683 | (async () => { |
| 684 | try { |
| 685 | const p = await getPrograms(); |
| 686 | if (alive) setStats((s) => ({...s, programs: Object.values(p).flat().length, univs: Object.keys(p).length})); |
| 687 | } catch { /* not signed in / offline — leave as null */ } |
| 688 | try { |
| 689 | const r = await getRecords(); |
| 690 | if (alive) setStats((s) => ({...s, records: Object.keys(r ?? {}).length})); |
| 691 | } catch { /* ignore */ } |
| 692 | try { |
| 693 | const posts = await getPosts(); |
| 694 | if (alive) setStats((s) => ({...s, posts: posts.length})); |
| 695 | } catch { /* ignore */ } |
| 696 | })(); |
| 697 | return () => { |
| 698 | alive = false; |
| 699 | }; |
| 700 | }, []); |
| 701 | |
| 702 | const tiles = [ |
| 703 | {icon: <TravelExploreOutlined/>, value: stats.programs, label: '收录项目'}, |
| 704 | {icon: <SchoolOutlined/>, value: stats.univs, label: '覆盖高校'}, |
| 705 | {icon: <TableChartOutlined/>, value: stats.records, label: '申请记录'}, |
| 706 | {icon: <MenuBookOutlined/>, value: stats.posts, label: '经验分享'}, |
| 707 | ]; |
| 708 | return ( |
| 709 | <Box sx={{width: '100%', maxWidth: 920}}> |
| 710 | <SectionHeading title='社区数据' subtitle='由上科大学子共同积累与维护'/> |
| 711 | <Box sx={{display: 'grid', gridTemplateColumns: {xs: '1fr 1fr', md: 'repeat(4, 1fr)'}, gap: {xs: 2, md: 2.5}}}> |
| 712 | {tiles.map((t) => ( |
| 713 | <Box key={t.label} className='GlassCard' |
| 714 | sx={glassSx(dark, {p: {xs: '1.25rem', md: '1.75rem 1rem'}, textAlign: 'center'})}> |
| 715 | <Box sx={{color: 'primary.main', display: 'flex', justifyContent: 'center', mb: 1}}>{t.icon}</Box> |
| 716 | <Typography sx={{fontWeight: 800, lineHeight: 1.1, fontSize: {xs: '2rem', md: '2.6rem'}, display: 'inline-block', ...gradientTextSx(dark)}}> |
| 717 | <StatNumber value={t.value}/> |
| 718 | </Typography> |
| 719 | <Typography variant='body2' sx={{color: 'text.secondary', mt: 0.5}}>{t.label}</Typography> |
| 720 | </Box> |
| 721 | ))} |
| 722 | </Box> |
| 723 | </Box> |
| 724 | ); |
| 725 | } |
| 726 | |
| 727 | function WorldMap({width, height}) { |
| 728 | const theme = useTheme(); |
nothing calls this directly
no test coverage detected