150 lines
3.3 KiB
Vue
150 lines
3.3 KiB
Vue
<template>
|
||
<view class="page">
|
||
<!--
|
||
小程序端 web-view 组件会自动铺满整屏(不受样式约束),
|
||
因此该端使用原生导航栏(pages.json 里 navigationStyle 保持 default);
|
||
H5 / App 端 web-view 渲染为 iframe,需要自绘标题栏。
|
||
-->
|
||
<!-- #ifndef MP-WEIXIN -->
|
||
<view class="bar">
|
||
<view class="bar-btn" @tap="goBack">
|
||
<FaIcon name="chevron-left" color="#1f2b48" :size="20" />
|
||
</view>
|
||
<text class="bar-title">{{ title }}</text>
|
||
<view class="bar-btn" @tap="onReload">
|
||
<FaIcon name="rotate" color="#5b6780" :size="18" />
|
||
</view>
|
||
</view>
|
||
<!-- #endif -->
|
||
|
||
<web-view v-if="url" :src="url" @message="onMessage" />
|
||
|
||
<view v-else class="empty">
|
||
<FaIcon name="triangle-exclamation" color="#c0c4cc" :size="40" />
|
||
<text class="empty-text">没有可打开的页面地址</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { resolveWebConsoleURL, resolveWebSSOURL } from '@/api/config.js'
|
||
|
||
const url = ref('')
|
||
const title = ref('功能')
|
||
|
||
/**
|
||
* 参数解码。
|
||
* 各端行为不一致:H5(vue-router)会先解一次码,小程序不会。
|
||
* 这里对纯 ASCII 参数(path / ticket)重复解码是幂等的,安全;
|
||
* 含中文的 title 解码一次后已无可解字符,再解也是空操作。
|
||
*/
|
||
function decodeParam(v) {
|
||
if (!v) return ''
|
||
try {
|
||
return decodeURIComponent(v)
|
||
} catch (e) {
|
||
return v
|
||
}
|
||
}
|
||
|
||
onLoad((options) => {
|
||
const opt = options || {}
|
||
const raw = decodeParam(opt.p || opt.url)
|
||
const ticket = decodeParam(opt.k)
|
||
title.value = decodeParam(opt.t || opt.title) || '功能'
|
||
|
||
if (!raw) {
|
||
uni.setNavigationBarTitle({ title: title.value })
|
||
return
|
||
}
|
||
|
||
// 带票据 → 走 Web 端免登录兑换页;否则按普通深链打开
|
||
if (ticket) {
|
||
url.value = resolveWebSSOURL(raw, ticket)
|
||
} else if (/^https?:\/\//i.test(raw)) {
|
||
url.value = raw
|
||
} else {
|
||
url.value = resolveWebConsoleURL(raw)
|
||
}
|
||
|
||
uni.setNavigationBarTitle({ title: title.value })
|
||
})
|
||
|
||
function goBack() {
|
||
const pages = getCurrentPages()
|
||
if (pages.length > 1) {
|
||
uni.navigateBack()
|
||
} else {
|
||
uni.reLaunch({ url: '/pages/nav/nav' })
|
||
}
|
||
}
|
||
|
||
// 重新加载:改 src 强制 webview/iframe 刷新
|
||
function onReload() {
|
||
const cur = url.value
|
||
if (!cur) return
|
||
const sep = cur.includes('?') ? '&' : '?'
|
||
url.value = cur + sep + '_t=' + Date.now()
|
||
}
|
||
|
||
function onMessage() {
|
||
// 预留:Web 端 postMessage 回传(如通知外层刷新数据)
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/src/styles/page-common.scss';
|
||
|
||
.page {
|
||
@include page-shell;
|
||
}
|
||
|
||
.bar {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
height: 88rpx;
|
||
padding: 0 16rpx;
|
||
background: $color-card;
|
||
border-bottom: 1rpx solid $color-border;
|
||
}
|
||
|
||
.bar-btn {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.bar-title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
text-align: center;
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
color: $color-text;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.empty {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 26rpx;
|
||
color: $color-text-muted;
|
||
}
|
||
</style>
|