Compare commits

...
5 changed files with 317 additions and 856 deletions
+117 -2
View File
@@ -1,10 +1,75 @@
<template>
<div id="app">
<div id="app" :class="{ 'embedded-shell': isInUniAppWebView }">
<router-view />
<!-- 底部菜单暂不展示,多分身能力统一从分身卡片进入。 -->
<!-- 底部导航栏 -->
<nav class="bottom-nav" v-if="showNav">
<button
class="nav-item"
:class="{ active: currentRoute === '/' || currentRoute === '/avatar/manage' }"
@click="navigateTo('/avatar/manage')"
>
<span class="nav-icon">🤖</span>
<span class="nav-label">我的分身</span>
</button>
<button
class="nav-item"
:class="{ active: currentRoute === '/authorization' }"
@click="navigateTo('/authorization')"
>
<span class="nav-icon">🔑</span>
<span class="nav-label">授权管理</span>
</button>
<button
v-if="isInUniAppWebView"
class="nav-item"
:class="{ active: currentRoute === '/token/charge' }"
@click="navigateTo('/token/charge')"
>
<span class="nav-icon">💰</span>
<span class="nav-label">充值购买</span>
</button>
</nav>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { isInUniWebView } from '@/utils/uniapp-bridge'
const router = useRouter()
const route = useRoute()
const isInUniAppWebView = isInUniWebView()
const currentRoute = ref<string>(route.path)
const showNav = ref<boolean>(shouldShowNav(route.path))
function shouldShowNav(path: string) {
return path !== '/'
&& path !== '/avatar/create'
&& path !== '/login/sms'
&& !path.startsWith('/avatar/edit')
&& !path.startsWith('/avatar/chat')
&& !path.startsWith('/share/')
}
// 监听路由变化
watch(() => route.path, (newPath) => {
currentRoute.value = newPath
showNav.value = shouldShowNav(newPath)
})
// 导航
const navigateTo = (path: string) => {
router.push(path)
}
onMounted(() => {
currentRoute.value = route.path
showNav.value = shouldShowNav(route.path)
})
</script>
<style>
* {
margin: 0;
@@ -18,4 +83,54 @@
background: #F8F9FA;
padding-bottom: env(safe-area-inset-bottom);
}
/* 原生 App / 微信小程序容器已经提供了自己的导航栏,H5 不再重复显示页头。 */
.embedded-shell .page-header,
.embedded-shell .chat-header {
display: none;
}
/* 底部导航栏 */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
background: white;
border-top: 1px solid #EDEEF1;
padding-bottom: env(safe-area-inset-bottom);
z-index: 100;
}
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 0;
background: none;
border: none;
cursor: pointer;
transition: color 0.2s;
}
.nav-icon {
font-size: 20px;
}
.nav-label {
font-size: 11px;
color: #9398AE;
font-weight: 500;
}
.nav-item.active .nav-label {
color: #F97316;
}
.nav-item.active .nav-icon {
filter: none;
}
</style>
+16 -2
View File
@@ -12,9 +12,10 @@ export interface UniLaunchParams {
nickname?: string
avatar?: string
ts?: string
nativeShell?: string
}
const PARAM_KEYS: (keyof UniLaunchParams)[] = ['token', 'userId', 'nickname', 'avatar', 'ts']
const PARAM_KEYS: (keyof UniLaunchParams)[] = ['token', 'userId', 'nickname', 'avatar', 'ts', 'nativeShell']
function readParams(search: string, target: UniLaunchParams): void {
const sp = new URLSearchParams(search)
@@ -24,6 +25,16 @@ function readParams(search: string, target: UniLaunchParams): void {
}
}
function hasNativeShellMarker(): boolean {
const params = getLaunchParams()
if (params.nativeShell === 'uniapp') return true
// Compatibility for already-installed shells. They have always appended a
// timestamp together with the native SSO context, even before the explicit
// nativeShell marker existed.
return Boolean(params.ts && (params.token || params.userId))
}
// 是否运行在 uniapp web-view 环境中
export function isInUniWebView(): boolean {
const runtime = window as any
@@ -40,7 +51,10 @@ export function isInUniWebView(): boolean {
runtime.swan?.webView ||
runtime.tt?.miniProgram
)
return Boolean(runtime.uni?.webView && (isDCloudApp || isMiniProgram))
// `plus` can be injected after the H5 entry point runs. The native shell
// therefore adds a URL marker while creating its web-view URL, so the
// payment entry does not disappear during that startup window.
return Boolean(runtime.uni?.webView && (isDCloudApp || isMiniProgram || hasNativeShellMarker()))
}
// 解析 web-view 加载 URL 时原生注入的参数(token / 会会用户)
File diff suppressed because it is too large Load Diff
+6
View File
@@ -13,6 +13,12 @@ import { requestAppPayment } from '@/utils/payment'
const userStore = useUserStore()
const webview = ref(null)
const H5_BASE_URL = import.meta.env.VITE_AVATAR_H5_URL || 'https://digital.99hui.com/'
// Restore the native shell session before constructing the H5 URL. Without
// this, the web-view opens as an anonymous H5 page and cannot enter the
// authenticated avatar-management route.
userStore.init()
const h5Url = computed(() => buildH5Url(H5_BASE_URL, userStore.$state))
function runtimeSurface() {
+3
View File
@@ -1,5 +1,8 @@
export function buildH5Url(base, session) {
const url = new URL(base)
// This is deliberately explicit instead of relying on the timing of the
// H5+ `plus` injection inside the embedded page.
url.searchParams.set('nativeShell', 'uniapp')
if (session.token) url.searchParams.set('token', session.token)
if (session.userId) url.searchParams.set('userId', session.userId)
if (session.nickname) url.searchParams.set('nickname', session.nickname)