201 lines
7.2 KiB
JavaScript
201 lines
7.2 KiB
JavaScript
/**
|
||
* 全局兜底分享 mixin
|
||
*
|
||
* 仅在微信小程序编译产物中被加载:
|
||
* main.js 中的 import + Vue.mixin 调用被 MP-WEIXIN 预处理宏 包住,
|
||
* 其它平台编译时整个模块不会进入产物,因此文件内部无需再写条件编译。
|
||
*
|
||
* 背景:
|
||
* - 微信小程序要求每个要支持「转发 / 朋友圈」的页面显式声明 onShareAppMessage / onShareTimeline,
|
||
* 否则页面右上角"三个点"菜单不会显示"转发"按钮。
|
||
* - 本项目之前只有 ~17 个页面声明了分享钩子,剩余页面无法转发。
|
||
* - 业务对"全页面都支持转发 + 复制小程序路径"有强诉求。
|
||
*
|
||
* 设计原则:
|
||
* 1. 不侵入现有页面:所有页面通过 Vue.mixin 自动获得默认行为
|
||
* 2. 已有 onShareAppMessage / onShareTimeline 的页面优先级更高(uni-app 会优先用页面自身的钩子)
|
||
* 3. 默认分享内容由后端 /weapp/api/weapp/share 按当前路由下发,
|
||
* 拉取失败时退化到站点名 + 当前路径,避免完全无法分享
|
||
* 4. 暴露 $mpShare 工具方法(currentPath / copyPath / refresh),
|
||
* 方便子页面在 onShareAppMessage 中复用 path
|
||
*/
|
||
|
||
// #ifdef MP-WEIXIN
|
||
const _safeGetCurrentPage = () => {
|
||
try {
|
||
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
|
||
return pages.length > 0 ? pages[pages.length - 1] : null;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
};
|
||
|
||
const _getCurrentPath = (withQuery = true) => {
|
||
const page = _safeGetCurrentPage();
|
||
if (!page) return '';
|
||
// page.route 在 mp-weixin 上不带前导斜杠、不带 query;page.options 携带 query
|
||
const route = page.route || (page.$page && page.$page.path) || '';
|
||
const options = page.options || {};
|
||
if (!withQuery) return route ? '/' + route.replace(/^\//, '') : '';
|
||
const query = Object.keys(options)
|
||
.filter(k => options[k] !== undefined && options[k] !== null && options[k] !== '')
|
||
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(options[k])}`)
|
||
.join('&');
|
||
return query ? `/${route}?${query}` : `/${route}`;
|
||
};
|
||
|
||
// 模块级缓存:避免重复请求后端
|
||
let _shareCache = null;
|
||
let _shareCacheAt = 0;
|
||
const SHARE_CACHE_TTL = 60 * 1000; // 60s
|
||
|
||
const _fetchShareFromServer = (path) => {
|
||
const Http = require('./http.js').default || require('./http.js');
|
||
return new Promise((resolve) => {
|
||
Http.sendRequest({
|
||
url: '/weapp/api/weapp/share',
|
||
data: { path },
|
||
success: (res) => {
|
||
if (res && res.code >= 0 && res.data && res.data.data) {
|
||
resolve(res.data.data);
|
||
} else {
|
||
resolve(null);
|
||
}
|
||
},
|
||
fail: () => resolve(null)
|
||
});
|
||
});
|
||
};
|
||
|
||
const _buildFallback = (path) => ({
|
||
title: '小程序分享',
|
||
path,
|
||
imageUrl: ''
|
||
});
|
||
|
||
const _getShareConfig = async (path) => {
|
||
const now = Date.now();
|
||
if (_shareCache && (now - _shareCacheAt) < SHARE_CACHE_TTL) {
|
||
return _shareCache;
|
||
}
|
||
const cfg = await _fetchShareFromServer(path);
|
||
_shareCache = cfg || _buildFallback(path);
|
||
_shareCacheAt = now;
|
||
return _shareCache;
|
||
};
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
__mpShareCache: null, // 当前页面的分享配置
|
||
};
|
||
},
|
||
onLoad() {
|
||
try {
|
||
const path = _getCurrentPath(true);
|
||
_getShareConfig(path).then(cfg => {
|
||
// 后端未返回配置时,注入站点名作为默认标题
|
||
if (cfg && !cfg.title) {
|
||
const siteName = (this.$store && this.$store.state && this.$store.state.siteInfo && this.$store.state.siteInfo.site_name) || '';
|
||
cfg.title = siteName || '小程序分享';
|
||
}
|
||
this.__mpShareCache = cfg;
|
||
});
|
||
} catch (e) {}
|
||
},
|
||
onShow() {
|
||
// 兜底:进入页面后如果尚未就绪,再异步拉一次
|
||
if (!this.__mpShareCache) {
|
||
try {
|
||
const path = _getCurrentPath(true);
|
||
_getShareConfig(path).then(cfg => {
|
||
this.__mpShareCache = cfg;
|
||
});
|
||
} catch (e) {}
|
||
}
|
||
},
|
||
/**
|
||
* 全局默认 onShareAppMessage:
|
||
* uni-app 规范:组件自身的钩子优先级 > mixin,因此已显式声明的页面不受影响。
|
||
*/
|
||
onShareAppMessage() {
|
||
const cfg = this.__mpShareCache;
|
||
if (cfg && cfg.title) {
|
||
return {
|
||
title: cfg.title,
|
||
path: cfg.path,
|
||
imageUrl: cfg.imageUrl || ''
|
||
};
|
||
}
|
||
// 后端无配置时:使用站点名 + 当前路径
|
||
return {
|
||
title: _buildFallback(_getCurrentPath(true)).title,
|
||
path: _getCurrentPath(true)
|
||
};
|
||
},
|
||
/**
|
||
* 全局默认 onShareTimeline(朋友圈):
|
||
* 朋友圈只支持 query(不能直接传 path),从 cfg.path 中抽取 ? 之后的 query。
|
||
*/
|
||
onShareTimeline() {
|
||
const cfg = this.__mpShareCache;
|
||
if (cfg && cfg.title) {
|
||
const path = cfg.path || _getCurrentPath(true);
|
||
const query = path.indexOf('?') > 0 ? path.split('?')[1] : path;
|
||
return {
|
||
title: cfg.title,
|
||
query,
|
||
imageUrl: cfg.imageUrl || ''
|
||
};
|
||
}
|
||
const path = _getCurrentPath(true);
|
||
const query = path.indexOf('?') > 0 ? path.split('?')[1] : '';
|
||
return {
|
||
title: _buildFallback(path).title,
|
||
query
|
||
};
|
||
},
|
||
methods: {
|
||
/**
|
||
* 获取当前小程序页面路径(含 query)
|
||
*/
|
||
$mpShare_currentPath() {
|
||
return _getCurrentPath(true);
|
||
},
|
||
/**
|
||
* 刷新当前页分享配置(用于页面参数变更后想重置转发 path 的场景)
|
||
*/
|
||
$mpShare_refresh() {
|
||
_shareCache = null;
|
||
_shareCacheAt = 0;
|
||
return _getShareConfig(_getCurrentPath(true)).then(cfg => {
|
||
this.__mpShareCache = cfg;
|
||
return cfg;
|
||
});
|
||
},
|
||
/**
|
||
* 复制当前页面小程序路径到剪贴板
|
||
* 微信小程序没有"页面 URL"概念,仅可复制形如:
|
||
* pages/goods/detail?id=123&source_member=xx
|
||
* 的小程序内部路径,便于分享者在聊天里粘贴。
|
||
* 优先复用 util.copyCurrentPath。
|
||
*/
|
||
$mpShare_copyPath() {
|
||
const path = _getCurrentPath(true);
|
||
const text = path.replace(/^\//, ''); // 去掉前导斜杠
|
||
if (this.$util && typeof this.$util.copyCurrentPath === 'function') {
|
||
return this.$util.copyCurrentPath({});
|
||
}
|
||
// util.copyCurrentPath 不存在(极端情况:util 模块未加载)时直接调 uni API
|
||
return new Promise((resolve, reject) => {
|
||
uni.setClipboardData({
|
||
data: text,
|
||
success: () => resolve(text),
|
||
fail: (err) => reject(err)
|
||
});
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
// #endif
|