forked from wangziqi/gongxue-base
122 lines
6.6 KiB
TypeScript
122 lines
6.6 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
||
import Taro from '@tarojs/taro';
|
||
import { Button, Text, View } from '@tarojs/components';
|
||
import { getTenantContext } from '@/services/api';
|
||
import { loadStudentDashboard, type DashboardSnapshot } from '@/services/catalog';
|
||
import { requireSignedIn } from '@/services/routeGuard';
|
||
import './index.css';
|
||
|
||
export default function StudentHomePage() {
|
||
const tenant = getTenantContext();
|
||
const [snapshot, setSnapshot] = useState<DashboardSnapshot | null>(null);
|
||
const [userName, setUserName] = useState('');
|
||
|
||
useEffect(() => {
|
||
requireSignedIn('/pages/student/home/index').then(payload => {
|
||
if (payload) setUserName(payload.user?.name || payload.item?.name || '');
|
||
}).catch(() => undefined);
|
||
loadStudentDashboard().then(setSnapshot).catch(() => setSnapshot({ entries: [], banners: [], announcements: [], profile: null }));
|
||
}, []);
|
||
|
||
const entryNames = useMemo(() => (snapshot?.entries || []).slice(0, 6), [snapshot]);
|
||
const brandName = tenant?.branding.brandName || tenant?.branding.shortName || '工学题库';
|
||
const profile = snapshot?.profile && typeof snapshot.profile === 'object' ? snapshot.profile as Record<string, unknown> : {};
|
||
const stats = profile.stats && typeof profile.stats === 'object' ? profile.stats as Record<string, unknown> : {};
|
||
const answerStats = stats.answers && typeof stats.answers === 'object' ? stats.answers as Record<string, unknown> : {};
|
||
const totalAnswered = Number(answerStats.totalAnswered || 0);
|
||
const accuracy = Math.round(Number(answerStats.accuracy || 0) * 100);
|
||
const wrongCount = Number((stats.wrongBook as Record<string, unknown> | undefined)?.unresolvedWrong || 0);
|
||
const quickActions = [
|
||
{ name: '文化课', path: '/pages/student/catalog/index?entryType=subjects', meta: '必刷好题', tone: 'red' },
|
||
{ name: '专业课', path: '/pages/student/catalog/index?entryType=exam_bank', meta: '专项练习', tone: 'blue' },
|
||
{ name: '升本单词', path: '/pages/student/vocabulary/index', meta: '今日待背', tone: 'orange' },
|
||
{ name: '知识手册', path: '/pages/student/handbook/index', meta: '考前速查', tone: 'green' },
|
||
{ name: '错题本', path: '/pages/student/review/index?type=wrong', meta: '查漏补缺', tone: 'violet' },
|
||
{ name: '收藏夹', path: '/pages/student/review/index?type=favorite', meta: '重点复习', tone: 'pink' },
|
||
{ name: '练习报告', path: '/pages/student/reports/index', meta: '模考 / 历史', tone: 'slate' },
|
||
{ name: '分数线', path: '/pages/student/scoreline/index', meta: '院校趋势', tone: 'blue' },
|
||
{ name: 'AI择校', path: '/pages/student/ai-school/index', meta: 'SVIP报告', tone: 'violet' },
|
||
{ name: '备考资料', path: '/pages/student/assets/index', meta: 'PDF 预览', tone: 'green' },
|
||
{ name: '消息通知', path: '/pages/student/notifications/index', meta: '通知 / 待办', tone: 'orange' },
|
||
{ name: '个人中心', path: '/pages/student/profile/index', meta: '会员 / 订单', tone: 'slate' },
|
||
];
|
||
|
||
return (
|
||
<View className='student-page'>
|
||
<View className='topbar'>
|
||
<View className='home-user'>
|
||
<View className='home-avatar'>
|
||
<Text className='home-avatar-mark'>{(userName || brandName || 'U').slice(0, 1).toUpperCase()}</Text>
|
||
</View>
|
||
<View>
|
||
<Text className='brand'>Hi, {userName || '同学'}</Text>
|
||
<Text className='subline'>{brandName} · 今天也要加油刷题哦</Text>
|
||
</View>
|
||
</View>
|
||
<Button className='ghost-button' onClick={() => Taro.navigateTo({ url: '/pages/student/profile/index' })}>我的</Button>
|
||
</View>
|
||
|
||
<View className='hero-band'>
|
||
<View className='hero-content'>
|
||
<Text className='hero-kicker'>今天也是元气满满的一天</Text>
|
||
<Text className='hero-title'>继续你的备考之旅</Text>
|
||
<Text className='hero-copy'>已经连续学习的每一天,都会让你离目标院校更近一步。</Text>
|
||
<View className='hero-actions'>
|
||
<Button className='hero-primary' onClick={() => Taro.navigateTo({ url: '/pages/student/catalog/index' })}>开始刷题</Button>
|
||
<Button className='hero-secondary' onClick={() => Taro.navigateTo({ url: '/pages/student/reports/index' })}>学习报告</Button>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
<View className='home-stat-grid'>
|
||
<View className='home-stat'>
|
||
<Text className='home-stat-value'>{String(totalAnswered)}</Text>
|
||
<Text className='home-stat-label'>累计刷题</Text>
|
||
</View>
|
||
<View className='home-stat'>
|
||
<Text className='home-stat-value'>{accuracy ? `${accuracy}%` : '--'}</Text>
|
||
<Text className='home-stat-label'>正确率</Text>
|
||
</View>
|
||
<View className='home-stat' onClick={() => Taro.navigateTo({ url: '/pages/student/review/index?type=wrong' })}>
|
||
<Text className='home-stat-value'>{String(wrongCount)}</Text>
|
||
<Text className='home-stat-label'>错题本</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View className='section'>
|
||
<Text className='section-title'>学习入口</Text>
|
||
<View className='entry-grid'>
|
||
{entryNames.length ? entryNames.map(item => (
|
||
<View className='entry-tile tone-blue' key={item.id} onClick={() => Taro.navigateTo({ url: `/pages/student/catalog/index?entryId=${item.id}` })}>
|
||
<Text className='entry-label'>已配置入口</Text>
|
||
<Text className='entry-name'>{item.name}</Text>
|
||
<Text className='entry-type'>{item.entryType || 'content'}</Text>
|
||
</View>
|
||
)) : quickActions.map(item => (
|
||
<View className={`entry-tile tone-${item.tone}`} key={item.name} onClick={() => Taro.navigateTo({ url: item.path })}>
|
||
<Text className='entry-label'>{item.meta}</Text>
|
||
<Text className='entry-name'>{item.name}</Text>
|
||
<Text className='entry-type'>点击进入</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
|
||
<View className='section'>
|
||
<Text className='section-title'>全部功能</Text>
|
||
<View className='quick-list'>
|
||
{quickActions.map(item => (
|
||
<View className='quick-row' key={item.path} onClick={() => Taro.navigateTo({ url: item.path })}>
|
||
<View>
|
||
<Text className='quick-name'>{item.name}</Text>
|
||
<Text className='quick-meta'>{item.meta}</Text>
|
||
</View>
|
||
<Text className='quick-arrow'>›</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|