forked from wangziqi/gongxue-base
fix: gate Taro routes before rendering
This commit is contained in:
@@ -79,3 +79,53 @@ textarea {
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255, 255, 255, 0.13);
|
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 { useDidShow } from '@tarojs/taro';
|
||||||
import 'katex/dist/katex.min.css';
|
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';
|
import './app.css';
|
||||||
|
|
||||||
export default function App({ children }: PropsWithChildren) {
|
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(() => {
|
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;
|
return children;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,9 +354,64 @@
|
|||||||
color: var(--tiku-primary);
|
color: var(--tiku-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.platform-chip.readonly {
|
||||||
|
border-color: #bfdbfe;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
.platform-error {
|
.platform-error {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
color: #be123c;
|
color: #be123c;
|
||||||
font-size: 22px;
|
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;
|
font-size: 42px;
|
||||||
line-height: 1;
|
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);
|
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;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-row.nested {
|
||||||
|
margin-top: 14px;
|
||||||
|
border-color: #bfdbfe;
|
||||||
|
background: linear-gradient(135deg, #eff6ff, #fff);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-error {
|
.admin-error {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
@@ -421,3 +428,57 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
white-space: pre-wrap;
|
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))}`;
|
return `/pages/student/login/index?reason=${encodeURIComponent(reason)}&redirect=${encodeURIComponent(safeRedirectPath(redirectPath))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentPagePath() {
|
export function currentPagePath() {
|
||||||
const instance = Taro.getCurrentInstance();
|
const instance = Taro.getCurrentInstance();
|
||||||
const path = instance.router?.path || '';
|
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) {
|
function isPublicPath(path: string) {
|
||||||
return path === '/pages/bootstrap/index' || path === '/pages/student/login/index';
|
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 (isPublicPath(path)) return false;
|
||||||
if (appEnv.portal === 'tenant-admin') return path.startsWith('/pages/tenant-admin/');
|
if (appEnv.portal === 'tenant-admin') return path.startsWith('/pages/tenant-admin/');
|
||||||
if (appEnv.portal === 'platform-admin') return path.startsWith('/pages/platform-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() {
|
export async function guardCurrentRoute() {
|
||||||
await ensureRuntimeConfigLoaded();
|
await ensureRuntimeConfigLoaded();
|
||||||
const path = currentPagePath();
|
const path = currentPagePath();
|
||||||
if (!path || !shouldGuardPath(path) || pendingGuardPath === path) return;
|
if (!path || !shouldGuardPath(path)) return true;
|
||||||
|
if (pendingGuardPath === path) return false;
|
||||||
pendingGuardPath = path;
|
pendingGuardPath = path;
|
||||||
try {
|
try {
|
||||||
if (appEnv.portal === 'platform-admin') {
|
if (appEnv.portal === 'platform-admin') {
|
||||||
await requirePlatformAdmin(path);
|
const payload = await requirePlatformAdmin(path);
|
||||||
return;
|
return Boolean(payload);
|
||||||
}
|
}
|
||||||
if (appEnv.portal === 'tenant-admin') {
|
if (appEnv.portal === 'tenant-admin') {
|
||||||
await requireTenantAdmin(path);
|
const payload = await requireTenantAdmin(path);
|
||||||
return;
|
return Boolean(payload);
|
||||||
}
|
}
|
||||||
await requireSignedIn(path);
|
const payload = await requireSignedIn(path);
|
||||||
|
return Boolean(payload);
|
||||||
} finally {
|
} finally {
|
||||||
pendingGuardPath = '';
|
pendingGuardPath = '';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user