fix: stabilize mobile panel height

This commit is contained in:
jxxghp
2026-06-17 07:01:33 +08:00
parent 60e2402aff
commit 2530c3bcd9
2 changed files with 33 additions and 60 deletions
+19 -27
View File
@@ -107,7 +107,6 @@ const HISTORY_TITLE_LENGTH = 36
const HISTORY_PREVIEW_LENGTH = 72 const HISTORY_PREVIEW_LENGTH = 72
const drawer = ref(false) const drawer = ref(false)
const drawerViewportHeight = ref('100dvh')
const inputText = ref('') const inputText = ref('')
const messages = ref<AgentChatMessage[]>([]) const messages = ref<AgentChatMessage[]>([])
const historySessions = ref<AgentSessionHistoryItem[]>([]) const historySessions = ref<AgentSessionHistoryItem[]>([])
@@ -144,7 +143,6 @@ const hasMessages = computed(() => messages.value.length > 0)
const hasHistorySessions = computed(() => historySessions.value.length > 0) const hasHistorySessions = computed(() => historySessions.value.length > 0)
const currentUserName = computed(() => userStore.getUserName || t('common.user')) const currentUserName = computed(() => userStore.getUserName || t('common.user'))
const drawerStyle = computed(() => ({ const drawerStyle = computed(() => ({
'--agent-assistant-viewport-height': drawerViewportHeight.value,
'--agent-assistant-panel-width': drawerWidth.value, '--agent-assistant-panel-width': drawerWidth.value,
})) }))
@@ -626,19 +624,6 @@ async function prepareAgentAttachments(items: AgentPendingAttachment[]): Promise
return { images, files, userAttachments } return { images, files, userAttachments }
} }
function getVisibleViewportHeight() {
if (typeof window === 'undefined') return '100dvh'
const height = window.visualViewport?.height || window.innerHeight || document.documentElement.clientHeight
return height > 0 ? `${Math.round(height)}px` : '100dvh'
}
// iOS 独立模式和地址栏收起时可见高度会变化,抽屉需要跟随真实 viewport。
function syncDrawerViewportHeight() {
drawerViewportHeight.value = getVisibleViewportHeight()
}
async function streamAgentMessage( async function streamAgentMessage(
text: string, text: string,
images: string[] = [], images: string[] = [],
@@ -874,13 +859,8 @@ watch(drawerWidth, () => {
onMounted(() => { onMounted(() => {
restoreHistorySessions() restoreHistorySessions()
restoreState() restoreState()
syncDrawerViewportHeight()
syncInputHeight() syncInputHeight()
window.addEventListener('resize', syncDrawerViewportHeight)
window.addEventListener('orientationchange', syncDrawerViewportHeight)
window.addEventListener('keydown', handleGlobalKeydown) window.addEventListener('keydown', handleGlobalKeydown)
window.visualViewport?.addEventListener('resize', syncDrawerViewportHeight)
window.visualViewport?.addEventListener('scroll', syncDrawerViewportHeight)
}) })
onScopeDispose(clearAgentAssistantOpenState) onScopeDispose(clearAgentAssistantOpenState)
@@ -888,11 +868,7 @@ onScopeDispose(clearPendingAttachments)
onScopeDispose(() => { onScopeDispose(() => {
if (typeof window === 'undefined') return if (typeof window === 'undefined') return
window.removeEventListener('resize', syncDrawerViewportHeight)
window.removeEventListener('orientationchange', syncDrawerViewportHeight)
window.removeEventListener('keydown', handleGlobalKeydown) window.removeEventListener('keydown', handleGlobalKeydown)
window.visualViewport?.removeEventListener('resize', syncDrawerViewportHeight)
window.visualViewport?.removeEventListener('scroll', syncDrawerViewportHeight)
}) })
</script> </script>
@@ -1269,16 +1245,26 @@ onScopeDispose(() => {
z-index: 2101; z-index: 2101;
overflow: hidden; overflow: hidden;
background: rgb(var(--v-theme-surface)); background: rgb(var(--v-theme-surface));
block-size: var(--agent-assistant-viewport-height, 100dvh) !important;
/* 背景层必须覆盖完整视口,不能跟随 iOS 键盘后的 visual viewport 缩短。 */
block-size: 100vh !important;
border-inline-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border-inline-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
box-shadow: var(--app-surface-shadow); box-shadow: var(--app-surface-shadow);
inline-size: var(--agent-assistant-panel-width, 30rem); inline-size: var(--agent-assistant-panel-width, 30rem);
inset-block-start: 0; inset-block-start: 0;
inset-inline-end: 0; inset-inline-end: 0;
max-block-size: var(--agent-assistant-viewport-height, 100dvh) !important; max-block-size: none !important;
min-block-size: 100vh !important;
overscroll-behavior: contain; overscroll-behavior: contain;
} }
@supports (block-size: 100lvh) {
.agent-assistant-panel {
block-size: 100lvh !important;
min-block-size: 100lvh !important;
}
}
.agent-assistant-shell { .agent-assistant-shell {
position: relative; position: relative;
display: grid; display: grid;
@@ -1345,7 +1331,7 @@ onScopeDispose(() => {
} }
.agent-assistant-history-list { .agent-assistant-history-list {
max-block-size: min(26rem, calc(var(--agent-assistant-viewport-height, 100dvh) - 7rem)); max-block-size: min(26rem, calc(100vh - 7rem));
overscroll-behavior: contain; overscroll-behavior: contain;
padding-block: 0.35rem; padding-block: 0.35rem;
@@ -1355,6 +1341,12 @@ onScopeDispose(() => {
} }
} }
@supports (block-size: 100lvh) {
.agent-assistant-history-list {
max-block-size: min(26rem, calc(100lvh - 7rem));
}
}
.agent-assistant-history-empty { .agent-assistant-history-empty {
color: rgba(var(--v-theme-on-surface), 0.58); color: rgba(var(--v-theme-on-surface), 0.58);
font-size: 0.85rem; font-size: 0.85rem;
+14 -33
View File
@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CSSProperties } from 'vue'
import { import {
themeCustomizerPrimaryColors, themeCustomizerPrimaryColors,
useThemeCustomizer, useThemeCustomizer,
@@ -35,20 +34,6 @@ const { appMode } = usePWA()
const { t } = useI18n() const { t } = useI18n()
const { global: globalTheme } = useTheme() const { global: globalTheme } = useTheme()
const defaultPrimaryColor = themeCustomizerPrimaryColors[0].value const defaultPrimaryColor = themeCustomizerPrimaryColors[0].value
const customizerViewportHeight = ref('100dvh')
function getVisibleViewportHeight() {
if (typeof window === 'undefined') return '100dvh'
const height = window.visualViewport?.height || window.innerHeight || document.documentElement.clientHeight
return height > 0 ? `${Math.round(height)}px` : '100dvh'
}
// iOS 小屏的可见视口会随地址栏和独立模式 safe area 变化,面板高度需要跟随真实可见高度。
function syncCustomizerViewportHeight() {
customizerViewportHeight.value = getVisibleViewportHeight()
}
// 将主题定制器打开状态同步到根节点,供全局悬浮按钮避让右侧面板。 // 将主题定制器打开状态同步到根节点,供全局悬浮按钮避让右侧面板。
function syncThemeCustomizerOpenState(isOpen: boolean) { function syncThemeCustomizerOpenState(isOpen: boolean) {
@@ -76,12 +61,7 @@ function handleGlobalKeydown(event: KeyboardEvent) {
} }
onMounted(() => { onMounted(() => {
syncCustomizerViewportHeight()
window.addEventListener('keydown', handleGlobalKeydown) window.addEventListener('keydown', handleGlobalKeydown)
window.addEventListener('resize', syncCustomizerViewportHeight)
window.addEventListener('orientationchange', syncCustomizerViewportHeight)
window.visualViewport?.addEventListener('resize', syncCustomizerViewportHeight)
window.visualViewport?.addEventListener('scroll', syncCustomizerViewportHeight)
}) })
onScopeDispose(clearThemeCustomizerOpenState) onScopeDispose(clearThemeCustomizerOpenState)
@@ -89,16 +69,8 @@ onScopeDispose(() => {
if (typeof window === 'undefined') return if (typeof window === 'undefined') return
window.removeEventListener('keydown', handleGlobalKeydown) window.removeEventListener('keydown', handleGlobalKeydown)
window.removeEventListener('resize', syncCustomizerViewportHeight)
window.removeEventListener('orientationchange', syncCustomizerViewportHeight)
window.visualViewport?.removeEventListener('resize', syncCustomizerViewportHeight)
window.visualViewport?.removeEventListener('scroll', syncCustomizerViewportHeight)
}) })
const customizerContainerStyle = computed<CSSProperties>(() => ({
'--theme-customizer-viewport-height': customizerViewportHeight.value,
}))
const themeOptions = computed<Array<{ icon: string; title: string; value: ThemeCustomizerTheme }>>(() => [ const themeOptions = computed<Array<{ icon: string; title: string; value: ThemeCustomizerTheme }>>(() => [
{ title: t('theme.light'), value: 'light', icon: 'mdi-white-balance-sunny' }, { title: t('theme.light'), value: 'light', icon: 'mdi-white-balance-sunny' },
{ title: t('theme.dark'), value: 'dark', icon: 'mdi-weather-night' }, { title: t('theme.dark'), value: 'dark', icon: 'mdi-weather-night' },
@@ -234,7 +206,6 @@ async function handleResetSettings() {
<template> <template>
<aside <aside
class="theme-customizer-panel-host" class="theme-customizer-panel-host"
:style="customizerContainerStyle"
role="dialog" role="dialog"
:aria-label="t('theme.customizer.title')" :aria-label="t('theme.customizer.title')"
> >
@@ -443,13 +414,23 @@ async function handleResetSettings() {
overflow: hidden; overflow: hidden;
border-radius: 0; border-radius: 0;
background: rgb(var(--v-theme-surface)); background: rgb(var(--v-theme-surface));
block-size: var(--theme-customizer-viewport-height, 100dvh) !important;
/* 背景层保持完整视口高度,避免 iOS 键盘触发 visual viewport resize 后露出底层页面。 */
block-size: 100vh !important;
border-inline-start: 1px solid rgba(var(--v-theme-on-surface), 0.08) !important; border-inline-start: 1px solid rgba(var(--v-theme-on-surface), 0.08) !important;
box-shadow: var(--app-surface-shadow) !important; box-shadow: var(--app-surface-shadow) !important;
inline-size: 420px !important; inline-size: 420px !important;
inset-block-start: 0 !important; inset-block-start: 0 !important;
inset-inline-end: 0 !important; inset-inline-end: 0 !important;
max-block-size: var(--theme-customizer-viewport-height, 100dvh) !important; max-block-size: none !important;
min-block-size: 100vh !important;
}
@supports (block-size: 100lvh) {
.theme-customizer-panel-host {
block-size: 100lvh !important;
min-block-size: 100lvh !important;
}
} }
.theme-customizer-panel { .theme-customizer-panel {
@@ -467,12 +448,12 @@ async function handleResetSettings() {
max-block-size: 100%; max-block-size: 100%;
/* 独立 App 模式会贴近 viewport-fit=cover 顶部,面板内部需要避开 iOS 状态栏。 */ /* 独立 App 模式会贴近 viewport-fit=cover 顶部,面板内部需要避开 iOS 状态栏。 */
padding-block-start: env(safe-area-inset-top); padding-block-start: env(safe-area-inset-top, 0px);
} }
.theme-customizer-panel--dialog .theme-customizer-body { .theme-customizer-panel--dialog .theme-customizer-body {
block-size: auto; block-size: auto;
padding-block-end: env(safe-area-inset-bottom); padding-block-end: env(safe-area-inset-bottom, 0px);
} }
@media (width <= 600px) { @media (width <= 600px) {