From 679c53c501fd0e9d6ab154d6feba1bf419c06b0a Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 10 Jul 2026 13:56:37 +0800 Subject: [PATCH] =?UTF-8?q?refactor(util):=20=E7=BB=9F=E4=B8=80=20URL=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=BA=8F=E5=88=97=E5=8C=96=E4=B8=8E=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增_toParamValueString处理复杂参数序列化 2. 新增parsePageParams作为page_params统一解析工具 3. 重构多处参数处理逻辑,避免重复代码和对象转字符串异常 4. 统一处理对象/数组参数的JSON序列化,避免出现[object Object] --- common/js/util.js | 97 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/common/js/util.js b/common/js/util.js index 76b456f..1600e20 100644 --- a/common/js/util.js +++ b/common/js/util.js @@ -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