From 57687de36069c22e3144bca03bbf2a67d6485b1e Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Tue, 25 Aug 2026 01:38:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(h5):=20=E5=B0=8F=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=E9=A1=B6=E6=A0=8F=20+=20=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E8=81=94=E8=B0=83=E4=B8=8E=E4=B8=80=E9=94=AE=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nh=1 隐藏 H5 顶栏并由 cover 承载 logo;H5 仅 4px 顶距避免重复留白。新增 dev:mp、deploy:h5 与联调文档。 Co-authored-by: Cursor --- .gitignore | 1 + apps/user-h5/src/App.vue | 13 +++++-- apps/user-h5/src/lib/mpEmbed.spec.ts | 11 +++++- apps/user-h5/src/lib/mpEmbed.ts | 36 ++++++++++++++++-- apps/user-h5/src/router/index.ts | 6 ++- apps/user-h5/src/styles/app.css | 2 +- deploy.env.example | 6 +++ docs/DEPLOY-MINI-PROGRAM-H5.md | 2 + docs/LOCAL-DEV-MINI-PROGRAM-H5.md | 56 ++++++++++++++++++++++++++++ package.json | 2 + scripts/deploy-h5.sh | 26 +++++++++++++ scripts/dev-mp-h5.sh | 37 ++++++++++++++++++ 12 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 deploy.env.example create mode 100644 docs/LOCAL-DEV-MINI-PROGRAM-H5.md create mode 100755 scripts/deploy-h5.sh create mode 100755 scripts/dev-mp-h5.sh diff --git a/.gitignore b/.gitignore index 00cce24..411a413 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ .venv/ *.log .env +deploy.env .DS_Store # ESS — machine-local root pointer(仓库内用 scripts 包装器) diff --git a/apps/user-h5/src/App.vue b/apps/user-h5/src/App.vue index 0d42d8e..b2381ad 100644 --- a/apps/user-h5/src/App.vue +++ b/apps/user-h5/src/App.vue @@ -1,7 +1,7 @@ diff --git a/apps/user-h5/src/lib/mpEmbed.spec.ts b/apps/user-h5/src/lib/mpEmbed.spec.ts index 764ff17..5750776 100644 --- a/apps/user-h5/src/lib/mpEmbed.spec.ts +++ b/apps/user-h5/src/lib/mpEmbed.spec.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import { applyMpEmbedDom, initMpEmbed, isMpEmbed } from './mpEmbed' +import { applyMpEmbedDom, initMpEmbed, isMpEmbed, isMpNativeHeader } from './mpEmbed' describe('mpEmbed', () => { beforeEach(() => { @@ -28,6 +28,15 @@ describe('mpEmbed', () => { expect(document.documentElement.style.getPropertyValue('--yxg-safe-top')).toBe('47px') }) + it('uses native mini-program header mode with ?nh=1', () => { + window.history.replaceState({}, '', '/psy/?mp=1&nh=1') + initMpEmbed() + expect(isMpNativeHeader()).toBe(true) + expect(document.documentElement.classList.contains('mp-native-header')).toBe(true) + expect(document.documentElement.style.getPropertyValue('--yxg-safe-top')).toBe('0px') + expect(document.documentElement.style.getPropertyValue('--yxg-page-top')).toBe('4px') + }) + 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 7e1d021..d9059b8 100644 --- a/apps/user-h5/src/lib/mpEmbed.ts +++ b/apps/user-h5/src/lib/mpEmbed.ts @@ -1,8 +1,19 @@ const MP_EMBED_KEY = 'yxg_mp_embed' +const MP_NATIVE_HEADER_KEY = 'yxg_mp_native_header' const MP_QUERY = 'mp' +const NH_QUERY = 'nh' const SBH_QUERY = 'sbh' 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. */ +function applyNativeHeaderMode(): 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`) +} function applySafeTopInset(px: number): void { if (typeof document === 'undefined' || px <= 0) return @@ -19,11 +30,22 @@ 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() + return + } const stored = readStoredSbh() if (stored > 0) applySafeTopInset(stored) } @@ -35,10 +57,16 @@ export function initMpEmbed(): void { if (params.get(MP_QUERY) === '1') { sessionStorage.setItem(MP_EMBED_KEY, '1') } - const sbh = params.get(SBH_QUERY) - if (sbh && /^\d+$/.test(sbh)) { - sessionStorage.setItem(SBH_KEY, sbh) - applySafeTopInset(parseInt(sbh, 10)) + if (params.get(NH_QUERY) === '1') { + sessionStorage.setItem(MP_NATIVE_HEADER_KEY, '1') + applyNativeHeaderMode() + } + if (!isMpNativeHeader()) { + const sbh = params.get(SBH_QUERY) + if (sbh && /^\d+$/.test(sbh)) { + sessionStorage.setItem(SBH_KEY, sbh) + applySafeTopInset(parseInt(sbh, 10)) + } } applyMpEmbedDom() } diff --git a/apps/user-h5/src/router/index.ts b/apps/user-h5/src/router/index.ts index 40195a7..253755c 100644 --- a/apps/user-h5/src/router/index.ts +++ b/apps/user-h5/src/router/index.ts @@ -57,8 +57,12 @@ router.beforeEach((to) => { q.mp = '1' changed = true } + if (sessionStorage.getItem('yxg_mp_native_header') === '1' && q.nh !== '1') { + q.nh = '1' + changed = true + } const storedSbh = sessionStorage.getItem('yxg_sbh') - if (storedSbh && q.sbh !== storedSbh) { + if (storedSbh && q.sbh !== storedSbh && q.nh !== '1') { q.sbh = storedSbh changed = true } diff --git a/apps/user-h5/src/styles/app.css b/apps/user-h5/src/styles/app.css index ec44168..114d560 100644 --- a/apps/user-h5/src/styles/app.css +++ b/apps/user-h5/src/styles/app.css @@ -61,7 +61,7 @@ html.mp-embed body{ .app-shell.mp-embed .home, .app-shell.mp-embed .ps{ margin-top:0; - padding-top:8px; + padding-top:var(--yxg-page-top); min-height:100%; } .app-footer{ diff --git a/deploy.env.example b/deploy.env.example new file mode 100644 index 0000000..68f12af --- /dev/null +++ b/deploy.env.example @@ -0,0 +1,6 @@ +# 复制为 deploy.env(勿提交 deploy.env) +# 用法: ./scripts/deploy-h5.sh 或 DEPLOY_ENV=./deploy.env ./scripts/deploy-h5.sh + +H5_DEPLOY_USER=ubuntu +H5_DEPLOY_HOST=your-server.example.com +H5_DEPLOY_PATH=/var/www/h5.yuxingu.com.cn/psy diff --git a/docs/DEPLOY-MINI-PROGRAM-H5.md b/docs/DEPLOY-MINI-PROGRAM-H5.md index 538f47e..575d65e 100644 --- a/docs/DEPLOY-MINI-PROGRAM-H5.md +++ b/docs/DEPLOY-MINI-PROGRAM-H5.md @@ -2,6 +2,8 @@ 适用于:**微信小程序**(`yuxingu-Miniprogram-dev`)嵌入 **愈心魔方 H5**(`digital-psychology/apps/user-h5`)的展示期上线。 +**本地联调(免每次改服务器)** → 见 [LOCAL-DEV-MINI-PROGRAM-H5.md](LOCAL-DEV-MINI-PROGRAM-H5.md) · `npm run dev:mp` + --- ## 1. 架构 diff --git a/docs/LOCAL-DEV-MINI-PROGRAM-H5.md b/docs/LOCAL-DEV-MINI-PROGRAM-H5.md new file mode 100644 index 0000000..673821c --- /dev/null +++ b/docs/LOCAL-DEV-MINI-PROGRAM-H5.md @@ -0,0 +1,56 @@ +# 愈心魔方 H5 · 本地联调(免每次改服务器) + +改 H5 时**不必**先部署线上;用本机 Vite + 微信开发者工具即可预览。 + +## 日常流程 + +```bash +# 终端 1:H5(会打印局域网 IP) +cd digital-psychology +npm run dev:mp + +# 终端 2(需要登录/接口时) +npm run dev:api +# 或仅数据库: npm run deps:up && npm run dev:api +``` + +```text +微信开发者工具 +├── 打开 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 打印一致 +``` + +## 浏览器自测 embed + +```text +http://localhost:5173/psy/?mp=1&sbh=88 +``` + +## 确认要上线时(一条命令) + +```bash +cp deploy.env.example deploy.env # 首次填写服务器 +chmod +x scripts/deploy-h5.sh +./scripts/deploy-h5.sh +``` + +## 局限 + +| 项 | 说明 | +|----|------| +| 真机预览 | 手机与电脑同一 WiFi;开发者工具可勾选「真机调试」 | +| 业务域名 | 本地为 `http`,仅开发模式 + 关闭域名校验时可用 | +| 与线上一致 | 发行前仍建议在体验版走 HTTPS 域名验收一次 | diff --git a/package.json b/package.json index 7cb681a..a1408d4 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ ], "scripts": { "dev:h5": "npm run dev -w @yuxingu/user-h5", + "dev:mp": "bash scripts/dev-mp-h5.sh", "build:h5": "npm run build -w @yuxingu/user-h5", + "deploy:h5": "bash scripts/deploy-h5.sh", "dev:admin": "npm run dev -w @yuxingu/admin-h5", "build:admin": "npm run build -w @yuxingu/admin-h5", "dev:api": "cd apps/api && go run ./cmd/server", diff --git a/scripts/deploy-h5.sh b/scripts/deploy-h5.sh new file mode 100755 index 0000000..a87fe43 --- /dev/null +++ b/scripts/deploy-h5.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# 构建并 rsync 上传 user-h5 dist(配置见 deploy.env.example) +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +ENV_FILE="${DEPLOY_ENV:-$ROOT/deploy.env}" +if [ ! -f "$ENV_FILE" ]; then + echo "缺少 $ENV_FILE — 请复制 deploy.env.example 为 deploy.env 并填写服务器信息" >&2 + exit 1 +fi +# shellcheck disable=SC1090 +source "$ENV_FILE" + +: "${H5_DEPLOY_USER:?在 deploy.env 设置 H5_DEPLOY_USER}" +: "${H5_DEPLOY_HOST:?在 deploy.env 设置 H5_DEPLOY_HOST}" +: "${H5_DEPLOY_PATH:?在 deploy.env 设置 H5_DEPLOY_PATH}" + +echo ">>> build h5" +npm run build:h5 + +DEST="${H5_DEPLOY_USER}@${H5_DEPLOY_HOST}:${H5_DEPLOY_PATH}/" +echo ">>> rsync → $DEST" +rsync -avz --delete "${ROOT}/apps/user-h5/dist/" "$DEST" + +echo ">>> done" diff --git a/scripts/dev-mp-h5.sh b/scripts/dev-mp-h5.sh new file mode 100755 index 0000000..d57ee1f --- /dev/null +++ b/scripts/dev-mp-h5.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# 本地小程序 + H5 联调:启动 Vite,并打印微信开发者工具所需地址 +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +pick_ip() { + local addr="" + if command -v ipconfig >/dev/null 2>&1; then + addr="$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || true)" + fi + if [ -z "$addr" ]; then + addr="$(hostname -I 2>/dev/null | awk '{print $1}' || true)" + fi + echo "$addr" +} + +IP="$(pick_ip)" +IP="${IP:-127.0.0.1}" + +echo "" +echo "══════════════════════════════════════════════════════════" +echo " 愈心魔方 · 本地 H5 联调" +echo "══════════════════════════════════════════════════════════" +echo " 1. 确认小程序 util/yxgConfig.local.js 中 LOCAL_H5_HOST 为:" +echo " ${IP}:5173" +echo " 2. digital-psychology 本窗口保持运行(Vite + API 代理 /psy/api → :8080)" +echo " 需要接口时另开终端: npm run dev:api 或 npm run deps:up" +echo " 3. 微信开发者工具 → 详情 → 本地设置 → 勾选:" +echo " 「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」" +echo " 4. 编译运行小程序(开发模式),打开「愈心魔方」Tab" +echo "" +echo " 浏览器自测: http://${IP}:5173/psy/?mp=1&sbh=88" +echo "══════════════════════════════════════════════════════════" +echo "" + +npm run dev:h5