From b0a27c64052b31daa63b1493a6eec065669d6214 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 14 Jul 2026 21:25:27 +0800 Subject: [PATCH] fix: optimize mobile input layout --- src/pages/login.vue | 93 +++++++++++++++++++++++++-------- src/plugins/vuetify/AppInput.ts | 5 ++ src/styles/common.scss | 27 +++++++++- 3 files changed, 102 insertions(+), 23 deletions(-) diff --git a/src/pages/login.vue b/src/pages/login.vue index 3a6e64d9..f1171b94 100644 --- a/src/pages/login.vue +++ b/src/pages/login.vue @@ -797,13 +797,15 @@ onUnmounted(() => {
-
@@ -1068,6 +1070,7 @@ onUnmounted(() => { display: flex; align-items: center; justify-content: center; + animation: logo-enter 700ms cubic-bezier(0.16, 1, 0.3, 1) 100ms both; margin-block-end: 8px; /* Logo 背后的柔光环 */ @@ -1114,6 +1117,7 @@ onUnmounted(() => { .login-title { margin: 0; + animation: text-enter 600ms cubic-bezier(0.16, 1, 0.3, 1) 200ms both; background: linear-gradient(135deg, rgb(var(--v-theme-on-surface)) 30%, rgba(var(--v-theme-primary), 1) 100%); background-clip: text; font-size: 1.85rem; @@ -1125,6 +1129,7 @@ onUnmounted(() => { } .login-subtitle { + animation: text-enter 600ms cubic-bezier(0.16, 1, 0.3, 1) 300ms both; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)); font-size: 0.875rem; font-weight: 400; @@ -1227,6 +1232,66 @@ onUnmounted(() => { outline: none; } +/* 原生保持登录复选框,避免使用全局 VCheckbox 小屏适配布局。 */ +.native-login-checkbox { + display: inline-flex; + align-items: center; + color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)); + cursor: pointer; + gap: 10px; + min-block-size: 40px; + user-select: none; +} + +.native-login-checkbox__input { + position: relative; + display: inline-grid; + flex: 0 0 18px; + border: 2px solid rgba(var(--v-theme-on-surface), 0.54); + border-radius: 4px; + margin: 0; + appearance: none; + background: transparent; + block-size: 18px; + cursor: pointer; + inline-size: 18px; + place-content: center; + transition: + background-color 150ms ease, + border-color 150ms ease, + box-shadow 150ms ease; +} + +.native-login-checkbox__input::before { + block-size: 5px; + border-block-end: 2px solid rgb(var(--v-theme-on-primary)); + border-inline-start: 2px solid rgb(var(--v-theme-on-primary)); + content: ''; + inline-size: 9px; + transform: translateY(-1px) rotate(-45deg) scale(0); + transform-origin: center; + transition: transform 120ms ease; +} + +.native-login-checkbox__input:checked { + border-color: rgb(var(--v-theme-primary)); + background-color: rgb(var(--v-theme-primary)); +} + +.native-login-checkbox__input:checked::before { + transform: translateY(-1px) rotate(-45deg) scale(1); +} + +.native-login-checkbox__input:focus-visible { + box-shadow: 0 0 0 3px rgba(var(--v-theme-primary), 0.18); + outline: none; +} + +.native-login-checkbox__label { + font-size: 0.9375rem; + line-height: 1.4; +} + /* Remember me 复选框样式优化 */ .login-checkbox { opacity: 0.85; @@ -1383,11 +1448,6 @@ onUnmounted(() => { } } -/* Logo 入场 */ -.login-logo-wrapper { - animation: logo-enter 700ms cubic-bezier(0.16, 1, 0.3, 1) 100ms both; -} - @keyframes logo-enter { 0% { opacity: 0; @@ -1400,15 +1460,6 @@ onUnmounted(() => { } } -/* 标题入场 */ -.login-title { - animation: text-enter 600ms cubic-bezier(0.16, 1, 0.3, 1) 200ms both; -} - -.login-subtitle { - animation: text-enter 600ms cubic-bezier(0.16, 1, 0.3, 1) 300ms both; -} - @keyframes text-enter { 0% { opacity: 0; diff --git a/src/plugins/vuetify/AppInput.ts b/src/plugins/vuetify/AppInput.ts index 49dcec51..80e1fc9d 100644 --- a/src/plugins/vuetify/AppInput.ts +++ b/src/plugins/vuetify/AppInput.ts @@ -4,6 +4,8 @@ import { useDisplay } from 'vuetify' type ResponsiveInputKind = 'choice' | 'field' | 'group' | 'multiline' | 'range' +const MOBILE_EMPTY_PLACEHOLDER = '-' + interface ResponsiveInputOptions { kind: ResponsiveInputKind name: string @@ -123,6 +125,9 @@ export function createResponsiveInputAdapter(component: Component, options: Resp if (options.kind === 'field' || options.kind === 'multiline') { controlAttrs.variant = 'plain' controlAttrs.singleLine = true + if (!hasDisplayText(controlAttrs.placeholder)) { + controlAttrs.placeholder = MOBILE_EMPTY_PLACEHOLDER + } } if (showHint) { diff --git a/src/styles/common.scss b/src/styles/common.scss index 08445433..b28e1df4 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -567,8 +567,12 @@ html[data-theme-radius='extra'] { } .app-responsive-input--empty:not(:has(.v-field--dirty)) .v-field { - background-color: rgba(var(--v-theme-on-surface), 0.035); - box-shadow: inset 0 0 0 1px rgba(var(--v-theme-on-surface), 0.14); + background-color: transparent; +} + +.app-responsive-input--empty:not(:has(.v-field--dirty)) .v-field__input::placeholder { + color: rgba(var(--v-theme-on-surface), 0.5); + opacity: 1; } .app-responsive-input--field .v-field__outline, @@ -644,6 +648,25 @@ html[data-theme-radius='extra'] { flex: 0 0 100%; max-width: 100%; } + + // 桌面也占满 12 格的字段在移动端改为上下两行,避免长输入只挤在右侧。 + .v-row > :is( + .v-col-12:not([class*='v-col-sm-']):not([class*='v-col-md-']):not([class*='v-col-lg-']):not([class*='v-col-xl-']):not([class*='v-col-xxl-']), + .v-col-sm-12:not([class*='v-col-md-']):not([class*='v-col-lg-']):not([class*='v-col-xl-']):not([class*='v-col-xxl-']), + .v-col-md-12:not([class*='v-col-lg-']):not([class*='v-col-xl-']):not([class*='v-col-xxl-']) + ) > :is(.app-responsive-input--field, .app-responsive-input--range) { + row-gap: 0.5rem; + align-items: start; + grid-template-columns: minmax(0, 1fr); + } + + .v-row > :is( + .v-col-12:not([class*='v-col-sm-']):not([class*='v-col-md-']):not([class*='v-col-lg-']):not([class*='v-col-xl-']):not([class*='v-col-xxl-']), + .v-col-sm-12:not([class*='v-col-md-']):not([class*='v-col-lg-']):not([class*='v-col-xl-']):not([class*='v-col-xxl-']), + .v-col-md-12:not([class*='v-col-lg-']):not([class*='v-col-xl-']):not([class*='v-col-xxl-']) + ) > :is(.app-responsive-input--field, .app-responsive-input--range) .app-responsive-input__control { + inline-size: 100%; + } } @media (hover: hover) {