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:
@@ -1,12 +1,22 @@
|
||||
<template>
|
||||
<div class="app-shell">
|
||||
<AppHeader />
|
||||
<div class="app-shell" :class="{ 'mp-embed': mpEmbed }">
|
||||
<AppHeader v-if="!mpEmbed || showEmbedHeader" />
|
||||
<router-view />
|
||||
<TabBar />
|
||||
<IcpFooter />
|
||||
<TabBar v-if="!mpEmbed" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import AppHeader from './components/AppHeader.vue'
|
||||
import IcpFooter from './components/IcpFooter.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>
|
||||
|
||||
@@ -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 router from './router'
|
||||
import { initAnalytics, trackPageView } from './lib/analytics'
|
||||
import { initMpEmbed } from './lib/mpEmbed'
|
||||
|
||||
initMpEmbed()
|
||||
import '@yuxingu/ui/tokens.css'
|
||||
import './styles/app.css'
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { isMpEmbed } from '../lib/mpEmbed'
|
||||
|
||||
const router = createRouter({
|
||||
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
|
||||
|
||||
@@ -14,9 +14,12 @@ body{
|
||||
}
|
||||
.app-shell{
|
||||
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;
|
||||
}
|
||||
.app-shell.mp-embed{
|
||||
padding-bottom:calc(env(safe-area-inset-bottom,0px) + 36px);
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
|
||||
/* ── Page shell (测测式:暖色洗底 + 白 sheet) ── */
|
||||
|
||||
Reference in New Issue
Block a user