diff --git a/apps/user-h5/index.html b/apps/user-h5/index.html index d79c458..527df1b 100644 --- a/apps/user-h5/index.html +++ b/apps/user-h5/index.html @@ -4,6 +4,31 @@ 愈心谷 + diff --git a/apps/user-h5/src/App.vue b/apps/user-h5/src/App.vue index b2381ad..62c0e78 100644 --- a/apps/user-h5/src/App.vue +++ b/apps/user-h5/src/App.vue @@ -17,10 +17,9 @@ import TabBar from './components/TabBar.vue' import { isMpEmbed, isMpNativeHeader } from './lib/mpEmbed' const mpEmbed = isMpEmbed() -const mpNativeHeader = isMpNativeHeader() -const showHeader = !mpNativeHeader +const showHeader = !isMpNativeHeader() const shellClass = computed(() => ({ 'mp-embed': mpEmbed, - 'mp-native-header': mpNativeHeader, + 'mp-native-header': isMpNativeHeader(), })) diff --git a/apps/user-h5/src/lib/mpEmbed.spec.ts b/apps/user-h5/src/lib/mpEmbed.spec.ts index 5750776..7a005eb 100644 --- a/apps/user-h5/src/lib/mpEmbed.spec.ts +++ b/apps/user-h5/src/lib/mpEmbed.spec.ts @@ -28,7 +28,7 @@ describe('mpEmbed', () => { expect(document.documentElement.style.getPropertyValue('--yxg-safe-top')).toBe('47px') }) - it('uses native mini-program header mode with ?nh=1', () => { + it('uses native mini-program cover header with ?nh=1', () => { window.history.replaceState({}, '', '/psy/?mp=1&nh=1') initMpEmbed() expect(isMpNativeHeader()).toBe(true) @@ -37,6 +37,17 @@ describe('mpEmbed', () => { expect(document.documentElement.style.getPropertyValue('--yxg-page-top')).toBe('4px') }) + it('offsets content below native cover bar with ?nh=1&sbh=', () => { + window.history.replaceState({}, '', '/psy/?mp=1&nh=1&sbh=87') + initMpEmbed() + expect(isMpNativeHeader()).toBe(true) + expect(sessionStorage.getItem('yxg_sbh')).toBe('87') + expect(document.documentElement.style.getPropertyValue('--yxg-page-top')).toBe('87px') + window.history.replaceState({}, '', '/psy/?mp=1') + applyMpEmbedDom() + expect(document.documentElement.style.getPropertyValue('--yxg-page-top')).toBe('87px') + }) + it('returns false without mp query', () => { initMpEmbed() expect(isMpEmbed()).toBe(false) diff --git a/apps/user-h5/src/lib/mpEmbed.ts b/apps/user-h5/src/lib/mpEmbed.ts index d9059b8..d5f72b5 100644 --- a/apps/user-h5/src/lib/mpEmbed.ts +++ b/apps/user-h5/src/lib/mpEmbed.ts @@ -7,12 +7,17 @@ const SBH_KEY = 'yxg_sbh' const HEADER_H_PX = 50 const MP_CONTENT_TOP_PX = 4 -/** Native cover-view handles logo; H5 only needs a tiny gap below web-view top. */ +/** 小程序 cover 顶栏承载 logo,H5 顶栏仅留极小间距 */ function applyNativeHeaderMode(): void { + applyNativeHeaderInset(MP_CONTENT_TOP_PX) +} + +/** nh=1 且带 sbh 时,内容从原生顶栏下方开始 */ +function applyNativeHeaderInset(topPx: number): void { if (typeof document === 'undefined') return const root = document.documentElement root.style.setProperty('--yxg-safe-top', '0px') - root.style.setProperty('--yxg-page-top', `${MP_CONTENT_TOP_PX}px`) + root.style.setProperty('--yxg-page-top', `${topPx}px`) } function applySafeTopInset(px: number): void { @@ -30,27 +35,29 @@ function readStoredSbh(): number { return parseInt(raw, 10) } -/** True when mini-program renders the brand header above web-view. */ export function isMpNativeHeader(): boolean { if (typeof window === 'undefined') return false return sessionStorage.getItem(MP_NATIVE_HEADER_KEY) === '1' } -/** Apply document-level embed chrome (safe-area wash, hide HTML title). */ export function applyMpEmbedDom(): void { if (typeof document === 'undefined' || !isMpEmbed()) return document.title = '\u200b' document.documentElement.classList.add('mp-embed') if (isMpNativeHeader()) { document.documentElement.classList.add('mp-native-header') - applyNativeHeaderMode() + const stored = readStoredSbh() + if (stored > 0) { + applyNativeHeaderInset(stored) + } else { + applyNativeHeaderMode() + } return } const stored = readStoredSbh() if (stored > 0) applySafeTopInset(stored) } -/** Persist ?mp=1 from mini-program web-view entry. Call once before router mount. */ export function initMpEmbed(): void { if (typeof window === 'undefined') return const params = new URLSearchParams(window.location.search) @@ -59,7 +66,13 @@ export function initMpEmbed(): void { } if (params.get(NH_QUERY) === '1') { sessionStorage.setItem(MP_NATIVE_HEADER_KEY, '1') - applyNativeHeaderMode() + const nativeSbh = params.get(SBH_QUERY) + if (nativeSbh && /^\d+$/.test(nativeSbh)) { + sessionStorage.setItem(SBH_KEY, nativeSbh) + applyNativeHeaderInset(parseInt(nativeSbh, 10)) + } else { + applyNativeHeaderMode() + } } if (!isMpNativeHeader()) { const sbh = params.get(SBH_QUERY) @@ -71,7 +84,6 @@ export function initMpEmbed(): void { applyMpEmbedDom() } -/** True when H5 runs inside WeChat mini-program web-view (no H5 tab bar). */ export function isMpEmbed(): boolean { if (typeof window === 'undefined') return false return sessionStorage.getItem(MP_EMBED_KEY) === '1' diff --git a/apps/user-h5/src/styles/app.css b/apps/user-h5/src/styles/app.css index 114d560..5e24113 100644 --- a/apps/user-h5/src/styles/app.css +++ b/apps/user-h5/src/styles/app.css @@ -64,6 +64,14 @@ html.mp-embed body{ padding-top:var(--yxg-page-top); min-height:100%; } +html.mp-embed.mp-native-header .app-shell .home, +html.mp-embed.mp-native-header .app-shell .ps, +html.mp-embed.mp-native-header .app-shell .yxg-page{ + padding-top:var(--yxg-page-top); +} +html.mp-embed.mp-native-header .app-header{ + display:none !important; +} .app-footer{ position:relative; z-index:1; diff --git a/docs/LOCAL-DEV-MINI-PROGRAM-H5.md b/docs/LOCAL-DEV-MINI-PROGRAM-H5.md index 673821c..f4458b9 100644 --- a/docs/LOCAL-DEV-MINI-PROGRAM-H5.md +++ b/docs/LOCAL-DEV-MINI-PROGRAM-H5.md @@ -1,8 +1,35 @@ # 愈心魔方 H5 · 本地联调(免每次改服务器) -改 H5 时**不必**先部署线上;用本机 Vite + 微信开发者工具即可预览。 +## 模式 A:本地小程序 → 远程 H5(默认) -## 日常流程 +只改小程序、不动 H5 时用这个。`util/yxgConfig.local.js` 保持: + +```js +export const USE_LOCAL_H5 = false +``` + +微信开发者工具 **编译运行** → 愈心魔方 Tab 加载 `https://h5.yuxingu.com.cn/psy/?mp=1&nh=1&v=…` + +**不必**开 `npm run dev:mp`;业务域名 / request 域名按线上一致(可不勾「不校验合法域名」)。 + +## 模式 B:本地小程序 → 本机 Vite + +改 H5 时要热更新: + +```bash +cd digital-psychology && npm run dev:mp +``` + +`util/yxgConfig.local.js`: + +```js +export const USE_LOCAL_H5 = true +export const LOCAL_H5_HOST = '192.168.x.x:5173' // 与 dev:mp 打印一致 +``` + +微信开发者工具:勾选「不校验合法域名、web-view…」。 + +## 日常流程(模式 B) ```bash # 终端 1:H5(会打印局域网 IP) @@ -19,19 +46,7 @@ npm run dev:api ├── 打开 yuxingu-Miniprogram-dev ├── 详情 → 本地设置 → 勾选「不校验合法域名、web-view…」 ├── 编译运行(开发模式,非发行) -└── 打开 Tab「愈心魔方」→ 自动加载本机 http://<你的IP>:5173/psy/ -``` - -小程序侧规则(已配置): - -- **开发编译**(`import.meta.env.DEV`)→ `util/yxgConfig.local.js` 里的局域网地址 -- **发行 / 上传体验版** → 自动使用 `https://h5.yuxingu.com.cn/psy/` - -只需改一次 `yuxingu-Miniprogram-dev/util/yxgConfig.local.js`: - -```js -export const LOCAL_H5_HOST = '192.168.x.x:5173' // 与 dev:mp 打印一致 -``` +└── 打开 Tab「愈心魔方」 ## 浏览器自测 embed