refactor(util): 统一 URL 参数序列化与解析逻辑
1. 新增_toParamValueString处理复杂参数序列化 2. 新增parsePageParams作为page_params统一解析工具 3. 重构多处参数处理逻辑,避免重复代码和对象转字符串异常 4. 统一处理对象/数组参数的JSON序列化,避免出现[object Object]
This commit is contained in:
@@ -35,6 +35,57 @@ export function decodeEntity(str, amp) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 把任意值序列化成可作为 URL 参数的字符串。
|
||||
* - null / undefined → '' (由调用方负责过滤空值)
|
||||
* - 对象 / 数组 → JSON.stringify (避免 String({}) 退化成 "[object Object]")
|
||||
* - 其它类型(number / boolean / string)→ String(v),保持原行为
|
||||
*
|
||||
* 与消费端约定:page_params 一律以 JSON 字符串形式落 URL,
|
||||
* 消费侧用 parsePageParams 还原为对象。
|
||||
*
|
||||
* @example
|
||||
* _toParamValueString({a:1}) // '{"a":1}'
|
||||
* _toParamValueString('hello') // 'hello'
|
||||
* _toParamValueString(null) // ''
|
||||
* _toParamValueString([1, 2]) // '[1,2]'
|
||||
*
|
||||
* @param {*} v
|
||||
* @returns {string}
|
||||
*/
|
||||
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 page_params 原始值(可能是 object / string / null / undefined)
|
||||
* @param {Object} [defaultValue={}] 解析失败时的兜底值
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function parsePageParams(raw, defaultValue = {}) {
|
||||
if (raw == null || raw === '') return defaultValue
|
||||
if (typeof raw === 'object') return raw
|
||||
if (typeof raw === 'string') {
|
||||
try { return JSON.parse(raw) } catch (e) { return defaultValue }
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
|
||||
// 关于页面路径,使用常量导出
|
||||
export const INDEX_PAGE_URL = '/pages/index/index';
|
||||
export const CATEGORY_PAGE_URL = '/pages_goods/category';
|
||||
@@ -282,13 +333,16 @@ export const resolvePageUrl = (page_key, params = {}) => {
|
||||
Object.keys(params).forEach(function (key) {
|
||||
const value = params[key];
|
||||
if (value === null || value === undefined || value === '') return;
|
||||
// 对象/数组走 JSON.stringify,避免 String({}) 退化成 "[object Object]"
|
||||
// 消费端 resolveLinkUrl 已用 JSON.parse 还原
|
||||
const encodedValue = _toParamValueString(value);
|
||||
// H5:浏览器不自动编码,需要手动 encodeURIComponent
|
||||
// 小程序/App:uni.navigateTo 会自动编码,这里不能再 encode,否则双重编码
|
||||
// #ifdef H5
|
||||
parts.push(key + '=' + encodeURIComponent(value));
|
||||
parts.push(key + '=' + encodeURIComponent(encodedValue));
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
parts.push(key + '=' + value);
|
||||
parts.push(key + '=' + encodedValue);
|
||||
// #endif
|
||||
});
|
||||
if (parts.length > 0) {
|
||||
@@ -386,12 +440,8 @@ export const resolveLinkUrl = (link) => {
|
||||
const v = eqIdx === -1 ? '' : kv.substring(eqIdx + 1);
|
||||
if (k) urlParams[k] = v;
|
||||
});
|
||||
// 解析 page_params (可能是字符串或对象)
|
||||
let pageParams = link.page_params;
|
||||
if (typeof pageParams === 'string' && pageParams) {
|
||||
try { pageParams = JSON.parse(pageParams); } catch (e) { pageParams = {}; }
|
||||
}
|
||||
pageParams = pageParams || {};
|
||||
// 解析 page_params (可能是字符串或对象) — 走统一 helper,避免分散 try/catch
|
||||
const pageParams = parsePageParams(link.page_params);
|
||||
// 合并: page_params 覆盖 urlParams (新机制优先)
|
||||
link.page_params = Object.assign({}, urlParams, pageParams);
|
||||
// 把 link.wap_url 改为不含 query 的 path
|
||||
@@ -400,11 +450,8 @@ export const resolveLinkUrl = (link) => {
|
||||
}
|
||||
// 1. 优先使用 page_key(新机制)
|
||||
if (link.page_key) {
|
||||
let params = link.page_params;
|
||||
if (typeof params === 'string' && params) {
|
||||
try { params = JSON.parse(params); } catch (e) { params = {}; }
|
||||
}
|
||||
const url = resolvePageUrl(link.page_key, params || {});
|
||||
const params = parsePageParams(link.page_params);
|
||||
const url = resolvePageUrl(link.page_key, params);
|
||||
if (url) return url;
|
||||
}
|
||||
// 2. 回退:旧的 wap_url(已被处理为不含 query 的 path)
|
||||
@@ -618,6 +665,9 @@ export default {
|
||||
/** 解析 link 对象得到最终可跳转的 URL(优先级: page_key → wap_url) */
|
||||
resolveLinkUrl,
|
||||
|
||||
/** 把 page_params 还原成对象(JSON.parse 的安全封装) */
|
||||
parsePageParams,
|
||||
|
||||
/** 判断给定路径是否是 TabBar 根页面(用于动态控制返回按钮显示) */
|
||||
isTabBarRootPage,
|
||||
|
||||
@@ -658,7 +708,8 @@ export default {
|
||||
// - H5: 浏览器不会自动编码 url,若参数含 & ? = 等特殊字符会被 queryString 错位解析
|
||||
// => 这里仍需手动 encodeURIComponent,decode->encode roundtrip 兼容调用方已编码的入参
|
||||
const safeEncode = (v) => {
|
||||
const s = v == null ? '' : String(v);
|
||||
// 对象/数组走 JSON.stringify,避免 String({}) 退化成 "[object Object]"
|
||||
const s = _toParamValueString(v);
|
||||
// #ifdef H5
|
||||
try {
|
||||
return encodeURIComponent(decodeURIComponent(s));
|
||||
@@ -667,6 +718,18 @@ export default {
|
||||
}
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
// 非 H5:uni.navigateTo / router.pushUrl 内部会再对 url 整体编码一次。
|
||||
// 如果调用方已经手动 encode 过 page_params (或上游已经 encode 过的 url 字符串),
|
||||
// 这里再走一遍就会出现双重编码(形如 %25xx)。
|
||||
// 兜底:仅当输入里包含 %xx 这种"看起来是已编码"的模式时,先 decode 一次,让 uni 再 encode 时得到单层编码。
|
||||
// 原始字符串(没有 %xx)不受影响,避免误把含 % 的原文吃掉。
|
||||
if (/%[0-9a-fA-F]{2}/.test(s)) {
|
||||
try {
|
||||
return decodeURIComponent(s);
|
||||
} catch (e) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
// #endif
|
||||
};
|
||||
@@ -2009,7 +2072,8 @@ export default {
|
||||
const urlObj = new URL(targetUrl);
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
urlObj.searchParams.append(key, String(value));
|
||||
// 对象/数组走 JSON.stringify,避免 String({}) 退化成 "[object Object]"
|
||||
urlObj.searchParams.append(key, _toParamValueString(value));
|
||||
}
|
||||
}
|
||||
return urlObj.toString();
|
||||
@@ -2039,7 +2103,8 @@ export default {
|
||||
// 这里不能再 encodeURIComponent,否则会被平台再编码一层导致双重编码
|
||||
const queryString = Object.entries(params)
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
// 对象/数组走 JSON.stringify,避免 String({}) 退化成 "[object Object]"
|
||||
.map(([key, value]) => `${key}=${_toParamValueString(value)}`)
|
||||
.join('&');
|
||||
|
||||
// 重新拼接成完整的 URL
|
||||
|
||||
Reference in New Issue
Block a user