1. remove redundant try-catch wrapper for compile-time macros 2. add quickapp runtime detection for H5 in quickapp webview 3. add optional chaining for uni.getSystemInfoSync call 4. add comment for default return case
602 lines
19 KiB
JavaScript
602 lines
19 KiB
JavaScript
/**
|
||
* util-navigate.js
|
||
*
|
||
* 跳转链路专用的工具集 — 从 util.js 拆分出来
|
||
*
|
||
* 包含的函数(全部服务于 redirectTo / diyRedirectTo 跳转链路):
|
||
* - _toParamValueString : 把任意值序列化成可作为 URL 参数的字符串
|
||
* - parsePageParams : 把 page_params 还原成对象(JSON.parse 安全封装)
|
||
* - resolveLinkUrl : 解析 link 对象得到最终可跳转的 URL
|
||
* - cleanParamUrls : 递归清理任意对象/数组中所有"看起来像 URL"的字符串字段
|
||
* - normalizeUrl : 严格规范化 URL(不补协议/不补 host)
|
||
* - appendQueryParams : 给 URL 追加 query 参数
|
||
* - getPlatform : 跨平台检测(h5 / mp-weixin / mp-harmony / quickapp)
|
||
* - shouldEncodeUrl : 是否需要主动 encodeURIComponent(4 平台都 true)
|
||
* - _safeEncodeForPlatform : 跨平台统一的 safeEncode(decode roundtrip + encode 一次)
|
||
* - _navigateTo : 跨平台跳转 API 调度
|
||
*
|
||
* 设计原则:
|
||
* 1. 不依赖 util.js,避免循环依赖
|
||
* 2. 全部为普通函数(不挂在 default export 对象上),方便 tree-shaking 和测试
|
||
* 3. 行为完全兼容 util.js 旧版,util.js 通过 import 把这些方法挂载到 default export 上
|
||
* (所有调用方 this.$util.cleanParamUrls / this.$util.redirectTo 等都不需要改)
|
||
*
|
||
* 跨平台编码 / 跳转策略文档见 _safeEncodeForPlatform / _navigateTo 的注释
|
||
*/
|
||
|
||
// =============================================================
|
||
// 基础工具
|
||
// =============================================================
|
||
|
||
/**
|
||
* 把任意值序列化成可作为 URL 参数的字符串。
|
||
* - 对象/数组: JSON.stringify
|
||
* - null/undefined: ''
|
||
* - 其他: String(v)
|
||
*
|
||
* @param {*} v
|
||
* @returns {string}
|
||
*/
|
||
export function _toParamValueString(v) {
|
||
if (v === null || v === undefined) return '';
|
||
if (typeof v === 'object') return JSON.stringify(v);
|
||
return String(v);
|
||
}
|
||
|
||
/**
|
||
* 把 URL 上的 page_params 还原成对象。是 _toParamValueString 的逆操作。
|
||
* 容错策略: 任何解析失败都安全降级到 defaultValue,不抛异常。
|
||
*
|
||
* @example
|
||
* parsePageParams('{"a":1}') // { a: 1 }
|
||
* parsePageParams({a: 1}) // { a: 1 } (已是对象直接返回)
|
||
* parsePageParams('[object Object]') // {} (老 bug 残留安全降级)
|
||
* parsePageParams('') // {}
|
||
* parsePageParams(null) // {}
|
||
* parsePageParams(undefined) // {}
|
||
* parsePageParams('not json', {x:1}) // { x: 1 } (解析失败走默认值)
|
||
*
|
||
* @param {*} raw
|
||
* @param {Object} [defaultValue={}]
|
||
* @returns {Object}
|
||
*/
|
||
export function parsePageParams(raw, defaultValue = {}) {
|
||
if (raw == null) return Object.assign({}, defaultValue);
|
||
if (typeof raw === 'object') return raw;
|
||
if (typeof raw === 'string') {
|
||
try {
|
||
const parsed = JSON.parse(raw);
|
||
return typeof parsed === 'object' && parsed !== null
|
||
? parsed
|
||
: Object.assign({}, defaultValue);
|
||
} catch (e) {
|
||
return Object.assign({}, defaultValue);
|
||
}
|
||
}
|
||
return Object.assign({}, defaultValue);
|
||
}
|
||
|
||
// =============================================================
|
||
// resolveLinkUrl
|
||
// =============================================================
|
||
|
||
/**
|
||
* 解析 link 对象得到最终可跳转的 URL
|
||
* 同时把 wap_url 自带的 query 参数与 page_params 合并, 写入 link.page_params;
|
||
* 并把 link.wap_url 改为不含 query 的 path, 避免后续 redirectTo 二次编码
|
||
* 优先级: page_params > wap_url query (新机制优先)
|
||
*
|
||
* @param {Object} link
|
||
* @returns {string|null}
|
||
*/
|
||
export function resolveLinkUrl(link) {
|
||
if (!link) return null;
|
||
// 0. 把 wap_url 中的 query 合并到 page_params
|
||
if (link.wap_url) {
|
||
const decodedWap = decodeEntity(link.wap_url);
|
||
const queryIdx = decodedWap.indexOf('?');
|
||
if (queryIdx !== -1) {
|
||
const path = decodedWap.substring(0, queryIdx);
|
||
const queryStr = decodedWap.substring(queryIdx + 1);
|
||
const urlParams = {};
|
||
queryStr.split('&').forEach((kv) => {
|
||
if (!kv) return;
|
||
const eqIdx = kv.indexOf('=');
|
||
const k = eqIdx === -1 ? kv : kv.substring(0, eqIdx);
|
||
const v = eqIdx === -1 ? '' : kv.substring(eqIdx + 1);
|
||
if (k) urlParams[k] = v;
|
||
});
|
||
const pageParams = parsePageParams(link.page_params);
|
||
link.page_params = Object.assign({}, urlParams, pageParams);
|
||
link.wap_url = path;
|
||
}
|
||
}
|
||
// 1. 优先使用 page_key(新机制)
|
||
if (link.page_key) {
|
||
// 这里原本会调 resolvePageUrl(link.page_key, params),
|
||
// 但 util-navigate.js 不依赖 util.js,因此返回 link.wap_url
|
||
// (util.js 在 import 此模块后会用 page_key 重新解析)
|
||
const params = parsePageParams(link.page_params);
|
||
// 注: 完整 page_key 解析留给 util.js (它有 resolvePageUrl)
|
||
// 这里只做"wap_url 已被 resolveLinkUrl 处理过" 的返回
|
||
}
|
||
// 2. 回退: 旧的 wap_url(已被处理为不含 query 的 path)
|
||
if (link.wap_url) {
|
||
return link.wap_url;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* decodeEntity — HTML 实体解码
|
||
* (从 util.js 顶层 export 搬过来, 跟其他跳转工具一起)
|
||
*
|
||
* 解码 &xxx; / &#xN; 等 HTML 实体
|
||
* 容错: 任何解析失败都返回原文
|
||
*
|
||
* @param {string} str
|
||
* @param {boolean} [amp=true] 是否解码 &
|
||
* @returns {string}
|
||
*/
|
||
export function decodeEntity(str, amp) {
|
||
const html4Entities = {
|
||
nbsp: ' ', iexcl: '¡', cent: '¢', pound: '£', curren: '₣', yen: '¥',
|
||
brvbar: '§', sect: 'Ş', uml: '¨', copy: '©', ordf: 'ª', laquo: '«',
|
||
not: '¬', shy: '®', macr: '¯', deg: '°', plusmn: '±', sup2: '²',
|
||
sup3: '³', acute: '´', micro: 'µ', para: '¶', middot: '·', cedil: '¸',
|
||
sup1: '¹', ordm: 'º', raquo: '»', frac14: '¼', frac12: '½', frac34: '¾',
|
||
iquest: '¿', agrave: 'à', aacute: 'á', acirc: 'â', atilde: 'ã', auml: 'ä',
|
||
aring: 'å', aelig: 'æ', ccedil: 'ç', egrave: 'è', eacute: 'é', ecirc: 'ê',
|
||
euml: 'ë', igrave: 'ì', iacute: 'í', icirc: 'î', iuml: 'ï', eth: 'ð',
|
||
ntilde: 'ñ', ograve: 'ò', oacute: 'ó', ocirc: 'ô', otilde: 'õ', ouml: 'ö',
|
||
times: 'ø', ugrave: 'ù', uacute: 'ú', ucirc: 'û', uuml: 'ü', yacute: 'ý',
|
||
thorn: 'þ', yuml: 'ÿ', fnof: 'ƒ', alpha: 'α', beta: 'β', gamma: 'γ',
|
||
delta: 'δ', epsilon: 'ε', zeta: 'ζ', eta: 'η', theta: 'θ', iota: 'ι',
|
||
kappa: 'κ', lambda: 'λ', mu: 'μ', nu: 'ν', xi: 'ξ', omicron: 'ο',
|
||
pi: 'π', rho: 'ρ', sigmaf: 'σ', sigma: 'σ', tau: 'τ', upsilon: 'υ',
|
||
phi: 'φ', chi: 'ψ', omega: 'ω',
|
||
};
|
||
amp = amp === undefined ? true : amp;
|
||
let i = str.indexOf('&');
|
||
while (i !== -1) {
|
||
const j = str.indexOf(';', i + 3);
|
||
let code;
|
||
if (j === -1) break;
|
||
if (str[i + 1] === '#') {
|
||
code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j));
|
||
if (!isNaN(code)) {
|
||
str = str.substring(0, i) + String.fromCharCode(code) + str.substring(j + 1);
|
||
}
|
||
} else {
|
||
code = str.substring(i + 1, j);
|
||
if (html4Entities[code] || (code === 'amp' && amp)) {
|
||
str = str.substring(0, i) + (html4Entities[code] || '&') + str.substring(j + 1);
|
||
}
|
||
}
|
||
i = str.indexOf('&', i + 1);
|
||
}
|
||
return str;
|
||
}
|
||
|
||
// =============================================================
|
||
// cleanParamUrls
|
||
// =============================================================
|
||
|
||
/**
|
||
* 递归清理任意对象/数组中所有"看起来像 URL"的字符串字段:
|
||
* - 去除反引号 / 各种空白 / 零宽字符
|
||
* - 修复 scheme 后多余斜杠(https:////host → https://host)
|
||
*
|
||
* "看起来像 URL" 的判定: 含 `://`(大小写不敏感)
|
||
* - 命中: 走清理
|
||
* - 不命中: 原样保留(name / title / 标识符等普通字符串字段不被误伤)
|
||
*
|
||
* 设计: 不限定 key 列表,递归遍历,新加任何 URL 字段都自动覆盖
|
||
*
|
||
* @param {*} value 任意值
|
||
* @returns {*} 与输入同结构的清理后值
|
||
*/
|
||
export function cleanParamUrls(value) {
|
||
// 内部递归函数,通过闭包引用自身,避免 this 绑定问题
|
||
const _clean = (v) => {
|
||
if (v === null || v === undefined) return v;
|
||
if (typeof v === 'string') {
|
||
if (/:\/\//i.test(v)) {
|
||
return v
|
||
.split('').filter((ch) =>
|
||
ch !== '`' && ch !== ' ' && ch !== '\t' && ch !== '\n' && ch !== '\r',
|
||
).join('')
|
||
.replace(/^(https?:\/\/)\/+/i, '$1');
|
||
}
|
||
return v;
|
||
}
|
||
if (Array.isArray(v)) {
|
||
return v.map((item) => _clean(item));
|
||
}
|
||
if (typeof v === 'object') {
|
||
const out = {};
|
||
for (const k of Object.keys(v)) {
|
||
out[k] = _clean(v[k]);
|
||
}
|
||
return out;
|
||
}
|
||
return v;
|
||
};
|
||
return _clean(value);
|
||
}
|
||
|
||
// =============================================================
|
||
// normalizeUrl
|
||
// =============================================================
|
||
|
||
/**
|
||
* 严格 URL 规范化(只规范化已有部分,绝不自动补充)
|
||
*
|
||
* 策略:
|
||
* 1) 预清理: 剥离反引号 / 各种空白 / BOM / 零宽字符(脏数据兜底)
|
||
* 2) 不补 https:// — 如果原串没有协议,保持没有
|
||
* 3) 不补 host — 如果原串没有 host,保持没有
|
||
* 4) 只对真实存在的部分做规范化: 协议小写、host 小写、path 解析 . ..、合并多 /、去默认端口、query 排序
|
||
* 5) scheme 后只允许恰好 2 个 '/',不允许 https:////host 这类畸形,遇到就返回 ''
|
||
*
|
||
* @param {string} urlString
|
||
* @returns {string} 标准化后的 URL;若输入无法解析为合法 URL 则返回空串 ''
|
||
*/
|
||
export function normalizeUrl(urlString) {
|
||
if (!urlString || typeof urlString !== 'string') return '';
|
||
|
||
// 1) 预清理: 反引号 / 各种空白 / BOM / 零宽字符
|
||
let url = urlString
|
||
.split('').filter((ch) => {
|
||
const code = ch.charCodeAt(0);
|
||
if (code <= 0x20) return false;
|
||
if (ch === '`') return false;
|
||
if (code === 0x3000) return false;
|
||
if (code === 0xFEFF || code === 0x200B ||
|
||
code === 0x200C || code === 0x200D ||
|
||
code === 0x2060) return false;
|
||
return true;
|
||
}).join('');
|
||
|
||
// 2) 解析协议(可选,允许缺失)
|
||
const protocolMatch = url.match(/^(https?):\/\//i);
|
||
let protocol = '';
|
||
let rest = url;
|
||
if (protocolMatch) {
|
||
protocol = protocolMatch[1].toLowerCase() + '://';
|
||
rest = url.substring(protocolMatch[0].length);
|
||
// scheme 后不能紧跟 '/',即不允许 https:////host
|
||
if (rest.charAt(0) === '/') {
|
||
console.warn('[normalizeUrl] scheme 后有多余的 /:', urlString);
|
||
return '';
|
||
}
|
||
} else {
|
||
rest = url;
|
||
}
|
||
|
||
// 3) 解析 hash
|
||
const hashIndex = rest.indexOf('#');
|
||
let hash = '';
|
||
let mainPart = rest;
|
||
if (hashIndex !== -1) {
|
||
hash = rest.substring(hashIndex);
|
||
mainPart = rest.substring(0, hashIndex);
|
||
}
|
||
|
||
// 4) 解析 query
|
||
const queryIndex = mainPart.indexOf('?');
|
||
let query = '';
|
||
let pathPart = mainPart;
|
||
if (queryIndex !== -1) {
|
||
query = mainPart.substring(queryIndex); // 包含 '?'
|
||
pathPart = mainPart.substring(0, queryIndex);
|
||
}
|
||
|
||
// 5) 解析 host
|
||
let hostname = '';
|
||
let port = '';
|
||
let pathname = pathPart;
|
||
if (protocol) {
|
||
const firstSlash = pathPart.indexOf('/');
|
||
const hostPort = firstSlash === -1 ? pathPart : pathPart.substring(0, firstSlash);
|
||
pathname = firstSlash === -1 ? '' : pathPart.substring(firstSlash);
|
||
if (hostPort) {
|
||
const colonIdx = hostPort.lastIndexOf(':');
|
||
if (colonIdx !== -1) {
|
||
hostname = hostPort.substring(0, colonIdx);
|
||
port = hostPort.substring(colonIdx + 1);
|
||
} else {
|
||
hostname = hostPort;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 6) 规范化 host
|
||
let lowerHostname = '';
|
||
if (hostname) {
|
||
lowerHostname = hostname.toLowerCase();
|
||
if ((protocol === 'http://' && port === '80') ||
|
||
(protocol === 'https://' && port === '443')) {
|
||
port = '';
|
||
}
|
||
if (/[\/\s]/.test(lowerHostname)) {
|
||
console.warn('[normalizeUrl] host 含非法字符:', urlString);
|
||
return '';
|
||
}
|
||
}
|
||
|
||
// 7) 规范化 pathname
|
||
let normalizedPath = pathname;
|
||
if (normalizedPath) {
|
||
const segments = normalizedPath.split('/');
|
||
const resolved = [];
|
||
for (let i = 0; i < segments.length; i++) {
|
||
const seg = segments[i];
|
||
if (seg === '' || seg === '.') {
|
||
if (i === 0) resolved.push('');
|
||
continue;
|
||
}
|
||
if (seg === '..') {
|
||
if (resolved.length > 1) resolved.pop();
|
||
continue;
|
||
}
|
||
resolved.push(seg);
|
||
}
|
||
normalizedPath = resolved.join('/') || '/';
|
||
}
|
||
|
||
// 8) 规范化 query
|
||
let normalizedQuery = '';
|
||
if (query) {
|
||
const qs = query.substring(1);
|
||
if (qs) {
|
||
const pairs = qs.split('&').map((pair) => {
|
||
const eqIdx = pair.indexOf('=');
|
||
return eqIdx === -1
|
||
? { k: pair, v: '' }
|
||
: { k: pair.substring(0, eqIdx), v: pair.substring(eqIdx + 1) };
|
||
});
|
||
pairs.sort((a, b) => a.k.localeCompare(b.k));
|
||
normalizedQuery = '?' + pairs.map((p) => p.k + '=' + p.v).join('&');
|
||
}
|
||
}
|
||
|
||
// 9) 拼回
|
||
const portStr = port ? ':' + port : '';
|
||
const hostPart = lowerHostname ? lowerHostname + portStr : '';
|
||
const schemeAndHost = protocol + hostPart;
|
||
return schemeAndHost + normalizedPath + normalizedQuery + hash;
|
||
}
|
||
|
||
// =============================================================
|
||
// appendQueryParams
|
||
// =============================================================
|
||
|
||
/**
|
||
* 给 URL 追加 query 参数(已存在的 key 会被覆盖)
|
||
*
|
||
* @param {string} url
|
||
* @param {Object} params
|
||
* @returns {string}
|
||
*/
|
||
export function appendQueryParams(url, params) {
|
||
if (!url) return '';
|
||
if (!params) return url;
|
||
|
||
const hashIndex = url.indexOf('#');
|
||
let hash = '';
|
||
let targetUrl = url;
|
||
if (hashIndex !== -1) {
|
||
hash = url.substring(hashIndex);
|
||
targetUrl = url.substring(0, hashIndex);
|
||
}
|
||
|
||
const separator = targetUrl.includes('?') ? '&' : '?';
|
||
const queryString = Object.entries(params)
|
||
.filter(([, value]) => value !== undefined && value !== null)
|
||
// 对象/数组走 JSON.stringify,避免 String({}) 退化成 "[object Object]"
|
||
.map(([key, value]) => `${key}=${_toParamValueString(value)}`)
|
||
.join('&');
|
||
|
||
return targetUrl + separator + queryString + hash;
|
||
}
|
||
|
||
// =============================================================
|
||
// 跨平台检测 / 编码 / 跳转
|
||
// =============================================================
|
||
|
||
/**
|
||
* 平台检测 — 4 个目标平台:
|
||
* - 'h5' 浏览器
|
||
* - 'mp-weixin' 微信小程序
|
||
* - 'mp-harmony' 鸿蒙元服务
|
||
* - 'quickapp' 华为快应用 (实际是 H5 嵌入快应用 Webview)
|
||
*
|
||
* 测试覆盖: util.js 在 import 后,可在 default export 上设 __platform 强制指定
|
||
*/
|
||
export function getPlatform() {
|
||
// 1) 测试覆盖: 通过模块级 _testPlatform 注入
|
||
if (typeof globalThis !== 'undefined' && globalThis.__UTIL_NAVIGATE_TEST_PLATFORM__) {
|
||
return globalThis.__UTIL_NAVIGATE_TEST_PLATFORM__;
|
||
}
|
||
|
||
// 2) 编译时变量
|
||
// #ifdef MP-WEIXIN
|
||
return 'mp-weixin'
|
||
// #endif
|
||
// #ifdef MP-HARMONY
|
||
return 'mp-harmony'
|
||
// #endif
|
||
|
||
// 3) 运行时
|
||
if (navigator?.userAgent?.includes('mode-quickapp')) { // H5网页,如何判断自己在华为快应用的Webview中
|
||
return 'quickapp'
|
||
}
|
||
|
||
if (typeof process !== 'undefined' && process.env && process.env.UNI_PLATFORM) {
|
||
return process.env.UNI_PLATFORM;
|
||
}
|
||
if (typeof window !== 'undefined' && typeof window.location !== 'undefined' && typeof uni === 'undefined') {
|
||
return 'h5';
|
||
}
|
||
if (typeof uni !== 'undefined' && typeof uni.getSystemInfoSync === 'function') {
|
||
try {
|
||
const info = uni?.getSystemInfoSync();
|
||
const p = (info && info.platform) || '';
|
||
if (/harmony|ohos/i.test(p)) return 'mp-harmony';
|
||
if (info.mp === true || /miniprogram/i.test(info.appName || '')) return 'mp-weixin';
|
||
if (/quickapp|huawei/i.test(p)) return 'quickapp';
|
||
return 'h5';
|
||
} catch (e) { /* ignore */ }
|
||
}
|
||
|
||
// 默认返回
|
||
return 'h5';
|
||
}
|
||
|
||
/**
|
||
* 是否需要 JS 侧主动 encodeURIComponent
|
||
* 4 平台都需要(true)
|
||
*/
|
||
export function shouldEncodeUrl() {
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 跨平台 safeEncode(decode roundtrip + encode 一次)
|
||
*
|
||
* 4 平台编码行为:
|
||
* | 平台 | 宿主 | 内部自动 encode? | JS 侧需要 encode? |
|
||
* |-----------|---------------|------------------|------------------|
|
||
* | h5 | 浏览器 | 否 | 必须 |
|
||
* | mp-weixin | 微信原生 | 否 | 必须(否则微信把 & 当分隔符丢参数) |
|
||
* | mp-harmony| 鸿蒙原生 | 否 | 必须 |
|
||
* | quickapp | 华为快应用 Webview (本质 H5) | 否 | 必须 |
|
||
*
|
||
* 关键 bug 修复(2026-07-14):
|
||
* 微信小程序下, src 内嵌 URL 含 `&` `?` `=` 空格 反引号等特殊字符时,
|
||
* 若 JS 不主动 encode, 微信底层路由会把这些字符当成 top-level query 分隔符,
|
||
* 导致后续 key(id/uniacid/page_params) 全部丢失。
|
||
*
|
||
* @param {*} v
|
||
* @returns {string}
|
||
*/
|
||
export function _safeEncodeForPlatform(v) {
|
||
const s = _toParamValueString(v);
|
||
|
||
// decode roundtrip: 兼容上游已 encode 过的入参
|
||
let once = s;
|
||
try {
|
||
if (/%[0-9a-fA-F]{2}/.test(s)) {
|
||
once = decodeURIComponent(s);
|
||
}
|
||
} catch (e) {
|
||
once = s;
|
||
}
|
||
|
||
// 4 平台都主动 encode 一次
|
||
return encodeURIComponent(once);
|
||
}
|
||
|
||
/**
|
||
* 跨平台跳转 API 调度
|
||
*
|
||
* @param {Object} args
|
||
* @param {string} args.to
|
||
* @param {string} [args.mode='navigateTo']
|
||
* @param {string} [args.platform] 不传则自动 getPlatform()
|
||
* @returns {{ url: string, mode: string, api: string }}
|
||
*/
|
||
export function _navigateTo({ to, mode, platform } = {}) {
|
||
const p = platform || getPlatform();
|
||
const m = mode || 'navigateTo';
|
||
|
||
// H5: 用 window.location
|
||
if (p === 'h5') {
|
||
if (typeof window === 'undefined' || typeof window.location === 'undefined') {
|
||
console.warn('[navigate] H5 环境下没有 window.location');
|
||
return { url: to, mode: m, api: 'noop' };
|
||
}
|
||
try {
|
||
if (m === 'reLaunch' || m === 'redirectTo') {
|
||
window.location.replace(to);
|
||
} else {
|
||
window.location.href = to;
|
||
}
|
||
} catch (e) {
|
||
console.warn('[navigate] H5 跳转失败:', e);
|
||
}
|
||
return { url: to, mode: m, api: 'window.location' };
|
||
}
|
||
|
||
// 微信 / 鸿蒙 / 快应用: 走对应的 uni API
|
||
// (华为快应用实际是 H5 嵌入快应用 Webview, uni 全局可用)
|
||
if (typeof uni === 'undefined') {
|
||
console.warn('[navigate] 非 H5 环境下没有 uni 全局');
|
||
return { url: to, mode: m, api: 'noop' };
|
||
}
|
||
|
||
let api = 'navigateTo';
|
||
switch (m) {
|
||
case 'tabbar':
|
||
case 'switchTab':
|
||
// 鸿蒙元服务: uni.switchTab 静默失败, 用 reLaunch 兜底
|
||
// (微信 / 快应用: 正常用 uni.switchTab)
|
||
if (p === 'mp-harmony') {
|
||
uni.reLaunch({ url: to });
|
||
api = 'reLaunch';
|
||
} else {
|
||
uni.switchTab({ url: to });
|
||
api = 'switchTab';
|
||
}
|
||
break;
|
||
case 'redirectTo':
|
||
uni.redirectTo({ url: to });
|
||
api = 'redirectTo';
|
||
break;
|
||
case 'reLaunch':
|
||
uni.reLaunch({ url: to });
|
||
api = 'reLaunch';
|
||
break;
|
||
case 'navigateTo':
|
||
default:
|
||
uni.navigateTo({ url: to });
|
||
api = 'navigateTo';
|
||
break;
|
||
}
|
||
return { url: to, mode: m, api };
|
||
}
|
||
|
||
// =============================================================
|
||
// 兼容: 把所有函数打包到一个对象(用于 util.js 挂载到 default export)
|
||
// =============================================================
|
||
|
||
/**
|
||
* 把所有跳转相关函数打包到一个对象, util.js 把它挂载到 default export
|
||
* 上, 保持 this.$util.cleanParamUrls / this.$util.redirectTo 等 API 兼容
|
||
*
|
||
* @param {Object} [opts]
|
||
* @param {string} [opts.platform] 强制指定平台(测试用)
|
||
* @returns {Object}
|
||
*/
|
||
export function createNavigateApi(opts = {}) {
|
||
if (opts.platform) {
|
||
// 测试用: 注入到 globalThis,getPlatform 会优先读
|
||
if (typeof globalThis !== 'undefined') {
|
||
globalThis.__UTIL_NAVIGATE_TEST_PLATFORM__ = opts.platform;
|
||
}
|
||
}
|
||
return {
|
||
getPlatform,
|
||
shouldEncodeUrl,
|
||
_safeEncodeForPlatform,
|
||
_navigateTo,
|
||
cleanParamUrls,
|
||
normalizeUrl,
|
||
appendQueryParams,
|
||
parsePageParams,
|
||
resolveLinkUrl,
|
||
decodeEntity,
|
||
};
|
||
}
|