forked from wangziqi/gongxue-base
fix: tighten H5 portal guard and legacy home stats
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<string, unknown> | undefined)?.unresolvedWrong || 0);
|
||||
const streakDays = Number((stats.checkin as Record<string, unknown> | 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() {
|
||||
|
||||
<View className='home-stat-grid'>
|
||||
<View className='home-stat'>
|
||||
<View className='home-stat-head'>
|
||||
<Text className='home-stat-label'>累计刷题</Text>
|
||||
<Text className='home-stat-badge green'>累计</Text>
|
||||
</View>
|
||||
<Text className='home-stat-value'>{String(totalAnswered)}</Text>
|
||||
<Text className='home-stat-label'>累计刷题</Text>
|
||||
<Text className='home-stat-meta'>道题目</Text>
|
||||
<View className='home-progress-track'><View className='home-progress-fill blue' style={{ width: `${answeredPercent}%` }} /></View>
|
||||
</View>
|
||||
<View className='home-stat'>
|
||||
<View className='home-stat-head'>
|
||||
<Text className='home-stat-label'>正确率</Text>
|
||||
<Text className='home-stat-badge amber'>{accuracy ? '良好' : '待开始'}</Text>
|
||||
</View>
|
||||
<Text className='home-stat-value'>{accuracy ? `${accuracy}%` : '--'}</Text>
|
||||
<Text className='home-stat-label'>正确率</Text>
|
||||
<Text className='home-stat-meta'>答对率</Text>
|
||||
<View className='home-mini-bars'>
|
||||
{[42, 56, 50, 68, 62, accuracyPercent || 18].map((height, itemIndex) => (
|
||||
<View className={`home-mini-bar ${itemIndex === 5 ? 'active' : ''}`} style={{ height: `${Math.max(14, height)}%` }} key={itemIndex} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View className='home-stat' onClick={() => Taro.navigateTo({ url: '/pages/student/review/index?type=wrong' })}>
|
||||
<View className='home-stat-head'>
|
||||
<Text className='home-stat-label'>错题本</Text>
|
||||
<Text className='home-stat-badge red'>待复习</Text>
|
||||
</View>
|
||||
<Text className='home-stat-value'>{String(wrongCount)}</Text>
|
||||
<Text className='home-stat-label'>错题本</Text>
|
||||
<Text className='home-stat-meta'>点击去复习 ›</Text>
|
||||
</View>
|
||||
<View className='home-stat'>
|
||||
<View className='home-stat-head'>
|
||||
<Text className='home-stat-label'>打卡记录</Text>
|
||||
<Text className='home-stat-badge blue'>加油</Text>
|
||||
</View>
|
||||
<Text className='home-stat-value'>{String(streakDays)}</Text>
|
||||
<Text className='home-stat-meta'>天连续学习</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user