diff --git a/web/e2e/helpers.ts b/web/e2e/helpers.ts index a7bc767..5a57ec7 100644 --- a/web/e2e/helpers.ts +++ b/web/e2e/helpers.ts @@ -5,6 +5,18 @@ export const tinyWebP = Buffer.from('UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD export async function login(page: Page) { await page.goto('/admin/login') + await expect(page.getByRole('contentinfo')).toBeVisible() + const viewport = await page.evaluate(() => { + const loginPage = document.querySelector('.login-page') + return { + documentClientHeight: document.documentElement.clientHeight, + documentScrollHeight: document.documentElement.scrollHeight, + loginClientHeight: loginPage?.clientHeight ?? 0, + loginScrollHeight: loginPage?.scrollHeight ?? 0, + } + }) + expect(viewport.documentScrollHeight).toBeLessThanOrEqual(viewport.documentClientHeight) + expect(viewport.loginScrollHeight).toBeLessThanOrEqual(viewport.loginClientHeight) await page.getByLabel('管理员邮箱').fill('e2e@example.com') await page.getByLabel('密码').fill('imagesilo-e2e-password') await page.getByRole('button', { name: '登录' }).click() diff --git a/web/src/features/auth/login-page.test.tsx b/web/src/features/auth/login-page.test.tsx index 001ad73..e73aa6d 100644 --- a/web/src/features/auth/login-page.test.tsx +++ b/web/src/features/auth/login-page.test.tsx @@ -22,5 +22,7 @@ describe('LoginPage', () => { expect(screen.getByRole('img', { name: 'ImageSilo' })).toBeInTheDocument() expect(await screen.findByLabelText('管理员邮箱')).toBeInTheDocument() expect(screen.getByLabelText('密码')).toBeInTheDocument() + expect(screen.getByRole('contentinfo')).toHaveTextContent(/© 2026ImageSilo·License/) + expect(screen.getByRole('link', { name: 'Version: dev' })).toHaveAttribute('href', 'https://github.com/Willxup/imagesilo/releases') }) }) diff --git a/web/src/features/auth/login-page.tsx b/web/src/features/auth/login-page.tsx index 69deafd..f7e1265 100644 --- a/web/src/features/auth/login-page.tsx +++ b/web/src/features/auth/login-page.tsx @@ -3,6 +3,7 @@ import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { Navigate, useLocation, useNavigate } from 'react-router-dom' +import { AppFooter } from '../../components/app-footer' import { BrandLogo } from '../../components/brand-logo' import { Button } from '../../components/ui/button' import { DropdownItem, DropdownMenu } from '../../components/ui/dropdown-menu' @@ -65,7 +66,7 @@ export function LoginPage() { } return ( -
+
-
-
-
- -

{t('auth.signIn')}

-

{t('auth.signInDescription')}

+
+
+
+ + +

{t('auth.signIn')}

+

{t('auth.signInDescription')}

- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- {error ? : null} - - + {error ? : null} + + +
-
-
+ + + ) } diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 8838c94..2281c94 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -2554,15 +2554,31 @@ input[type="checkbox"]:disabled { display: flex; min-height: 100vh; min-height: 100dvh; - align-items: center; - justify-content: flex-end; - overflow-x: hidden; - overflow-y: auto; + flex-direction: column; + overflow-x: clip; background: var(--login-backdrop); - padding: 76px clamp(40px, 6vw, 96px) 52px; isolation: isolate; } +.login-page-content { + position: relative; + z-index: 2; + display: flex; + width: 100%; + min-height: 0; + flex: 1 0 auto; + align-items: center; + justify-content: flex-end; + padding: 72px clamp(40px, 6vw, 96px) clamp(12px, 3dvh, 28px); +} + +.login-page > .app-footer { + position: relative; + z-index: 2; + width: 100%; + padding-top: 12px; +} + .login-page::before, .login-page::after { position: absolute; @@ -2572,7 +2588,7 @@ input[type="checkbox"]:disabled { } .login-page::before { - inset: -8%; + inset: 0 -8%; background: url('/brand/imagesilo-login-mosaic.svg') center / cover no-repeat; filter: saturate(0.92) contrast(0.98); } @@ -2643,7 +2659,7 @@ input[type="checkbox"]:disabled { border: 1px solid var(--login-card-border); border-radius: 24px; background: var(--login-card); - padding: 42px 40px 40px; + padding: clamp(28px, 5dvh, 42px) 40px clamp(28px, 5dvh, 40px); box-shadow: 0 24px 72px rgba(35, 43, 56, 0.2), 0 4px 18px rgba(35, 43, 56, 0.08); backdrop-filter: blur(24px); } @@ -2691,7 +2707,7 @@ input[type="checkbox"]:disabled { } .login-form-title { - margin: 30px 0 0; + margin: clamp(20px, 4dvh, 30px) 0 0; color: var(--tail-text); font-size: 32px; font-weight: 600; @@ -2699,7 +2715,7 @@ input[type="checkbox"]:disabled { } .login-form-copy { - margin: 8px 0 28px; + margin: 8px 0 clamp(20px, 4dvh, 28px); color: var(--tail-text-soft); font-size: 12px; line-height: 18px; @@ -2821,10 +2837,10 @@ input[type="checkbox"]:disabled { min-height: 0; } - .login-page { + .login-page-content { align-items: center; justify-content: center; - padding: 92px 24px 40px; + padding: 92px 24px clamp(12px, 3dvh, 28px); } .login-page::before { @@ -2847,7 +2863,8 @@ input[type="checkbox"]:disabled { .login-form-wrap { min-height: 0; - padding: 42px 36px 40px; + padding-right: 36px; + padding-left: 36px; } } @@ -2923,9 +2940,9 @@ input[type="checkbox"]:disabled { top: 14px; } - .login-page { + .login-page-content { align-items: center; - padding: 64px 16px; + padding: 64px 16px clamp(8px, 2dvh, 16px); } .login-page::before {