forked from wangziqi/gongxue-base
style: align H5 auth entry with legacy UI
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user