Files
gongxue-base/apps/taro/src/pages/student/home/index.tsx

122 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}