feat(h5): 小程序 embed 模式与全站 ICP 备案页脚
H5 支持 ?mp=1 嵌入微信 WebView;user-h5/admin-h5 底部展示蜀ICP备2025140386号-2 并链至工信部;补充小程序+H5 部署说明。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<footer class="icp-footer">
|
||||||
|
<a class="icp-link" :href="ICP_MIIT_URL" target="_blank" rel="noopener noreferrer">{{ ICP_RECORD }}</a>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ICP_MIIT_URL, ICP_RECORD } from '@yuxingu/utils'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icp-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem 0 0.5rem;
|
||||||
|
}
|
||||||
|
.icp-link {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--muted, #8a7f7b);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.icp-link:hover {
|
||||||
|
color: var(--accent, #e54d42);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { RouterLink, RouterView, useRouter } from 'vue-router'
|
import { RouterLink, RouterView, useRouter } from 'vue-router'
|
||||||
import BrandLogo from '@/components/BrandLogo.vue'
|
import BrandLogo from '@/components/BrandLogo.vue'
|
||||||
|
import IcpFooter from '@/components/IcpFooter.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
@@ -55,7 +56,10 @@ async function onLogout() {
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<main class="main">
|
<main class="main">
|
||||||
|
<div class="main-body">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
<IcpFooter />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -91,7 +95,13 @@ nav a.router-link-active {
|
|||||||
}
|
}
|
||||||
.foot { margin-top: auto; display: flex; flex-direction: column; gap: 0.5rem; }
|
.foot { margin-top: auto; display: flex; flex-direction: column; gap: 0.5rem; }
|
||||||
.role { font-size: 0.75rem; color: var(--muted); }
|
.role { font-size: 0.75rem; color: var(--muted); }
|
||||||
.main { padding: 1.5rem 1.75rem; }
|
.main {
|
||||||
|
padding: 1.5rem 1.75rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.main-body { flex: 1; min-width: 0; }
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.shell { grid-template-columns: 1fr; }
|
.shell { grid-template-columns: 1fr; }
|
||||||
.nav { border-right: 0; border-bottom: 1px solid var(--line); }
|
.nav { border-right: 0; border-bottom: 1px solid var(--line); }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import BrandLogo from '@/components/BrandLogo.vue'
|
import BrandLogo from '@/components/BrandLogo.vue'
|
||||||
|
import IcpFooter from '@/components/IcpFooter.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
@@ -46,11 +47,24 @@ async function onSubmit() {
|
|||||||
{{ loading ? '登录中…' : '登录' }}
|
{{ loading ? '登录中…' : '登录' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<IcpFooter />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.wrap { min-height: 100vh; display: grid; place-items: center; padding: 1.5rem; }
|
.wrap {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
.wrap :deep(.icp-footer) {
|
||||||
|
margin-top: auto;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
}
|
||||||
.login { width: min(380px, 100%); text-align: center; }
|
.login { width: min(380px, 100%); text-align: center; }
|
||||||
.login :deep(.field) { text-align: left; }
|
.login :deep(.field) { text-align: left; }
|
||||||
h1 { margin: 0 0 0.25rem; font-size: 1.45rem; font-family: "Noto Serif SC", "Songti SC", serif; }
|
h1 { margin: 0 0 0.25rem; font-size: 1.45rem; font-family: "Noto Serif SC", "Songti SC", serif; }
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 357 KiB |
@@ -1,12 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-shell">
|
<div class="app-shell" :class="{ 'mp-embed': mpEmbed }">
|
||||||
<AppHeader />
|
<AppHeader v-if="!mpEmbed || showEmbedHeader" />
|
||||||
<router-view />
|
<router-view />
|
||||||
<TabBar />
|
<IcpFooter />
|
||||||
|
<TabBar v-if="!mpEmbed" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import AppHeader from './components/AppHeader.vue'
|
import AppHeader from './components/AppHeader.vue'
|
||||||
|
import IcpFooter from './components/IcpFooter.vue'
|
||||||
import TabBar from './components/TabBar.vue'
|
import TabBar from './components/TabBar.vue'
|
||||||
|
import { isMpEmbed } from './lib/mpEmbed'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const mpEmbed = isMpEmbed()
|
||||||
|
/** Sub-pages in web-view: show back only; home hides duplicate chrome. */
|
||||||
|
const showEmbedHeader = computed(() => mpEmbed && route.path !== '/')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<footer class="icp-footer" :class="{ 'mp-embed': mpEmbed }">
|
||||||
|
<a class="icp-link" :href="ICP_MIIT_URL" target="_blank" rel="noopener noreferrer">{{ ICP_RECORD }}</a>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ICP_MIIT_URL, ICP_RECORD } from '@yuxingu/utils'
|
||||||
|
import { isMpEmbed } from '../lib/mpEmbed'
|
||||||
|
|
||||||
|
const mpEmbed = isMpEmbed()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icp-footer {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--yxg-max-w);
|
||||||
|
bottom: calc(var(--yxg-nav-h) + env(safe-area-inset-bottom, 0px));
|
||||||
|
z-index: 90;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: rgba(255, 252, 250, 0.92);
|
||||||
|
border-top: 1px solid rgba(240, 230, 224, 0.6);
|
||||||
|
}
|
||||||
|
.icp-footer.mp-embed {
|
||||||
|
bottom: env(safe-area-inset-bottom, 0px);
|
||||||
|
}
|
||||||
|
.icp-link {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #9a8f8b;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.icp-link:active {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
import { initMpEmbed, isMpEmbed } from './mpEmbed'
|
||||||
|
|
||||||
|
describe('mpEmbed', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sessionStorage.clear()
|
||||||
|
window.history.replaceState({}, '', '/psy/')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sessionStorage.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('persists embed flag from ?mp=1', () => {
|
||||||
|
window.history.replaceState({}, '', '/psy/?mp=1')
|
||||||
|
initMpEmbed()
|
||||||
|
expect(isMpEmbed()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false without mp query', () => {
|
||||||
|
initMpEmbed()
|
||||||
|
expect(isMpEmbed()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
const MP_EMBED_KEY = 'yxg_mp_embed'
|
||||||
|
const MP_QUERY = 'mp'
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
if (params.get(MP_QUERY) === '1') {
|
||||||
|
sessionStorage.setItem(MP_EMBED_KEY, '1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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'
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ import { createPinia } from 'pinia'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import { initAnalytics, trackPageView } from './lib/analytics'
|
import { initAnalytics, trackPageView } from './lib/analytics'
|
||||||
|
import { initMpEmbed } from './lib/mpEmbed'
|
||||||
|
|
||||||
|
initMpEmbed()
|
||||||
import '@yuxingu/ui/tokens.css'
|
import '@yuxingu/ui/tokens.css'
|
||||||
import './styles/app.css'
|
import './styles/app.css'
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { isMpEmbed } from '../lib/mpEmbed'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory('/psy/'),
|
history: createWebHistory('/psy/'),
|
||||||
@@ -48,4 +49,9 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
if (!isMpEmbed() || to.query.mp === '1') return true
|
||||||
|
return { path: to.path, query: { ...to.query, mp: '1' }, hash: to.hash, replace: true }
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ body{
|
|||||||
}
|
}
|
||||||
.app-shell{
|
.app-shell{
|
||||||
max-width:var(--yxg-max-w);margin:0 auto;min-height:100vh;
|
max-width:var(--yxg-max-w);margin:0 auto;min-height:100vh;
|
||||||
padding-bottom:calc(var(--yxg-nav-h) + env(safe-area-inset-bottom,0px) + 12px);
|
padding-bottom:calc(var(--yxg-nav-h) + env(safe-area-inset-bottom,0px) + 36px);
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
|
.app-shell.mp-embed{
|
||||||
|
padding-bottom:calc(env(safe-area-inset-bottom,0px) + 36px);
|
||||||
|
}
|
||||||
a{color:inherit;text-decoration:none}
|
a{color:inherit;text-decoration:none}
|
||||||
|
|
||||||
/* ── Page shell (测测式:暖色洗底 + 白 sheet) ── */
|
/* ── Page shell (测测式:暖色洗底 + 白 sheet) ── */
|
||||||
|
|||||||
@@ -0,0 +1,332 @@
|
|||||||
|
# 愈心谷小程序 + 愈心魔方 H5 部署说明
|
||||||
|
|
||||||
|
适用于:**微信小程序**(`yuxingu-Miniprogram-dev`)嵌入 **愈心魔方 H5**(`digital-psychology/apps/user-h5`)的展示期上线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 架构
|
||||||
|
|
||||||
|
```
|
||||||
|
微信小程序(uni-app)
|
||||||
|
├── 首页 / 咨询 / 测评 / 我的 → https://miniapp.yuxingu.com.cn (Java,rp-server-psychic)
|
||||||
|
└── 愈心魔方 Tab / 首页入口 → https://h5.yuxingu.com.cn/psy/?mp=1 (WebView)
|
||||||
|
└── /psy/api/* → Go API(digital-psychology)
|
||||||
|
```
|
||||||
|
|
||||||
|
| 组件 | 仓库 | 域名 |
|
||||||
|
|------|------|------|
|
||||||
|
| 小程序客户端 | `yuxingu-Miniprogram-dev` | —(微信后台上传) |
|
||||||
|
| 愈心魔方 H5 | `digital-psychology/apps/user-h5` | `h5.yuxingu.com.cn` |
|
||||||
|
| Go API | `digital-psychology/apps/api` | 同域反代 `/psy/api/` |
|
||||||
|
| 旧业务 API | `rp-server-psychic`(独立部署) | `miniapp.yuxingu.com.cn` |
|
||||||
|
|
||||||
|
H5 地址配置在小程序 `util/yxgConfig.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export const YXG_H5_BASE = 'https://h5.yuxingu.com.cn/psy/'
|
||||||
|
export const YXG_H5_HOME_URL = `${YXG_H5_BASE}?mp=1`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 前置条件
|
||||||
|
|
||||||
|
### 2.1 域名与证书
|
||||||
|
|
||||||
|
| 域名 | 用途 | 要求 |
|
||||||
|
|------|------|------|
|
||||||
|
| `h5.yuxingu.com.cn` | H5 静态 + Go API 反代 | HTTPS、已备案 |
|
||||||
|
| `miniapp.yuxingu.com.cn` | 咨询/测评/登录(已有) | HTTPS |
|
||||||
|
|
||||||
|
### 2.2 微信小程序后台
|
||||||
|
|
||||||
|
路径:**开发 → 开发管理 → 开发设置**
|
||||||
|
|
||||||
|
| 配置项 | 域名 |
|
||||||
|
|--------|------|
|
||||||
|
| **业务域名**(WebView 必配) | `h5.yuxingu.com.cn`(需上传校验文件到站点根目录) |
|
||||||
|
| **request 合法域名** | `https://miniapp.yuxingu.com.cn`、`https://h5.yuxingu.com.cn` |
|
||||||
|
| uploadFile 合法域名 | `https://miniapp.yuxingu.com.cn`(维持现状) |
|
||||||
|
| downloadFile 合法域名 | `https://miniapp.yuxingu.com.cn`(维持现状) |
|
||||||
|
| socket 合法域名 | 无需配置 |
|
||||||
|
|
||||||
|
### 2.3 服务器
|
||||||
|
|
||||||
|
- 一台可跑 Nginx + Go 二进制(或容器)的 VPS
|
||||||
|
- PostgreSQL(Go API 用;可与现有库同机或托管)
|
||||||
|
- Java 微服务 + `miniapp.yuxingu.com.cn` 保持现有部署,本说明不覆盖
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Go API 部署
|
||||||
|
|
||||||
|
仓库:`digital-psychology`
|
||||||
|
|
||||||
|
### 3.1 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/digital-psychology/apps/api
|
||||||
|
go test ./...
|
||||||
|
go build -o yuxingu-api ./cmd/server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp config.example.yaml config.local.yaml
|
||||||
|
# 编辑 config.local.yaml:database、deepseek.api_key(Ask 需要)等
|
||||||
|
```
|
||||||
|
|
||||||
|
生产环境建议用环境变量覆盖敏感项,勿把密钥提交 Git。
|
||||||
|
|
||||||
|
### 3.3 数据库迁移
|
||||||
|
|
||||||
|
API 启动时会自动应用 `apps/api/migrations/`。首次部署前确保 PostgreSQL 已创建库和用户。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地仅 DB 示例
|
||||||
|
cd /path/to/digital-psychology
|
||||||
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 运行(示例 systemd)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/systemd/system/yuxingu-api.service
|
||||||
|
[Unit]
|
||||||
|
Description=Yuxingu Go API
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/yuxingu/apps/api
|
||||||
|
ExecStart=/opt/yuxingu/apps/api/yuxingu-api
|
||||||
|
Environment=APP_ENV=prod
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
API 默认监听 `:8080`(见 `config.local.yaml` 的 `http_addr`)。
|
||||||
|
|
||||||
|
### 3.5 健康检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS http://127.0.0.1:8080/api/v1/healthz
|
||||||
|
# 期望:{"code":0,"data":{...}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. H5 构建与 Nginx 部署
|
||||||
|
|
||||||
|
### 4.1 构建静态资源
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/digital-psychology
|
||||||
|
npm ci
|
||||||
|
npm run build:h5
|
||||||
|
# 产物:apps/user-h5/dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
构建产物 `base` 为 `/psy/`,与线上路径一致,**无需改 `baseURL`**(客户端请求 `/psy/api/v1/...`)。
|
||||||
|
|
||||||
|
### 4.2 上传到服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -avz --delete apps/user-h5/dist/ user@your-server:/var/www/yuxingu-h5/psy/
|
||||||
|
```
|
||||||
|
|
||||||
|
目录结构示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
/var/www/yuxingu-h5/psy/
|
||||||
|
├── index.html
|
||||||
|
├── assets/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Nginx 配置(推荐:H5 + API 同域)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name h5.yuxingu.com.cn;
|
||||||
|
|
||||||
|
ssl_certificate /etc/ssl/h5.yuxingu.com.cn/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/ssl/h5.yuxingu.com.cn/privkey.pem;
|
||||||
|
|
||||||
|
# 微信业务域名校验文件(按后台提示的文件名放置)
|
||||||
|
location = /MP_verify_xxxx.txt {
|
||||||
|
root /var/www/wechat-verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
# user-h5 SPA(history 模式)
|
||||||
|
location /psy/ {
|
||||||
|
alias /var/www/yuxingu-h5/psy/;
|
||||||
|
try_files $uri $uri/ /psy/index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Go API 反代:/psy/api/v1/* → :8080/api/v1/*
|
||||||
|
location /psy/api/ {
|
||||||
|
rewrite ^/psy/api/(.*)$ /api/$1 break;
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# Ask 流式响应
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = / {
|
||||||
|
return 301 https://$host/psy/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 H5 embed 模式说明
|
||||||
|
|
||||||
|
小程序 WebView 打开 `https://h5.yuxingu.com.cn/psy/?mp=1` 时:
|
||||||
|
|
||||||
|
- H5 **隐藏底部 TabBar**(仅小程序底栏导航)
|
||||||
|
- 子页路由自动保留 `mp=1` 参数
|
||||||
|
- 子页显示顶部返回键,不显示 H5 主导航
|
||||||
|
|
||||||
|
浏览器直访 `https://h5.yuxingu.com.cn/psy/`(无 `mp=1`)仍为完整 H5 体验(含 TabBar)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 微信小程序部署
|
||||||
|
|
||||||
|
仓库:`yuxingu-Miniprogram-dev`
|
||||||
|
|
||||||
|
### 5.1 开发工具构建
|
||||||
|
|
||||||
|
1. 用 **HBuilderX** 打开 `yuxingu-Miniprogram-dev`
|
||||||
|
2. 菜单:**发行 → 小程序-微信**
|
||||||
|
3. 或使用 CLI(需已安装 `@dcloudio/uni-cli` 等依赖):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/yuxingu-Miniprogram-dev
|
||||||
|
# 具体命令以 HBuilderX / uni-app 版本为准
|
||||||
|
npx uni build -p mp-weixin
|
||||||
|
```
|
||||||
|
|
||||||
|
编译产物目录:`unpackage/dist/build/mp-weixin/`(或 dev 目录)
|
||||||
|
|
||||||
|
### 5.2 微信开发者工具
|
||||||
|
|
||||||
|
1. 导入上述 `mp-weixin` 目录
|
||||||
|
2. AppID:`wx6782dd88e655f1a7`(见 `manifest.json`)
|
||||||
|
3. 预览 / 真机调试
|
||||||
|
|
||||||
|
### 5.3 上传体验版 / 正式版
|
||||||
|
|
||||||
|
1. 开发者工具 → **上传**
|
||||||
|
2. 登录 [微信公众平台](https://mp.weixin.qq.com/) → **管理 → 版本管理**
|
||||||
|
3. 将上传版本设为 **体验版** 供内部验收,或提交审核后发布
|
||||||
|
|
||||||
|
### 5.4 发布前检查(小程序侧)
|
||||||
|
|
||||||
|
- [ ] `util/yxgConfig.js` 中 H5 地址为生产域名
|
||||||
|
- [ ] `util/requestConfig.js` 中 Java API 仍为 `https://miniapp.yuxingu.com.cn`
|
||||||
|
- [ ] Tab 顺序:首页 | 愈心魔方 | 心理咨询 | 心理测评 | 我的
|
||||||
|
- [ ] `static/n-menu/mf-*.png` 图标已替换为正式稿(当前可为占位图)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 上线验证清单
|
||||||
|
|
||||||
|
### 6.1 服务端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# H5 首页
|
||||||
|
curl -I https://h5.yuxingu.com.cn/psy/
|
||||||
|
|
||||||
|
# API 健康
|
||||||
|
curl -sS https://h5.yuxingu.com.cn/psy/api/v1/healthz
|
||||||
|
|
||||||
|
# embed 首页可访问
|
||||||
|
curl -I 'https://h5.yuxingu.com.cn/psy/?mp=1'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 微信开发者工具 / 真机
|
||||||
|
|
||||||
|
| 步骤 | 预期 |
|
||||||
|
|------|------|
|
||||||
|
| 打开小程序首页 | Banner、愈心魔方紫色入口条、测评/咨询卡片正常 |
|
||||||
|
| 点击「愈心魔方」入口 | 切到第 2 个 Tab,加载 H5 |
|
||||||
|
| 点击底部「愈心魔方」Tab | 同上 |
|
||||||
|
| H5 内点九宫格进子页(如问答) | 无 H5 底栏,有返回键,接口正常 |
|
||||||
|
| 咨询 / 测评 Tab | 仍走 miniapp 旧接口,登录与订单正常 |
|
||||||
|
| 未登录访问需登录能力 | 弹出微信手机号登录 |
|
||||||
|
|
||||||
|
### 6.3 常见故障
|
||||||
|
|
||||||
|
| 现象 | 原因 | 处理 |
|
||||||
|
|------|------|------|
|
||||||
|
| WebView 白屏 | 业务域名未配或校验文件缺失 | 微信后台 → 业务域名 → 重新校验 |
|
||||||
|
| H5 内接口失败 | request 合法域名缺 `h5.yuxingu.com.cn` | 补充合法域名,等 5 分钟生效 |
|
||||||
|
| 404 on `/psy/ask` 刷新 | Nginx 未配 SPA fallback | 确认 `try_files` 指向 `/psy/index.html` |
|
||||||
|
| API 502 | Go 未启动或反代路径错误 | 查 `yuxingu-api` 日志与 `proxy_pass` |
|
||||||
|
| H5 仍显示底栏 | 未带 `mp=1` | 检查 `yxgConfig.js` 与 WebView URL |
|
||||||
|
| 双层底栏 | H5 未进 embed 模式 | 确认 URL 含 `?mp=1` 且已部署最新 H5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 更新与回滚
|
||||||
|
|
||||||
|
### 仅更新愈心魔方(H5 + API)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 构建 H5
|
||||||
|
cd digital-psychology && npm run build:h5
|
||||||
|
rsync -avz --delete apps/user-h5/dist/ user@server:/var/www/yuxingu-h5/psy/
|
||||||
|
|
||||||
|
# 2. 更新 API(需停服瞬间)
|
||||||
|
cd apps/api && go build -o yuxingu-api ./cmd/server
|
||||||
|
sudo systemctl restart yuxingu-api
|
||||||
|
```
|
||||||
|
|
||||||
|
小程序 **WebView 无缓存强制刷新**时,用户可能仍看到旧 H5;可在 URL 加版本 query(如 `?mp=1&v=20260824`)或在微信公众平台重新发布小程序。
|
||||||
|
|
||||||
|
### 仅更新小程序壳
|
||||||
|
|
||||||
|
重新 HBuilderX 发行 → 上传新版本。不影响 H5 与 API。
|
||||||
|
|
||||||
|
### 回滚
|
||||||
|
|
||||||
|
- H5:恢复 `/var/www/yuxingu-h5/psy/` 备份目录
|
||||||
|
- API:`systemctl restart` 旧二进制
|
||||||
|
- 小程序:版本管理 → 回退到上一审核版本
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 仓库与路径速查
|
||||||
|
|
||||||
|
| 内容 | 路径 |
|
||||||
|
|------|------|
|
||||||
|
| 小程序 Tab / 首页入口 | `yuxingu-Miniprogram-dev/pages/yxg-magic/`、`pages/index/index.vue` |
|
||||||
|
| H5 地址常量 | `yuxingu-Miniprogram-dev/util/yxgConfig.js` |
|
||||||
|
| H5 embed 逻辑 | `digital-psychology/apps/user-h5/src/lib/mpEmbed.ts` |
|
||||||
|
| Go API | `digital-psychology/apps/api/cmd/server` |
|
||||||
|
| H5 构建命令 | `digital-psychology` 根目录 `npm run build:h5` |
|
||||||
|
| 本地开发 | Go:`cd apps/api && go run ./cmd/server`;H5:`npm run dev:h5` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 后续(非展示期)
|
||||||
|
|
||||||
|
- 微信登录与 Go Auth 统一(单账号)
|
||||||
|
- Java 顾问/测评域逐步迁入 Go API
|
||||||
|
- CI 镜像发布见 `.ai/deployment.md`、`deploy/README.md`
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/** 工信部 ICP 备案(全站 H5 / 后台页脚) */
|
||||||
|
export const ICP_RECORD = '蜀ICP备2025140386号-2'
|
||||||
|
|
||||||
|
/** 工信部备案查询入口 */
|
||||||
|
export const ICP_MIIT_URL = 'https://beian.miit.gov.cn/'
|
||||||
@@ -25,3 +25,5 @@ export {
|
|||||||
type UserTextKind,
|
type UserTextKind,
|
||||||
type ValidateUserTextResult,
|
type ValidateUserTextResult,
|
||||||
} from './textsafe'
|
} from './textsafe'
|
||||||
|
|
||||||
|
export { ICP_MIIT_URL, ICP_RECORD } from './icp'
|
||||||
|
|||||||
Reference in New Issue
Block a user