style: align H5 auth entry with legacy UI

This commit is contained in:
Codex
2026-07-02 03:23:03 +08:00
parent 2cb025b74f
commit 02412f2bfb
6 changed files with 369 additions and 18 deletions

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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<Portal>(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 (
<View className='student-page login-page'>
<View className={`student-page login-page ${isAdminPortal ? 'login-page-admin' : 'login-page-student'} ${runtimeReady ? 'runtime-ready' : ''}`}>
<View className='login-hero'>
<View className='login-avatar'>
<Text className='login-avatar-mark'>{(tenant?.branding.shortName || tenant?.branding.brandName || '题').slice(0, 1)}</Text>
<Text className='login-avatar-mark'>{loginCopy.mark}</Text>
</View>
<Text className='login-kicker'>{tenant?.branding.shortName || tenant?.branding.brandName || '题库系统'}</Text>
<Text className='login-title'></Text>
<Text className='login-subtitle'></Text>
<Text className='login-kicker'>{loginCopy.kicker}</Text>
<Text className='login-title'>{loginCopy.title}</Text>
<Text className='login-subtitle'>{loginCopy.subtitle}</Text>
</View>
<View className='login-card list-stack'>
<Text className='row-main'></Text>
<Text className='row-meta'></Text>
<Text className='row-main'>{loginCopy.formTitle}</Text>
<Text className='row-meta'>{loginCopy.formMeta}</Text>
{reason ? <Text className='error-text'>{reason}</Text> : null}
<Input className='input' type='number' maxlength={11} placeholder='请输入手机号' value={phone} onInput={event => setPhone(String(event.detail.value || ''))} />
<View className='toolbar'>

View File

@@ -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;

View File

@@ -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));
}
}