fix(web_view): 修复 H5 分享消息投递,废弃无效 uni.share()
- handleMessage share 分支:存储 H5 传来的分享数据,Toast 引导用户点击右上角原生分享 - onShareAppMessage / onShareTimeline:使用 H5 传来的 title/imageUrl/href - 新增 shareSummary/shareImageUrl/shareHref data 属性
This commit is contained in:
@@ -1,30 +1,309 @@
|
||||
<template>
|
||||
<web-view class="web-view" :webview-styles="webviewStyles" :src="url" :style="{width: windowW + 'px', height: windowH + 'px'}"></web-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
windowH: 0,
|
||||
windowW: 0,
|
||||
webviewStyles: {
|
||||
progress: {
|
||||
color: 'transparent'
|
||||
}
|
||||
},
|
||||
url: ''
|
||||
}
|
||||
},
|
||||
onLoad(option) {
|
||||
this.url = decodeURIComponent(option.url);
|
||||
try {
|
||||
const res = uni.getWindowInfo();
|
||||
this.windowW = res.windowWidth;
|
||||
this.windowH = res.windowHeight;
|
||||
} catch (e) {
|
||||
// error
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
<!-- 调试浮层(已隐藏,保留代码以备后续调试) -->
|
||||
<cover-view v-if="false" class="debug-overlay" @click="debugMsg=''">
|
||||
<cover-view class="debug-text">{{ debugMsg }}</cover-view>
|
||||
</cover-view>
|
||||
<web-view v-if="url" class="web-view" :webview-styles="webviewStyles" :src="url"
|
||||
:style="{width: windowW + 'px', height: windowH + 'px'}" @message="handleMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '@/store';
|
||||
import { HTTP_REQUEST_URL, TRUSTED_DOMAINS } from '@/config/app';
|
||||
import { LOGIN_STATUS } from '@/config/cache';
|
||||
import { toLogin } from '@/libs/login';
|
||||
|
||||
function base64Decode(str) {
|
||||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
var output = '';
|
||||
var i = 0;
|
||||
str = String(str).replace(/[^A-Za-z0-9+/=]/g, '');
|
||||
while (i < str.length) {
|
||||
var b1 = chars.indexOf(str.charAt(i++));
|
||||
var b2 = chars.indexOf(str.charAt(i++));
|
||||
var b3 = chars.indexOf(str.charAt(i++));
|
||||
var b4 = chars.indexOf(str.charAt(i++));
|
||||
output += String.fromCharCode((b1 << 2) | (b2 >> 4));
|
||||
if (b3 !== 64) output += String.fromCharCode(((b2 & 15) << 4) | (b3 >> 2));
|
||||
if (b4 !== 64) output += String.fromCharCode(((b3 & 3) << 6) | b4);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function getOpenid() {
|
||||
let oid = store.state.app.openid;
|
||||
if (oid) return oid;
|
||||
try { oid = uni.getStorageSync('OPENID'); } catch (e) {}
|
||||
if (oid) return oid;
|
||||
try {
|
||||
const token = store.state.app.token || uni.getStorageSync(LOGIN_STATUS);
|
||||
if (token) {
|
||||
let parts = token.split('.');
|
||||
if (parts.length === 3) {
|
||||
let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (payload.length % 4 !== 0) payload += '=';
|
||||
const data = JSON.parse(base64Decode(payload));
|
||||
oid = data.openid || '';
|
||||
if (oid) {
|
||||
store.commit('SET_OPENID', oid);
|
||||
return oid;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[web_view] JWT decode openid failed:', e.message);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getCurrentUser() {
|
||||
const token = store.state.app.token;
|
||||
if (!token) return { openid: '', unionid: '' };
|
||||
let openid = getOpenid();
|
||||
let unionid = store.state.app.unionid || '';
|
||||
if (!unionid) { try { unionid = uni.getStorageSync('UNIONID'); } catch (e) {} }
|
||||
if (!unionid) {
|
||||
try {
|
||||
let parts = token.split('.');
|
||||
if (parts.length === 3) {
|
||||
let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (payload.length % 4 !== 0) payload += '=';
|
||||
unionid = JSON.parse(base64Decode(payload)).unionid || '';
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return { openid, unionid };
|
||||
}
|
||||
|
||||
function stripAuthParams(url) {
|
||||
return url
|
||||
.replace(/([?&])openid=[^&]+(&|$)/g, (_, pre, post) => pre === '?' ? '?' : (post || ''))
|
||||
.replace(/([?&])unionid=[^&]+(&|$)/g, (_, pre, post) => pre === '?' ? '?' : (post || ''))
|
||||
.replace(/([?&])t=[^&]+(&|$)/g, (_, pre, post) => pre === '?' ? '?' : (post || ''))
|
||||
.replace(/\?&$/, '').replace(/\?$/, '').replace(/&$/, '');
|
||||
}
|
||||
|
||||
function getUrlParam(url, name) {
|
||||
try {
|
||||
const match = url.match(new RegExp('[?&]' + name + '=([^&]*)'));
|
||||
return match ? decodeURIComponent(match[1]) : '';
|
||||
} catch (e) { return ''; }
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
windowH: 0, windowW: 0,
|
||||
webviewStyles: { progress: { color: 'transparent' } },
|
||||
url: '',
|
||||
shareTitle: '',
|
||||
shareSummary: '',
|
||||
shareImageUrl: '',
|
||||
shareHref: '',
|
||||
debugMsg: '',
|
||||
savedUrl: ''
|
||||
}
|
||||
},
|
||||
onLoad(option) {
|
||||
try {
|
||||
let targetUrl = decodeURIComponent(option.url);
|
||||
console.log('[web_view] 原始URL:', targetUrl);
|
||||
this.debugMsg = '[web_view] 加载中...\nurl=' + targetUrl.substring(0, 100);
|
||||
|
||||
const isTrustedDomain = targetUrl.indexOf(HTTP_REQUEST_URL) === 0 ||
|
||||
(TRUSTED_DOMAINS || []).some(d => targetUrl.indexOf(d) === 0);
|
||||
|
||||
if (isTrustedDomain) {
|
||||
const token = store.state.app.token;
|
||||
if (token) {
|
||||
let { openid, unionid } = getCurrentUser();
|
||||
const urlOpenid = getUrlParam(targetUrl, 'openid');
|
||||
|
||||
// 收集调试信息
|
||||
let info = '[debug] token=' + (token ? '有' : '无') +
|
||||
', myOpenid=' + (openid || '(空)') +
|
||||
', urlOpenid=' + (urlOpenid || '(空)') +
|
||||
', url=' + targetUrl.substring(0, 80);
|
||||
this.debugMsg = info;
|
||||
if (openid && urlOpenid && urlOpenid !== openid) this.debugMsg += '→ 身份不一致,将替换';
|
||||
|
||||
if (!openid) {
|
||||
// 获取不到身份信息,用 setTimeout 重试
|
||||
console.warn('[web_view] 首次获取 openid 为空,延迟重试', info);
|
||||
this.debugMsg = info + '\n→ 等待重试...';
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const retry = getCurrentUser();
|
||||
console.log('[web_view] 重试后 openid=' + (retry.openid || '(空)'));
|
||||
this.debugMsg = info + '\n→ 重试后 openid=' + (retry.openid || '(空)');
|
||||
if (retry.openid) {
|
||||
this._applyIdentity(targetUrl, retry.openid, retry.unionid, urlOpenid);
|
||||
} else {
|
||||
this.debugMsg = info + '\n→ 重试仍失败,直接加载原始URL';
|
||||
this.url = targetUrl;
|
||||
}
|
||||
} catch (e) {
|
||||
this.debugMsg = '重试异常: ' + e.message;
|
||||
this.url = targetUrl;
|
||||
}
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
this._applyIdentity(targetUrl, openid, unionid, urlOpenid);
|
||||
return; // _applyIdentity 内部已调用 _finalizeUrl,避免163重复覆盖
|
||||
} else {
|
||||
console.log('[web_view] 无 token,跳转登录');
|
||||
this.savedUrl = targetUrl;
|
||||
toLogin();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log('[web_view] 非可信域,不做身份替换');
|
||||
}
|
||||
|
||||
this._finalizeUrl(targetUrl);
|
||||
} catch (e) {
|
||||
console.error('[web_view] onLoad 异常:', e);
|
||||
this.debugMsg = 'onLoad异常: ' + e.message + '\n' + e.stack;
|
||||
// 异常时直接加载原始 URL
|
||||
try {
|
||||
this.url = decodeURIComponent(option.url);
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 登录返回后:页面仍在栈中,onLoad 不会再次触发
|
||||
// 检查是否有保存的 URL 和 token,重新处理身份
|
||||
if (this.savedUrl && store.state.app.token && !this.url) {
|
||||
console.log('[web_view] onShow: 登录返回,重新处理 URL:', this.savedUrl.substring(0, 80));
|
||||
this.debugMsg = '[web_view] 登录返回...\n' + this.savedUrl.substring(0, 100);
|
||||
const targetUrl = this.savedUrl;
|
||||
const { openid, unionid } = getCurrentUser();
|
||||
const urlOpenid = getUrlParam(targetUrl, 'openid');
|
||||
if (openid) {
|
||||
this._applyIdentity(targetUrl, openid, unionid, urlOpenid);
|
||||
} else {
|
||||
console.warn('[web_view] onShow: 仍无 openid,加载原始URL');
|
||||
this._finalizeUrl(targetUrl);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_applyIdentity(targetUrl, openid, unionid, urlOpenid) {
|
||||
if (urlOpenid && urlOpenid !== openid) {
|
||||
targetUrl = stripAuthParams(targetUrl);
|
||||
const joiner = targetUrl.indexOf('?') !== -1 ? '&' : '?';
|
||||
const t = Math.floor(Date.now() / 1000);
|
||||
targetUrl = targetUrl + joiner + 'openid=' + encodeURIComponent(openid);
|
||||
if (unionid) targetUrl += '&unionid=' + encodeURIComponent(unionid);
|
||||
targetUrl += '&t=' + t;
|
||||
console.log('[web_view] 替换为当前用户身份');
|
||||
} else if (!urlOpenid) {
|
||||
targetUrl = stripAuthParams(targetUrl);
|
||||
const joiner = targetUrl.indexOf('?') !== -1 ? '&' : '?';
|
||||
const t = Math.floor(Date.now() / 1000);
|
||||
targetUrl = targetUrl + joiner + 'openid=' + encodeURIComponent(openid);
|
||||
if (unionid) targetUrl += '&unionid=' + encodeURIComponent(unionid);
|
||||
targetUrl += '&t=' + t;
|
||||
console.log('[web_view] 补充身份参数');
|
||||
} else {
|
||||
console.log('[web_view] 身份一致,保留原 URL');
|
||||
}
|
||||
this.debugMsg += ' [最终URL] ' + targetUrl.substring(0, 120);
|
||||
this._finalizeUrl(targetUrl);
|
||||
},
|
||||
_finalizeUrl(targetUrl) {
|
||||
console.log('[web_view] 最终URL:', targetUrl);
|
||||
try {
|
||||
const qIdx = targetUrl.indexOf('?');
|
||||
if (qIdx !== -1) console.log('[web_view] params:', targetUrl.slice(qIdx + 1));
|
||||
} catch (_) {}
|
||||
|
||||
// #ifdef MP
|
||||
uni.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] });
|
||||
// #endif
|
||||
|
||||
// 自动关闭调试浮层(3秒后)
|
||||
setTimeout(() => {
|
||||
if (this.debugMsg) this.debugMsg = '';
|
||||
}, 3000);
|
||||
|
||||
this.url = targetUrl;
|
||||
try {
|
||||
const res = uni.getWindowInfo();
|
||||
this.windowW = res.windowWidth;
|
||||
this.windowH = res.windowHeight;
|
||||
} catch (_) {}
|
||||
},
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: this.shareTitle || '分享',
|
||||
path: '/pages/annex/web_view/index?url=' + encodeURIComponent(this.shareHref || this.url),
|
||||
imageUrl: this.shareImageUrl || ''
|
||||
}
|
||||
},
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: this.shareTitle || '分享',
|
||||
query: 'url=' + encodeURIComponent(this.shareHref || this.url),
|
||||
imageUrl: this.shareImageUrl || ''
|
||||
}
|
||||
},
|
||||
handleMessage(e) {
|
||||
const msgs = e.detail && e.detail.data ? e.detail.data : [];
|
||||
const data = msgs[msgs.length - 1] || null;
|
||||
if (!data) return
|
||||
|
||||
switch (data.action) {
|
||||
case 'share':
|
||||
// 存储分享数据,供 onShareAppMessage / onShareTimeline 使用
|
||||
if (data.title) {
|
||||
this.shareTitle = data.title;
|
||||
this.shareSummary = data.summary || '';
|
||||
this.shareImageUrl = data.imageUrl || '';
|
||||
this.shareHref = data.href || this.url;
|
||||
}
|
||||
// 小程序中无法用代码触发分享面板,引导用户点击右上角 ··· 按钮
|
||||
uni.showToast({ title: '点击右上角 ··· 分享给好友', icon: 'none', duration: 2500 });
|
||||
break
|
||||
|
||||
case 'copyLink':
|
||||
const link = data.href || this.url
|
||||
uni.setClipboardData({
|
||||
data: link,
|
||||
success() { uni.showToast({ title: '链接已复制', icon: 'success' }) },
|
||||
fail(err) {
|
||||
console.warn('[web_view] 复制失败:', err)
|
||||
uni.showToast({ title: '复制失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case 'setTitle':
|
||||
if (data.title) {
|
||||
this.shareTitle = data.title;
|
||||
uni.setNavigationBarTitle({ title: data.title })
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.debug-overlay {
|
||||
position: fixed; top: 0; left: 0; right: 0; height: 45%;
|
||||
background: rgba(0,0,0,0.8); z-index: 9999;
|
||||
padding: 20px; box-sizing: border-box;
|
||||
overflow-y: auto; border-bottom: 2px solid #0f0;
|
||||
}
|
||||
.debug-text {
|
||||
color: #0f0; font-size: 13px;
|
||||
white-space: pre-wrap; word-break: break-all; line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user