/** * ============================================================ * URL 参数解析工具 - url-query.js * 支持 uni-app 全平台(H5、微信小程序、支付宝小程序、 * 百度小程序、抖音小程序、App、快应用) * ============================================================ * * 【使用方式】 * import UrlQuery from '@/scratch/url-query.js'; * * // 获取单个参数(自动类型转换) * const id = UrlQuery.get('id'); // "123" → 123 (number) * const name = UrlQuery.get('name'); // "张三" (string) * const flag = UrlQuery.get('flag'); // "true" → true (boolean) * const rid = UrlQuery.get('rid', '-1'); // 不存在返回默认值 '-1' * * // 获取原始字符串(不自动类型转换) * const raw = UrlQuery.get('id', '', false); // "123" (string) * * // 获取所有参数 * const params = UrlQuery.getAll(); // { id: 123, name: "张三" } * * // 判断参数是否存在 * const hasId = UrlQuery.has('id'); // true / false * * // 解析指定 URL 字符串 * const parsed = UrlQuery.parseUrl('https://example.com?id=1&name=a'); * * // 解析查询字符串 * const obj = UrlQuery.parse('?a=1&b=2&b=3'); // { a: "1", b: ["2", "3"] } * * 【特性】 * 1. 多平台自适应(H5 通过 location 获取,小程序/App 通过页面栈获取) * 2. 自动类型转换(数字、布尔值、null) * 3. 安全解码(异常编码不会报错) * 4. 支持数组参数(同名参数自动转为数组) * 5. 支持 history 路由和 hash 路由模式 * 6. 容错处理(空值、undefined、异常 URL 等) * ============================================================ */ const UrlQuery = { /** * 解析 URL 查询字符串为对象 * @param {string} queryString - 查询字符串,如 "?a=1&b=2" 或 "a=1&b=2" * @returns {Object} 解析后的参数对象,同名参数自动转为数组 */ parse(queryString) { const result = {}; if (typeof queryString !== 'string') { return result; } // 统一去掉开头的 ? 和 #,并去除首尾空白 queryString = queryString.replace(/^[?#]/, '').trim(); if (!queryString) { return result; } const pairs = queryString.split('&'); for (let i = 0; i < pairs.length; i++) { const pair = pairs[i]; if (!pair) continue; const eqIndex = pair.indexOf('='); let key, value; if (eqIndex === -1) { // 无等号的情况,如 "?foo&bar" key = pair; value = ''; } else { key = pair.substring(0, eqIndex); value = pair.substring(eqIndex + 1); } // 安全解码(异常编码不抛错) try { key = decodeURIComponent(key); } catch (e) { /* 保留原始值 */ } try { value = decodeURIComponent(value); } catch (e) { /* 保留原始值 */ } // 处理数组参数(如 id=1&id=2 → { id: ['1', '2'] }) if (Object.prototype.hasOwnProperty.call(result, key)) { if (!Array.isArray(result[key])) { result[key] = [result[key]]; } result[key].push(value); } else { result[key] = value; } } return result; }, /** * 获取当前页面的 URL 参数对象 * 多平台自适应:H5 从 window.location 获取,其他平台从页面栈获取 * @returns {Object} 当前页面所有参数 */ getCurrent() { let params = {}; // #ifdef H5 // H5 环境下优先从 window.location 解析(兼容 history 和 hash 路由) try { if (typeof window !== 'undefined' && window.location) { const searchParams = this.parse(window.location.search); const hashParts = window.location.hash.split('?'); const hashParams = hashParts.length > 1 ? this.parse(hashParts[1]) : {}; params = { ...searchParams, ...hashParams }; } } catch (e) { console.error('[UrlQuery] H5 解析 location 失败:', e); } // #endif // 尝试从页面栈获取(uni-app 所有平台通用) try { const pages = getCurrentPages(); if (pages && pages.length > 0) { const currentPage = pages[pages.length - 1]; if (currentPage && currentPage.options && typeof currentPage.options === 'object') { // H5 下合并 location 与 options,其他平台以 options 为准 // #ifdef H5 params = { ...params, ...currentPage.options }; // #endif // #ifndef H5 params = { ...currentPage.options }; // #endif } } } catch (e) { // getCurrentPages 在部分环境下可能不可用,静默处理 } return params; }, /** * 解析指定 URL 字符串中的参数 * @param {string} url - 完整 URL,如 "https://example.com/path?id=1&name=a#hash?b=2" * @returns {Object} 解析后的参数对象(search 参数优先,hash 参数补充) */ parseUrl(url) { if (typeof url !== 'string' || !url.trim()) { return {}; } url = url.trim(); // 优先使用浏览器 URL API(H5 / 现代环境) try { if (typeof URL !== 'undefined') { const urlObj = new URL(url); return this.parse(urlObj.search); } } catch (e) { // URL 构造函数不支持或解析失败,降级到手动提取 } // 降级处理:手动提取 search 和 hash 中的参数 let search = ''; let hashQuery = ''; const searchIndex = url.indexOf('?'); const hashIndex = url.indexOf('#'); if (searchIndex !== -1) { const endIndex = (hashIndex !== -1 && hashIndex > searchIndex) ? hashIndex : url.length; search = url.substring(searchIndex, endIndex); } if (hashIndex !== -1) { const hashPart = url.substring(hashIndex + 1); const hashQIndex = hashPart.indexOf('?'); if (hashQIndex !== -1) { hashQuery = hashPart.substring(hashQIndex); } } const searchParams = this.parse(search); const hashParams = this.parse(hashQuery); return { ...searchParams, ...hashParams }; }, /** * 获取单个参数值 * @param {string} key - 参数名(必填) * @param {*} defaultValue - 参数不存在时返回的默认值(默认空字符串) * @param {boolean} autoType - 是否自动类型转换(默认 true) * true : "123"→123, "true"→true, "false"→false, "null"→null * false : 始终返回原始字符串 * @returns {*} 参数值 */ get(key, defaultValue = '', autoType = true) { if (typeof key !== 'string' || !key) { return defaultValue; } const params = this.getCurrent(); if (!Object.prototype.hasOwnProperty.call(params, key)) { return defaultValue; } let value = params[key]; // 值为 undefined / null 时返回默认值 if (value === undefined || value === null) { return defaultValue; } // 不自动类型转换时直接返回字符串形式 if (!autoType) { return String(value); } // 已经是非字符串类型(如数字、布尔)直接返回 if (typeof value !== 'string') { return value; } // 空字符串处理 if (value === '') { return defaultValue !== '' ? defaultValue : ''; } // 布尔值转换 if (value === 'true') return true; if (value === 'false') return false; // null / undefined 字符串转换 if (value === 'null') return null; if (value === 'undefined') return undefined; // 整数转换(严格匹配,避免 "0123" 被转成 123 后丢失前导零) if (/^-?(0|[1-9]\d*)$/.test(value)) { const intVal = parseInt(value, 10); if (!isNaN(intVal) && String(intVal) === value) { return intVal; } } // 浮点数转换 if (/^-?(0|[1-9]\d*)\.\d+$/.test(value)) { const floatVal = parseFloat(value); if (!isNaN(floatVal) && String(floatVal) === value) { return floatVal; } } return value; }, /** * 获取所有参数(原始字符串形式,不做类型转换) * @returns {Object} 所有参数的字符串键值对 */ getAll() { return this.getCurrent(); }, /** * 获取所有参数并进行自动类型转换 * @returns {Object} 所有参数(数字、布尔值已转换) */ getAllTyped() { const params = this.getCurrent(); const result = {}; for (const key in params) { if (Object.prototype.hasOwnProperty.call(params, key)) { result[key] = this.get(key, '', true); } } return result; }, /** * 判断参数是否存在于当前 URL 中 * @param {string} key - 参数名 * @returns {boolean} */ has(key) { if (typeof key !== 'string' || !key) { return false; } return Object.prototype.hasOwnProperty.call(this.getCurrent(), key); }, /** * 获取数组类型的参数值 * 若参数为单个值则包装为数组返回,不存在返回默认值 * @param {string} key - 参数名 * @param {Array} defaultValue - 默认值(默认空数组) * @returns {Array} 数组形式的参数值 */ getArray(key, defaultValue = []) { if (typeof key !== 'string' || !key) { return defaultValue; } const params = this.getCurrent(); if (!Object.prototype.hasOwnProperty.call(params, key)) { return defaultValue; } const value = params[key]; if (Array.isArray(value)) { return value; } if (value === undefined || value === null) { return defaultValue; } return [value]; } }; // 兼容 CommonJS / ES Module / 全局变量 // #ifdef VUE3 export default UrlQuery; // #endif // #ifndef VUE3 if (typeof module !== 'undefined' && module.exports) { module.exports = UrlQuery; } else if (typeof define === 'function' && define.amd) { define(function () { return UrlQuery; }); } else { try { window.UrlQuery = UrlQuery; } catch (e) { // 非浏览器环境静默处理 } } // #endif