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

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