diff --git a/apps/taro/src/app.tsx b/apps/taro/src/app.tsx index 129af959..16525f55 100644 --- a/apps/taro/src/app.tsx +++ b/apps/taro/src/app.tsx @@ -4,7 +4,7 @@ import 'katex/dist/katex.min.css'; import { AdminLegacyShell } from '@/components/AdminLegacyShell'; import { StudentLegacyShell } from '@/components/StudentLegacyShell'; import { appEnv, isH5Runtime } from '@/env'; -import { currentPagePath, guardCurrentRoute, shouldGuardPath } from '@/services/routeGuard'; +import { currentPagePath, guardCurrentRoute, isPathAllowedForPortal, shouldGuardPath } from '@/services/routeGuard'; import './app.css'; const routeChangeEvent = 'tiku-route-change'; @@ -61,14 +61,18 @@ function shouldUseAdminShell(path: string) { } export default function App({ children }: PropsWithChildren) { - const [routeReady, setRouteReady] = useState(() => !shouldGuardPath(currentPagePath())); + const [routeReady, setRouteReady] = useState(() => { + const initialPath = currentPagePath(); + return isPathAllowedForPortal(initialPath) && !shouldGuardPath(initialPath); + }); const [path, setPath] = useState(() => currentPagePath()); function verifyCurrentRoute() { const nextPath = currentPagePath(); setPath(nextPath); + const allowedRoute = isPathAllowedForPortal(nextPath); const protectedRoute = shouldGuardPath(nextPath); - setRouteReady(!protectedRoute); + setRouteReady(allowedRoute && !protectedRoute); guardCurrentRoute() .then(ok => setRouteReady(Boolean(ok))) .catch(() => setRouteReady(false)); diff --git a/apps/taro/src/index.html b/apps/taro/src/index.html index 379a713a..9447141c 100644 --- a/apps/taro/src/index.html +++ b/apps/taro/src/index.html @@ -25,6 +25,18 @@ window.location.replace('/pages/bootstrap/index?redirect=' + encodeURIComponent('/')); return; } + if (portal === 'student' && path.indexOf('/pages/') === 0 && path.indexOf('/pages/student/') !== 0 && path.indexOf('/pages/bootstrap/') !== 0) { + window.location.replace('/pages/bootstrap/index?redirect=' + encodeURIComponent('/pages/student/home/index')); + return; + } + if (portal === 'tenant-admin' && path.indexOf('/pages/') === 0 && path.indexOf('/pages/tenant-admin/') !== 0 && path.indexOf('/pages/bootstrap/') !== 0 && path.indexOf('/pages/student/login/') !== 0) { + window.location.replace('/pages/bootstrap/index?redirect=' + encodeURIComponent('/pages/tenant-admin/workbench/index')); + return; + } + if (portal === 'platform-admin' && path.indexOf('/pages/') === 0 && path.indexOf('/pages/platform-admin/') !== 0 && path.indexOf('/pages/bootstrap/') !== 0 && path.indexOf('/pages/student/login/') !== 0) { + window.location.replace('/pages/bootstrap/index?redirect=' + encodeURIComponent('/pages/platform-admin/workbench/index')); + return; + } if (path.indexOf('/pages/student/') === 0 && !publicStudentPaths[path]) { window.location.replace('/pages/bootstrap/index?redirect=' + encodeURIComponent(redirect)); return; diff --git a/apps/taro/src/pages/student/home/index.css b/apps/taro/src/pages/student/home/index.css index 53b4238a..dc20c981 100644 --- a/apps/taro/src/pages/student/home/index.css +++ b/apps/taro/src/pages/student/home/index.css @@ -179,20 +179,28 @@ .home-stat-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 20px; } .home-stat { - min-height: 126px; - padding: 20px 18px; + min-height: 160px; + padding: 20px; border: 1px solid #f1f5f9; border-radius: 16px; background: #fff; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); } +.home-stat-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + margin-bottom: 14px; +} + .home-stat-value { display: block; color: var(--tiku-text); @@ -203,10 +211,83 @@ .home-stat-label { display: block; - margin-top: 12px; color: var(--tiku-muted); - font-size: 21px; - font-weight: 700; + font-size: 18px; + font-weight: 760; +} + +.home-stat-meta { + display: block; + margin-top: 8px; + color: #94a3b8; + font-size: 17px; + font-weight: 720; + line-height: 1.3; +} + +.home-stat-badge { + flex: 0 0 auto; + padding: 4px 9px; + border-radius: 999px; + font-size: 14px; + font-weight: 900; + line-height: 1.2; +} + +.home-stat-badge.green { + background: #ecfdf5; + color: #059669; +} + +.home-stat-badge.amber { + background: #fffbeb; + color: #b45309; +} + +.home-stat-badge.red { + background: #fff1f2; + color: #e11d48; +} + +.home-stat-badge.blue { + background: #eff6ff; + color: #1152d4; +} + +.home-progress-track { + height: 8px; + margin-top: 16px; + overflow: hidden; + border-radius: 999px; + background: #f1f5f9; +} + +.home-progress-fill { + height: 100%; + border-radius: inherit; +} + +.home-progress-fill.blue { + background: #1152d4; +} + +.home-mini-bars { + display: flex; + align-items: flex-end; + gap: 5px; + height: 34px; + margin-top: 14px; +} + +.home-mini-bar { + flex: 1; + min-width: 0; + border-radius: 4px 4px 0 0; + background: rgba(17, 82, 212, 0.18); +} + +.home-mini-bar.active { + background: #1152d4; } .section { @@ -373,6 +454,11 @@ .entry-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } + + .home-stat-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 18px; + } } @media (max-width: 420px) { diff --git a/apps/taro/src/pages/student/home/index.tsx b/apps/taro/src/pages/student/home/index.tsx index 8cbfb1a2..fbec7dac 100644 --- a/apps/taro/src/pages/student/home/index.tsx +++ b/apps/taro/src/pages/student/home/index.tsx @@ -26,6 +26,9 @@ export default function StudentHomePage() { const totalAnswered = Number(answerStats.totalAnswered || 0); const accuracy = Math.round(Number(answerStats.accuracy || 0) * 100); const wrongCount = Number((stats.wrongBook as Record | undefined)?.unresolvedWrong || 0); + const streakDays = Number((stats.checkin as Record | undefined)?.streakDays || 0); + const accuracyPercent = Math.max(0, Math.min(100, accuracy || 0)); + const answeredPercent = Math.max(12, Math.min(100, Math.round(totalAnswered / 20))); 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' }, @@ -70,16 +73,42 @@ export default function StudentHomePage() { + + 累计刷题 + 累计 + {String(totalAnswered)} - 累计刷题 + 道题目 + + + 正确率 + {accuracy ? '良好' : '待开始'} + {accuracy ? `${accuracy}%` : '--'} - 正确率 + 答对率 + + {[42, 56, 50, 68, 62, accuracyPercent || 18].map((height, itemIndex) => ( + + ))} + Taro.navigateTo({ url: '/pages/student/review/index?type=wrong' })}> + + 错题本 + 待复习 + {String(wrongCount)} - 错题本 + 点击去复习 › + + + + 打卡记录 + 加油 + + {String(streakDays)} + 天连续学习 diff --git a/apps/taro/src/services/routeGuard.ts b/apps/taro/src/services/routeGuard.ts index 30624e4e..654733c4 100644 --- a/apps/taro/src/services/routeGuard.ts +++ b/apps/taro/src/services/routeGuard.ts @@ -50,8 +50,17 @@ function isPublicPath(path: string) { return path === '/pages/bootstrap/index' || path === '/pages/student/login/index'; } +export function isPathAllowedForPortal(path: string) { + if (isPublicPath(path)) return true; + if (!path.startsWith('/pages/')) return true; + if (appEnv.portal === 'tenant-admin') return path.startsWith('/pages/tenant-admin/'); + if (appEnv.portal === 'platform-admin') return path.startsWith('/pages/platform-admin/'); + return path.startsWith('/pages/student/'); +} + export function shouldGuardPath(path: string) { if (isPublicPath(path)) return false; + if (!isPathAllowedForPortal(path)) return true; if (appEnv.portal === 'tenant-admin') return path.startsWith('/pages/tenant-admin/'); if (appEnv.portal === 'platform-admin') return path.startsWith('/pages/platform-admin/'); return path.startsWith('/pages/student/') && path !== '/pages/student/region/index'; @@ -107,6 +116,10 @@ export async function guardCurrentRoute() { await ensureRuntimeConfigLoaded(); const path = currentPagePath(); if (!path || !shouldGuardPath(path)) return true; + if (!isPathAllowedForPortal(path)) { + redirectToAuthUrl(`/pages/bootstrap/index?redirect=${encodeURIComponent(landingPath())}`); + return false; + } if (pendingGuardPath === path) return false; pendingGuardPath = path; try {