fix(h5): 修复 WebView 顶栏 logo 与内容重叠

embed 模式 header 改文档流、移除负 margin;持久化 sbh 并在路由中保留,避免胶囊区与档案条重叠。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-25 01:22:39 +08:00
co-authored by Cursor
parent 9d4fb52d29
commit f4bd57ce1b
7 changed files with 48 additions and 14 deletions
+8 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<header class="app-header"> <header class="app-header" :class="{ 'is-embed': mpEmbed }">
<div class="side left"> <div class="side left">
<BackButton v-if="showBack" /> <BackButton v-if="showBack" />
</div> </div>
@@ -15,8 +15,10 @@ import { computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import BackButton from './BackButton.vue' import BackButton from './BackButton.vue'
import BrandLogo from './BrandLogo.vue' import BrandLogo from './BrandLogo.vue'
import { isMpEmbed } from '../lib/mpEmbed'
const route = useRoute() const route = useRoute()
const mpEmbed = isMpEmbed()
/** 首页 / 我的 为 Tab 根页不显示返回;探索可从首页进入,显示返回 */ /** 首页 / 我的 为 Tab 根页不显示返回;探索可从首页进入,显示返回 */
const showBack = computed(() => { const showBack = computed(() => {
@@ -40,6 +42,11 @@ const showBack = computed(() => {
background: transparent; background: transparent;
pointer-events: none; pointer-events: none;
} }
.app-header.is-embed {
position: relative;
padding: calc(6px + var(--yxg-safe-top)) 12px 8px;
background: transparent;
}
.side { .side {
display: flex; display: flex;
align-items: center; align-items: center;
+1 -3
View File
@@ -35,16 +35,14 @@ withDefaults(
.ps { .ps {
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
margin-top: calc(-1 * var(--yxg-safe-top));
padding-top: var(--yxg-page-top); padding-top: var(--yxg-page-top);
padding-bottom: var(--spacing-xs); padding-bottom: var(--spacing-xs);
min-height: calc(100% + var(--yxg-safe-top)); min-height: 100%;
background: var(--yxg-page-wash); background: var(--yxg-page-wash);
} }
.ps-flat { .ps-flat {
background: transparent; background: transparent;
min-height: 0; min-height: 0;
margin-top: calc(-1 * var(--yxg-safe-top));
padding-top: var(--yxg-page-top); padding-top: var(--yxg-page-top);
} }
.atm { .atm {
+6 -2
View File
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { initMpEmbed, isMpEmbed } from './mpEmbed' import { applyMpEmbedDom, initMpEmbed, isMpEmbed } from './mpEmbed'
describe('mpEmbed', () => { describe('mpEmbed', () => {
beforeEach(() => { beforeEach(() => {
@@ -17,11 +17,15 @@ describe('mpEmbed', () => {
expect(isMpEmbed()).toBe(true) expect(isMpEmbed()).toBe(true)
}) })
it('applies status bar height from ?sbh=', () => { it('persists status bar height from ?sbh=', () => {
window.history.replaceState({}, '', '/psy/?mp=1&sbh=47') window.history.replaceState({}, '', '/psy/?mp=1&sbh=47')
initMpEmbed() initMpEmbed()
expect(document.documentElement.style.getPropertyValue('--yxg-safe-top')).toBe('47px') expect(document.documentElement.style.getPropertyValue('--yxg-safe-top')).toBe('47px')
expect(document.documentElement.style.getPropertyValue('--yxg-page-top')).toBe('calc(47px + 50px)') expect(document.documentElement.style.getPropertyValue('--yxg-page-top')).toBe('calc(47px + 50px)')
expect(sessionStorage.getItem('yxg_sbh')).toBe('47')
window.history.replaceState({}, '', '/psy/?mp=1')
applyMpEmbedDom()
expect(document.documentElement.style.getPropertyValue('--yxg-safe-top')).toBe('47px')
}) })
it('returns false without mp query', () => { it('returns false without mp query', () => {
+11
View File
@@ -1,6 +1,7 @@
const MP_EMBED_KEY = 'yxg_mp_embed' const MP_EMBED_KEY = 'yxg_mp_embed'
const MP_QUERY = 'mp' const MP_QUERY = 'mp'
const SBH_QUERY = 'sbh' const SBH_QUERY = 'sbh'
const SBH_KEY = 'yxg_sbh'
const HEADER_H_PX = 50 const HEADER_H_PX = 50
function applySafeTopInset(px: number): void { function applySafeTopInset(px: number): void {
@@ -11,11 +12,20 @@ function applySafeTopInset(px: number): void {
root.style.setProperty('--yxg-page-top', `calc(${safe} + ${HEADER_H_PX}px)`) root.style.setProperty('--yxg-page-top', `calc(${safe} + ${HEADER_H_PX}px)`)
} }
function readStoredSbh(): number {
if (typeof sessionStorage === 'undefined') return 0
const raw = sessionStorage.getItem(SBH_KEY)
if (!raw || !/^\d+$/.test(raw)) return 0
return parseInt(raw, 10)
}
/** Apply document-level embed chrome (safe-area wash, hide HTML title). */ /** Apply document-level embed chrome (safe-area wash, hide HTML title). */
export function applyMpEmbedDom(): void { export function applyMpEmbedDom(): void {
if (typeof document === 'undefined' || !isMpEmbed()) return if (typeof document === 'undefined' || !isMpEmbed()) return
document.title = '\u200b' document.title = '\u200b'
document.documentElement.classList.add('mp-embed') document.documentElement.classList.add('mp-embed')
const stored = readStoredSbh()
if (stored > 0) applySafeTopInset(stored)
} }
/** Persist ?mp=1 from mini-program web-view entry. Call once before router mount. */ /** Persist ?mp=1 from mini-program web-view entry. Call once before router mount. */
@@ -27,6 +37,7 @@ export function initMpEmbed(): void {
} }
const sbh = params.get(SBH_QUERY) const sbh = params.get(SBH_QUERY)
if (sbh && /^\d+$/.test(sbh)) { if (sbh && /^\d+$/.test(sbh)) {
sessionStorage.setItem(SBH_KEY, sbh)
applySafeTopInset(parseInt(sbh, 10)) applySafeTopInset(parseInt(sbh, 10))
} }
applyMpEmbedDom() applyMpEmbedDom()
+1 -2
View File
@@ -48,10 +48,9 @@ const {
.home { .home {
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
margin-top: calc(-1 * var(--yxg-safe-top));
padding-top: var(--yxg-page-top); padding-top: var(--yxg-page-top);
padding-bottom: var(--spacing-xs); padding-bottom: var(--spacing-xs);
min-height: calc(100% + var(--yxg-safe-top)); min-height: 100%;
background: var(--yxg-page-wash); background: var(--yxg-page-wash);
} }
.atm { .atm {
+14 -2
View File
@@ -50,8 +50,20 @@ const router = createRouter({
}) })
router.beforeEach((to) => { router.beforeEach((to) => {
if (!isMpEmbed() || to.query.mp === '1') return true if (!isMpEmbed()) return true
return { path: to.path, query: { ...to.query, mp: '1' }, hash: to.hash, replace: true } const q = { ...to.query }
let changed = false
if (q.mp !== '1') {
q.mp = '1'
changed = true
}
const storedSbh = sessionStorage.getItem('yxg_sbh')
if (storedSbh && q.sbh !== storedSbh) {
q.sbh = storedSbh
changed = true
}
if (!changed) return true
return { path: to.path, query: q, hash: to.hash, replace: true }
}) })
export default router export default router
+7 -4
View File
@@ -52,15 +52,18 @@ html.mp-embed body{
.app-shell.mp-embed::before{ .app-shell.mp-embed::before{
content:""; content:"";
position:fixed; position:fixed;
top:calc(-1 * var(--yxg-safe-top)); inset:0;
left:0;
right:0;
bottom:0;
z-index:0; z-index:0;
pointer-events:none; pointer-events:none;
background:var(--yxg-body-wash); background:var(--yxg-body-wash);
background-color:var(--color-bg-start); background-color:var(--color-bg-start);
} }
.app-shell.mp-embed .home,
.app-shell.mp-embed .ps{
margin-top:0;
padding-top:8px;
min-height:100%;
}
.app-footer{ .app-footer{
position:relative; position:relative;
z-index:1; z-index:1;