diff --git a/apps/taro/src/app.tsx b/apps/taro/src/app.tsx index 16525f55..6a56acbe 100644 --- a/apps/taro/src/app.tsx +++ b/apps/taro/src/app.tsx @@ -61,10 +61,7 @@ function shouldUseAdminShell(path: string) { } export default function App({ children }: PropsWithChildren) { - const [routeReady, setRouteReady] = useState(() => { - const initialPath = currentPagePath(); - return isPathAllowedForPortal(initialPath) && !shouldGuardPath(initialPath); - }); + const [routeReady, setRouteReady] = useState(false); const [path, setPath] = useState(() => currentPagePath()); function verifyCurrentRoute() { diff --git a/apps/taro/src/components/AdminLegacyShell.css b/apps/taro/src/components/AdminLegacyShell.css index d74a1ae2..ba5024b9 100644 --- a/apps/taro/src/components/AdminLegacyShell.css +++ b/apps/taro/src/components/AdminLegacyShell.css @@ -47,6 +47,15 @@ box-shadow: 0 6px 15px rgba(15, 23, 42, 0.18); } +.backoffice-mobile-nav::-webkit-scrollbar { + height: 5px; +} + +.backoffice-mobile-nav::-webkit-scrollbar-thumb { + border-radius: 999px; + background: #cbd5e1; +} + .backoffice-mobile-mark { font-size: 14px; font-weight: 900; @@ -203,6 +212,23 @@ color: #fff; } +.backoffice-legacy-main .admin-input, +.backoffice-legacy-main .platform-input, +.backoffice-legacy-main .admin-textarea { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7); +} + +.backoffice-legacy-main .admin-form-grid, +.backoffice-legacy-main .platform-form, +.backoffice-legacy-main .platform-permission-panel { + border-color: #e5e7eb; +} + +.backoffice-legacy-main .admin-list, +.backoffice-legacy-main .platform-list { + overflow: hidden; +} + @media (min-width: 980px) { .backoffice-legacy-layout { display: grid; diff --git a/apps/taro/src/pages/platform-admin/platform.css b/apps/taro/src/pages/platform-admin/platform.css index c6b4ebdb..f1c26481 100644 --- a/apps/taro/src/pages/platform-admin/platform.css +++ b/apps/taro/src/pages/platform-admin/platform.css @@ -681,6 +681,73 @@ font-size: 14px; } +.platform-actions .platform-input { + flex: 0 1 280px; +} + +.platform-form .platform-button { + align-self: center; +} + +.platform-row-actions { + padding-top: 8px; + border-top: 1px solid #f8fafc; +} + +.platform-permission-panel { + gap: 12px; + padding: 12px; + background: #f8fafc; +} + +.platform-permission-header { + padding-bottom: 10px; + border-bottom: 1px solid #e5e7eb; +} + +.platform-permission-group { + gap: 6px; +} + +.platform-chip-list { + gap: 7px; +} + +.platform-section .platform-form + .platform-permission-panel { + margin-top: 12px; +} + +.platform-row .platform-chip-list.compact { + padding-top: 10px; +} + +.platform-row .platform-chip.readonly { + max-width: 260px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.platform-metric:nth-child(4n + 1), +.platform-row:nth-child(4n + 1) { + border-left: 4px solid #2563eb; +} + +.platform-metric:nth-child(4n + 2), +.platform-row:nth-child(4n + 2) { + border-left: 4px solid #10b981; +} + +.platform-metric:nth-child(4n + 3), +.platform-row:nth-child(4n + 3) { + border-left: 4px solid #f59e0b; +} + +.platform-metric:nth-child(4n), +.platform-row:nth-child(4n) { + border-left: 4px solid #64748b; +} + @media (min-width: 980px) { .platform-page { padding: 24px; @@ -697,4 +764,12 @@ .platform-title { font-size: 30px; } + + .platform-form { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .platform-field.wide { + grid-column: span 2; + } } diff --git a/apps/taro/src/pages/student/login/index.tsx b/apps/taro/src/pages/student/login/index.tsx index 83875f77..97be58fb 100644 --- a/apps/taro/src/pages/student/login/index.tsx +++ b/apps/taro/src/pages/student/login/index.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useRouter } from '@tarojs/taro'; import { Button, Input, Text, View } from '@tarojs/components'; +import { appEnv, ensureRuntimeConfigLoaded, type Portal } from '@/env'; import { getTenantContext } from '@/services/api'; import { loadCurrentUser, sendSmsCode, verifySmsCode } from '@/services/auth'; import { landingPath, redirectAfterLogin } from '@/services/routeGuard'; @@ -9,6 +10,8 @@ import '../student.css'; export default function StudentLoginPage() { const router = useRouter(); const tenant = getTenantContext(); + const [portal, setPortal] = useState(appEnv.portal); + const [runtimeReady, setRuntimeReady] = useState(false); const [phone, setPhone] = useState(''); const [code, setCode] = useState(''); const [debugCode, setDebugCode] = useState(''); @@ -19,15 +22,54 @@ export default function StudentLoginPage() { const [verifying, setVerifying] = useState(false); useEffect(() => { - if (router.params?.reason) { - setReason(decodeURIComponent(router.params.reason)); - return; - } - loadCurrentUser() - .then(() => redirectAfterLogin(router.params?.redirect || landingPath())) - .catch(() => undefined); + ensureRuntimeConfigLoaded() + .then(() => { + setPortal(appEnv.portal); + setRuntimeReady(true); + if (router.params?.reason) { + setReason(decodeURIComponent(router.params.reason)); + return; + } + loadCurrentUser() + .then(() => redirectAfterLogin(router.params?.redirect || landingPath())) + .catch(() => undefined); + }) + .catch(() => { + setPortal(appEnv.portal); + setRuntimeReady(true); + }); }, []); + const isPlatformAdmin = portal === 'platform-admin'; + const isTenantAdmin = portal === 'tenant-admin'; + const isAdminPortal = isPlatformAdmin || isTenantAdmin; + const loginCopy = isPlatformAdmin + ? { + mark: '管', + kicker: 'Platform Console', + title: '平台后台登录', + subtitle: '登录后管理租户、账务、公共题库和平台员工权限。', + formTitle: '管理员手机号登录', + formMeta: '短信验证码确认身份后,还会校验平台管理员权限。', + } + : isTenantAdmin + ? { + mark: '题', + kicker: 'Tenant Admin', + title: '租户后台登录', + subtitle: '登录后管理学生、题库内容、营销活动、财务和品牌设置。', + formTitle: '后台手机号登录', + formMeta: '短信验证码确认身份后,还会校验租户后台权限。', + } + : { + mark: (tenant?.branding.shortName || tenant?.branding.brandName || '题').slice(0, 1), + kicker: tenant?.branding.shortName || tenant?.branding.brandName || '题库系统', + title: '欢迎回来', + subtitle: '登录后继续刷题、查看错题和学习报告。', + formTitle: '手机号登录', + formMeta: '短信验证码将用于确认当前租户和账号身份。', + }; + async function handleSendCode() { setSending(true); setError(''); @@ -58,19 +100,19 @@ export default function StudentLoginPage() { } return ( - + - {(tenant?.branding.shortName || tenant?.branding.brandName || '题').slice(0, 1)} + {loginCopy.mark} - {tenant?.branding.shortName || tenant?.branding.brandName || '题库系统'} - 欢迎回来 - 登录后继续刷题、查看错题和学习报告。 + {loginCopy.kicker} + {loginCopy.title} + {loginCopy.subtitle} - 手机号登录 - 短信验证码将用于确认当前租户和账号身份。 + {loginCopy.formTitle} + {loginCopy.formMeta} {reason ? {reason} : null} setPhone(String(event.detail.value || ''))} /> diff --git a/apps/taro/src/pages/student/student.css b/apps/taro/src/pages/student/student.css index 1c48adcc..eeb6184d 100644 --- a/apps/taro/src/pages/student/student.css +++ b/apps/taro/src/pages/student/student.css @@ -114,6 +114,48 @@ min-width: 0; } +.login-page-admin { + background: + linear-gradient(to right, rgba(15, 23, 42, 0.035) 1px, transparent 1px), + linear-gradient(to bottom, rgba(15, 23, 42, 0.035) 1px, transparent 1px), + #f4f6f9; + background-size: 40px 40px; +} + +.login-page-admin .login-hero { + background: #111827; + box-shadow: 0 16px 34px rgba(15, 23, 42, 0.18); +} + +.login-page-admin .login-hero::after { + background: rgba(37, 99, 235, 0.26); +} + +.login-page-admin .login-avatar { + border-color: rgba(255, 255, 255, 0.18); + background: #1e293b; +} + +.login-page-admin .login-avatar-mark { + color: #fff; +} + +.login-page-admin .login-kicker { + color: #93c5fd; +} + +.login-page-admin .login-card { + border-color: #e5e7eb; +} + +.login-page-admin .login-card .row-main { + color: #111827; +} + +.login-page-admin .login-card .row-meta { + color: #64748b; +} + .student-topbar { display: flex; align-items: center; @@ -2026,6 +2068,71 @@ font-weight: 900; } +.notifications-page .notification-row, +.review-page .list-row, +.video-page .list-row { + border-left: 4px solid #1152d4; +} + +.notifications-page .notification-row:nth-child(2n), +.review-page .list-row:nth-child(2n), +.video-page .list-row:nth-child(2n) { + border-left-color: #10b981; +} + +.notifications-page .notification-row:nth-child(3n), +.review-page .list-row:nth-child(3n), +.video-page .list-row:nth-child(3n) { + border-left-color: #f59e0b; +} + +.checkout-page .quiet-panel, +.reports-page .report-panel, +.profile-page .profile-card, +.vocabulary-overview-panel, +.scoreline-filter-panel { + overflow: hidden; +} + +.checkout-page .quiet-panel::before, +.reports-page .report-panel::before, +.profile-page .profile-card::before, +.vocabulary-overview-panel::before, +.scoreline-filter-panel::before { + content: ""; + display: block; + width: 56px; + height: 4px; + margin-bottom: 16px; + border-radius: 999px; + background: #1152d4; +} + +.assets-page .asset-preview-panel, +.scoreline-page .scoreline-filter-panel, +.vocabulary-page .vocabulary-card, +.handbook-page .handbook-search-panel { + border-top: 4px solid #1152d4; +} + +.video-page .video-frame { + border-radius: 16px; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08); +} + +.student-page .toolbar::-webkit-scrollbar, +.scoreline-chip-row::-webkit-scrollbar, +.handbook-outline::-webkit-scrollbar { + height: 6px; +} + +.student-page .toolbar::-webkit-scrollbar-thumb, +.scoreline-chip-row::-webkit-scrollbar-thumb, +.handbook-outline::-webkit-scrollbar-thumb { + border-radius: 999px; + background: #cbd5e1; +} + @media (min-width: 900px) { .student-page { width: 100%; @@ -2068,6 +2175,10 @@ padding: 40px; } + .login-page-admin .login-hero { + border-radius: 24px 0 0 24px; + } + .login-card .input { height: 52px; font-size: 15px; diff --git a/apps/taro/src/pages/tenant-admin/admin.css b/apps/taro/src/pages/tenant-admin/admin.css index 0c05ac9a..3113aa60 100644 --- a/apps/taro/src/pages/tenant-admin/admin.css +++ b/apps/taro/src/pages/tenant-admin/admin.css @@ -759,6 +759,94 @@ border-radius: 9px; } +.admin-field { + min-width: 0; +} + +.admin-actions .admin-input, +.admin-actions .admin-button, +.admin-tabs .admin-button { + flex: 0 0 auto; +} + +.admin-actions .admin-input { + width: 240px; +} + +.admin-form-grid .admin-button { + align-self: center; +} + +.admin-metric.theme-template { + border-top: 4px solid #2563eb; +} + +.admin-metric.theme-template.selected { + border-color: #bfdbfe; + background: #eff6ff; + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.12); +} + +.admin-sub-list { + gap: 6px; + padding: 10px; + background: #f8fafc; +} + +.admin-sub-row { + padding: 9px 10px; +} + +.admin-row-actions { + padding-top: 8px; + border-top: 1px solid #f8fafc; +} + +.admin-row .admin-chip, +.admin-chip { + display: inline-flex; + align-items: center; + min-height: 32px; + padding: 0 10px; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: #f8fafc; + color: #475569; + font-size: 13px; + font-weight: 760; +} + +.theme-preview-panel { + border-top: 4px solid #2563eb; +} + +.theme-preview-banner { + min-height: 96px; + padding: 18px; +} + +.theme-preview-title { + font-size: 22px; + font-weight: 900; +} + +.theme-preview-subtitle { + font-size: 14px; +} + +.theme-preview-actions { + padding: 12px 16px; +} + +.theme-preview-pill { + width: 64px; + height: 24px; +} + +.admin-error { + font-size: 14px; +} + @media (min-width: 980px) { .admin-page { padding: 24px; @@ -779,4 +867,16 @@ .admin-section .admin-list { gap: 10px; } + + .admin-form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .admin-form-grid .admin-textarea { + grid-column: 1 / -1; + } + + .admin-section .admin-grid.three { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } }