forked from wangziqi/gongxue-base
fix: gate Taro routes before rendering
This commit is contained in:
@@ -79,3 +79,53 @@ textarea {
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.13);
|
||||
}
|
||||
|
||||
.route-guard-page {
|
||||
min-height: 100vh;
|
||||
padding: 28px;
|
||||
background: var(--tiku-page);
|
||||
}
|
||||
|
||||
.route-guard-shell {
|
||||
width: 100%;
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding-top: 52px;
|
||||
}
|
||||
|
||||
.route-guard-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 38px;
|
||||
border: 1px solid #dbeafe;
|
||||
border-radius: 30px;
|
||||
background: linear-gradient(135deg, #1152d4 0%, #2563eb 62%, #60a5fa 100%);
|
||||
box-shadow: var(--tiku-shadow-blue);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.route-guard-kicker {
|
||||
display: block;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.route-guard-title {
|
||||
display: block;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: 900;
|
||||
line-height: 1.16;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.route-guard-subtitle {
|
||||
display: block;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-size: 24px;
|
||||
line-height: 1.6;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,72 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { Text, View } from '@tarojs/components';
|
||||
import { useDidShow } from '@tarojs/taro';
|
||||
import 'katex/dist/katex.min.css';
|
||||
import { guardCurrentRoute } from '@/services/routeGuard';
|
||||
import { appEnv } from '@/env';
|
||||
import { currentPagePath, guardCurrentRoute, shouldGuardPath } from '@/services/routeGuard';
|
||||
import './app.css';
|
||||
|
||||
export default function App({ children }: PropsWithChildren) {
|
||||
const [routeReady, setRouteReady] = useState(() => !shouldGuardPath(currentPagePath()));
|
||||
|
||||
function verifyCurrentRoute() {
|
||||
const path = currentPagePath();
|
||||
const protectedRoute = shouldGuardPath(path);
|
||||
setRouteReady(!protectedRoute);
|
||||
guardCurrentRoute()
|
||||
.then(ok => setRouteReady(Boolean(ok)))
|
||||
.catch(() => setRouteReady(false));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
verifyCurrentRoute();
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
guardCurrentRoute().catch(() => undefined);
|
||||
verifyCurrentRoute();
|
||||
});
|
||||
|
||||
if (!routeReady && appEnv.portal === 'tenant-admin') {
|
||||
return (
|
||||
<View className='route-guard-page'>
|
||||
<View className='route-guard-shell'>
|
||||
<View className='route-guard-panel'>
|
||||
<Text className='route-guard-kicker'>Tenant Admin</Text>
|
||||
<Text className='route-guard-title'>正在校验后台权限</Text>
|
||||
<Text className='route-guard-subtitle'>请先完成登录,系统会确认当前账号是否拥有租户后台权限。</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!routeReady && appEnv.portal === 'platform-admin') {
|
||||
return (
|
||||
<View className='route-guard-page'>
|
||||
<View className='route-guard-shell'>
|
||||
<View className='route-guard-panel'>
|
||||
<Text className='route-guard-kicker'>Platform Admin</Text>
|
||||
<Text className='route-guard-title'>正在校验平台权限</Text>
|
||||
<Text className='route-guard-subtitle'>请先完成登录,系统会确认当前账号是否拥有平台管理员权限。</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!routeReady) {
|
||||
return (
|
||||
<View className='route-guard-page'>
|
||||
<View className='route-guard-shell'>
|
||||
<View className='route-guard-panel'>
|
||||
<Text className='route-guard-kicker'>Student</Text>
|
||||
<Text className='route-guard-title'>正在校验登录状态</Text>
|
||||
<Text className='route-guard-subtitle'>请先完成登录,系统会带你回到刚才打开的页面。</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -354,9 +354,64 @@
|
||||
color: var(--tiku-primary);
|
||||
}
|
||||
|
||||
.platform-chip.readonly {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.platform-error {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
color: #be123c;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
@media (min-width: 980px) {
|
||||
.platform-page {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 38px;
|
||||
}
|
||||
|
||||
.platform-shell {
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.platform-header {
|
||||
padding: 38px;
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.platform-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.platform-grid.three {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.platform-module-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.platform-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.platform-grid,
|
||||
.platform-grid.three,
|
||||
.platform-module-grid,
|
||||
.platform-form {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
font-size: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,3 +323,46 @@
|
||||
font-size: 42px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
.student-page {
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 36px;
|
||||
}
|
||||
|
||||
.hero-band {
|
||||
min-height: 320px;
|
||||
padding: 44px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 52px;
|
||||
}
|
||||
|
||||
.entry-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.student-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.home-stat-grid {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.entry-grid {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1301,3 +1301,68 @@
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.points-list {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.points-list .list-row {
|
||||
border-color: #bfdbfe;
|
||||
background: linear-gradient(135deg, #eff6ff, #fff);
|
||||
}
|
||||
|
||||
.points-list .list-row:nth-child(2n) {
|
||||
border-color: #bbf7d0;
|
||||
background: linear-gradient(135deg, #ecfdf5, #fff);
|
||||
}
|
||||
|
||||
.scoreline-page .student-topbar {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.scoreline-page .student-title {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.scoreline-page .secondary-button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
.student-page {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 36px;
|
||||
}
|
||||
|
||||
.student-topbar {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.student-title {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.answer-sheet {
|
||||
grid-template-columns: repeat(10, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.toolbar.wrap {
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.student-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.student-title {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.student-subtitle,
|
||||
.row-meta {
|
||||
font-size: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,13 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.admin-row.nested {
|
||||
margin-top: 14px;
|
||||
border-color: #bfdbfe;
|
||||
background: linear-gradient(135deg, #eff6ff, #fff);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.admin-error {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
@@ -421,3 +428,57 @@
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (min-width: 980px) {
|
||||
.admin-page {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 38px;
|
||||
}
|
||||
|
||||
.admin-shell {
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
padding: 38px;
|
||||
}
|
||||
|
||||
.admin-title {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.admin-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-grid.three,
|
||||
.admin-metric-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-module-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-form-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.admin-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.admin-grid,
|
||||
.admin-grid.three,
|
||||
.admin-metric-grid,
|
||||
.admin-module-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.admin-title {
|
||||
font-size: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,23 @@ function forbiddenUrl(reason: string, redirectPath: string) {
|
||||
return `/pages/student/login/index?reason=${encodeURIComponent(reason)}&redirect=${encodeURIComponent(safeRedirectPath(redirectPath))}`;
|
||||
}
|
||||
|
||||
function currentPagePath() {
|
||||
export function currentPagePath() {
|
||||
const instance = Taro.getCurrentInstance();
|
||||
const path = instance.router?.path || '';
|
||||
return path.startsWith('/') ? path : `/${path}`;
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
if (normalizedPath !== '/') return normalizedPath;
|
||||
if (isH5Runtime() && typeof window !== 'undefined') {
|
||||
const pathname = window.location.pathname || '';
|
||||
if (pathname.startsWith('/pages/')) return pathname;
|
||||
}
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
function isPublicPath(path: string) {
|
||||
return path === '/pages/bootstrap/index' || path === '/pages/student/login/index';
|
||||
}
|
||||
|
||||
function shouldGuardPath(path: string) {
|
||||
export function shouldGuardPath(path: string) {
|
||||
if (isPublicPath(path)) return false;
|
||||
if (appEnv.portal === 'tenant-admin') return path.startsWith('/pages/tenant-admin/');
|
||||
if (appEnv.portal === 'platform-admin') return path.startsWith('/pages/platform-admin/');
|
||||
@@ -93,18 +99,20 @@ export async function requireTenantAdmin(redirectPath: string) {
|
||||
export async function guardCurrentRoute() {
|
||||
await ensureRuntimeConfigLoaded();
|
||||
const path = currentPagePath();
|
||||
if (!path || !shouldGuardPath(path) || pendingGuardPath === path) return;
|
||||
if (!path || !shouldGuardPath(path)) return true;
|
||||
if (pendingGuardPath === path) return false;
|
||||
pendingGuardPath = path;
|
||||
try {
|
||||
if (appEnv.portal === 'platform-admin') {
|
||||
await requirePlatformAdmin(path);
|
||||
return;
|
||||
const payload = await requirePlatformAdmin(path);
|
||||
return Boolean(payload);
|
||||
}
|
||||
if (appEnv.portal === 'tenant-admin') {
|
||||
await requireTenantAdmin(path);
|
||||
return;
|
||||
const payload = await requireTenantAdmin(path);
|
||||
return Boolean(payload);
|
||||
}
|
||||
await requireSignedIn(path);
|
||||
const payload = await requireSignedIn(path);
|
||||
return Boolean(payload);
|
||||
} finally {
|
||||
pendingGuardPath = '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user