347 lines
9.4 KiB
JavaScript
347 lines
9.4 KiB
JavaScript
/**
|
||
* ============================================================
|
||
* 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
|