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) {
|
export default function App({ children }: PropsWithChildren) {
|
||||||
const [routeReady, setRouteReady] = useState(() => {
|
const [routeReady, setRouteReady] = useState(false);
|
||||||
const initialPath = currentPagePath();
|
|
||||||
return isPathAllowedForPortal(initialPath) && !shouldGuardPath(initialPath);
|
|
||||||
});
|
|
||||||
const [path, setPath] = useState(() => currentPagePath());
|
const [path, setPath] = useState(() => currentPagePath());
|
||||||
|
|
||||||
function verifyCurrentRoute() {
|
function verifyCurrentRoute() {
|
||||||
|
|||||||
@@ -47,6 +47,15 @@
|
|||||||
box-shadow: 0 6px 15px rgba(15, 23, 42, 0.18);
|
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 {
|
.backoffice-mobile-mark {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
@@ -203,6 +212,23 @@
|
|||||||
color: #fff;
|
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) {
|
@media (min-width: 980px) {
|
||||||
.backoffice-legacy-layout {
|
.backoffice-legacy-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -681,6 +681,73 @@
|
|||||||
font-size: 14px;
|
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) {
|
@media (min-width: 980px) {
|
||||||
.platform-page {
|
.platform-page {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
@@ -697,4 +764,12 @@
|
|||||||
.platform-title {
|
.platform-title {
|
||||||
font-size: 30px;
|
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 { useEffect, useState } from 'react';
|
||||||
import { useRouter } from '@tarojs/taro';
|
import { useRouter } from '@tarojs/taro';
|
||||||
import { Button, Input, Text, View } from '@tarojs/components';
|
import { Button, Input, Text, View } from '@tarojs/components';
|
||||||
|
import { appEnv, ensureRuntimeConfigLoaded, type Portal } from '@/env';
|
||||||
import { getTenantContext } from '@/services/api';
|
import { getTenantContext } from '@/services/api';
|
||||||
import { loadCurrentUser, sendSmsCode, verifySmsCode } from '@/services/auth';
|
import { loadCurrentUser, sendSmsCode, verifySmsCode } from '@/services/auth';
|
||||||
import { landingPath, redirectAfterLogin } from '@/services/routeGuard';
|
import { landingPath, redirectAfterLogin } from '@/services/routeGuard';
|
||||||
@@ -9,6 +10,8 @@ import '../student.css';
|
|||||||
export default function StudentLoginPage() {
|
export default function StudentLoginPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const tenant = getTenantContext();
|
const tenant = getTenantContext();
|
||||||
|
const [portal, setPortal] = useState<Portal>(appEnv.portal);
|
||||||
|
const [runtimeReady, setRuntimeReady] = useState(false);
|
||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
const [debugCode, setDebugCode] = useState('');
|
const [debugCode, setDebugCode] = useState('');
|
||||||
@@ -19,15 +22,54 @@ export default function StudentLoginPage() {
|
|||||||
const [verifying, setVerifying] = useState(false);
|
const [verifying, setVerifying] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.params?.reason) {
|
ensureRuntimeConfigLoaded()
|
||||||
setReason(decodeURIComponent(router.params.reason));
|
.then(() => {
|
||||||
return;
|
setPortal(appEnv.portal);
|
||||||
}
|
setRuntimeReady(true);
|
||||||
loadCurrentUser()
|
if (router.params?.reason) {
|
||||||
.then(() => redirectAfterLogin(router.params?.redirect || landingPath()))
|
setReason(decodeURIComponent(router.params.reason));
|
||||||
.catch(() => undefined);
|
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() {
|
async function handleSendCode() {
|
||||||
setSending(true);
|
setSending(true);
|
||||||
setError('');
|
setError('');
|
||||||
@@ -58,19 +100,19 @@ export default function StudentLoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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-hero'>
|
||||||
<View className='login-avatar'>
|
<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>
|
</View>
|
||||||
<Text className='login-kicker'>{tenant?.branding.shortName || tenant?.branding.brandName || '题库系统'}</Text>
|
<Text className='login-kicker'>{loginCopy.kicker}</Text>
|
||||||
<Text className='login-title'>欢迎回来</Text>
|
<Text className='login-title'>{loginCopy.title}</Text>
|
||||||
<Text className='login-subtitle'>登录后继续刷题、查看错题和学习报告。</Text>
|
<Text className='login-subtitle'>{loginCopy.subtitle}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className='login-card list-stack'>
|
<View className='login-card list-stack'>
|
||||||
<Text className='row-main'>手机号登录</Text>
|
<Text className='row-main'>{loginCopy.formTitle}</Text>
|
||||||
<Text className='row-meta'>短信验证码将用于确认当前租户和账号身份。</Text>
|
<Text className='row-meta'>{loginCopy.formMeta}</Text>
|
||||||
{reason ? <Text className='error-text'>{reason}</Text> : null}
|
{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 || ''))} />
|
<Input className='input' type='number' maxlength={11} placeholder='请输入手机号' value={phone} onInput={event => setPhone(String(event.detail.value || ''))} />
|
||||||
<View className='toolbar'>
|
<View className='toolbar'>
|
||||||
|
|||||||
@@ -114,6 +114,48 @@
|
|||||||
min-width: 0;
|
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 {
|
.student-topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -2026,6 +2068,71 @@
|
|||||||
font-weight: 900;
|
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) {
|
@media (min-width: 900px) {
|
||||||
.student-page {
|
.student-page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -2068,6 +2175,10 @@
|
|||||||
padding: 40px;
|
padding: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page-admin .login-hero {
|
||||||
|
border-radius: 24px 0 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-card .input {
|
.login-card .input {
|
||||||
height: 52px;
|
height: 52px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|||||||
@@ -759,6 +759,94 @@
|
|||||||
border-radius: 9px;
|
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) {
|
@media (min-width: 980px) {
|
||||||
.admin-page {
|
.admin-page {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
@@ -779,4 +867,16 @@
|
|||||||
.admin-section .admin-list {
|
.admin-section .admin-list {
|
||||||
gap: 10px;
|
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