@anjianshi/utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +6 -0
- package/README.md +1 -0
- package/build-cleanup.cjs +10 -0
- package/env-browser/device.d.ts +24 -0
- package/env-browser/device.js +49 -0
- package/env-browser/global.d.ts +11 -0
- package/env-browser/global.js +14 -0
- package/env-browser/load-script.d.ts +5 -0
- package/env-browser/load-script.js +13 -0
- package/env-browser/logging.d.ts +18 -0
- package/env-browser/logging.js +49 -0
- package/env-browser/manage-vconsole.d.ts +16 -0
- package/env-browser/manage-vconsole.js +38 -0
- package/env-node/index.d.ts +17 -0
- package/env-node/index.js +45 -0
- package/env-node/logging.d.ts +64 -0
- package/env-node/logging.js +159 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/init-dayjs.d.ts +2 -0
- package/init-dayjs.js +7 -0
- package/lang/async.d.ts +19 -0
- package/lang/async.js +34 -0
- package/lang/index.d.ts +5 -0
- package/lang/index.js +5 -0
- package/lang/object.d.ts +5 -0
- package/lang/object.js +31 -0
- package/lang/string.d.ts +17 -0
- package/lang/string.js +59 -0
- package/lang/time.d.ts +10 -0
- package/lang/time.js +18 -0
- package/lang/types.d.ts +98 -0
- package/lang/types.js +41 -0
- package/logging/adapt.d.ts +5 -0
- package/logging/adapt.js +38 -0
- package/logging/formatters.d.ts +10 -0
- package/logging/formatters.js +22 -0
- package/logging/index.d.ts +43 -0
- package/logging/index.js +72 -0
- package/package.json +43 -0
- package/src/.eslintrc.cjs +8 -0
- package/src/env-browser/.eslintrc.cjs +6 -0
- package/src/env-browser/device.ts +60 -0
- package/src/env-browser/global.ts +20 -0
- package/src/env-browser/load-script.ts +13 -0
- package/src/env-browser/logging.ts +58 -0
- package/src/env-browser/manage-vconsole.ts +54 -0
- package/src/env-node/.eslintrc.cjs +4 -0
- package/src/env-node/index.ts +46 -0
- package/src/env-node/logging.ts +202 -0
- package/src/index.ts +3 -0
- package/src/init-dayjs.ts +8 -0
- package/src/lang/async.ts +47 -0
- package/src/lang/index.ts +5 -0
- package/src/lang/object.ts +28 -0
- package/src/lang/string.ts +62 -0
- package/src/lang/time.ts +19 -0
- package/src/lang/types.ts +138 -0
- package/src/logging/adapt.ts +40 -0
- package/src/logging/formatters.ts +23 -0
- package/src/logging/index.ts +87 -0
- package/src/url.ts +153 -0
- package/url.d.ts +28 -0
- package/url.js +116 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Debug } from 'debug'
|
|
2
|
+
import { getLogger } from './index.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 适配 debug package
|
|
6
|
+
*/
|
|
7
|
+
export function adaptDebugLib(debugLib: Debug, enable = '') {
|
|
8
|
+
// 不在 localStorage 里记录 debugLib enable 状态,
|
|
9
|
+
// 以解决 web worker 里读不到 localStorage 而无法启用 debugLib 日志的问题
|
|
10
|
+
const emulate = {
|
|
11
|
+
storage: {
|
|
12
|
+
data: {} as Record<string, string>,
|
|
13
|
+
getItem(name: string) {
|
|
14
|
+
return emulate.storage.data[name]
|
|
15
|
+
},
|
|
16
|
+
setItem(name: string, value: string) {
|
|
17
|
+
emulate.storage.data[name] = value
|
|
18
|
+
},
|
|
19
|
+
removeItem(name: string) {
|
|
20
|
+
delete emulate.storage.data[name]
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
save(namespaces: string) {
|
|
24
|
+
if (namespaces) emulate.storage.setItem('debug', namespaces)
|
|
25
|
+
else emulate.storage.removeItem('debug')
|
|
26
|
+
},
|
|
27
|
+
load() {
|
|
28
|
+
return emulate.storage.getItem('debug')
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
Object.assign(debugLib, emulate)
|
|
32
|
+
|
|
33
|
+
// 将 debugLib 日志转发给 logger
|
|
34
|
+
const logger = getLogger('3rd-library')
|
|
35
|
+
debugLib.log = logger.debug.bind(logger)
|
|
36
|
+
|
|
37
|
+
if (enable) {
|
|
38
|
+
debugLib.enable(enable)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志常用数据的格式化函数
|
|
3
|
+
*/
|
|
4
|
+
import { type LogInfo, LogLevel } from './index.js'
|
|
5
|
+
|
|
6
|
+
const formatters = {
|
|
7
|
+
time(info: LogInfo) {
|
|
8
|
+
return info.time.format('HH:mm:ss.SSS')
|
|
9
|
+
},
|
|
10
|
+
datetime(info: LogInfo) {
|
|
11
|
+
return info.time.format('YY-MM-DD HH:mm:ss.SSS')
|
|
12
|
+
},
|
|
13
|
+
level(info: LogInfo) {
|
|
14
|
+
const map = {
|
|
15
|
+
[LogLevel.Debug]: 'debug',
|
|
16
|
+
[LogLevel.Info]: 'info',
|
|
17
|
+
[LogLevel.Warning]: 'warn',
|
|
18
|
+
[LogLevel.Error]: 'error',
|
|
19
|
+
}
|
|
20
|
+
return map[info.level]
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
export default formatters
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export { default as formatters } from './formatters.js'
|
|
2
|
+
export * from './adapt.js'
|
|
3
|
+
import dayjs, { type Dayjs } from 'dayjs'
|
|
4
|
+
import { initDayJs } from '../init-dayjs.js'
|
|
5
|
+
|
|
6
|
+
// 引入 logging 库会自动初始化 dayjs
|
|
7
|
+
initDayJs()
|
|
8
|
+
|
|
9
|
+
export enum LogLevel {
|
|
10
|
+
Debug = 1,
|
|
11
|
+
Info = 2,
|
|
12
|
+
Warning = 3,
|
|
13
|
+
Error = 4,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LogInfo {
|
|
17
|
+
logger: string // logger name;有多级 logger 的情况下,这是最初的 logger 名称
|
|
18
|
+
level: LogLevel
|
|
19
|
+
time: Dayjs
|
|
20
|
+
args: unknown[] // log content
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class LogHandler {
|
|
24
|
+
log(info: LogInfo) {} // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class Logger {
|
|
28
|
+
level = LogLevel.Info
|
|
29
|
+
handlers = new Set<LogHandler>()
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
public name: string = '',
|
|
33
|
+
public base: Logger | null = null, // 指定上级 logger,当前 logger 记录的日志也会传递给上级
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
setLevel(level: LogLevel) {
|
|
37
|
+
this.level = level
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
addHandler(handler: LogHandler) {
|
|
41
|
+
this.handlers.add(handler)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 创建一个以当前 logger 为 base 的 child logger
|
|
46
|
+
*/
|
|
47
|
+
getChild(name: string) {
|
|
48
|
+
const fullname = this.name ? `${this.name}/${name}` : name
|
|
49
|
+
type Constructor = new (...args: ConstructorParameters<typeof Logger>) => Logger
|
|
50
|
+
// 这里加上 `as this` 才能让 TypeScript 判定,对继承了 Logger 的类调用此方法时,返回的是那个类而不是原始的 Logger 类的实例
|
|
51
|
+
return new (this.constructor as Constructor)(fullname, this) as this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log(level: LogLevel, args: unknown[]) {
|
|
55
|
+
this.logByInfo({ logger: this.name, level, time: dayjs(), args })
|
|
56
|
+
}
|
|
57
|
+
protected logByInfo(info: LogInfo) {
|
|
58
|
+
if (this.base) this.base.logByInfo(info)
|
|
59
|
+
if (this.level > info.level) return
|
|
60
|
+
for (const handler of this.handlers) {
|
|
61
|
+
handler.log(info)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
debug(...args: any[]) {
|
|
66
|
+
this.log(LogLevel.Debug, args)
|
|
67
|
+
}
|
|
68
|
+
info(...args: any[]) {
|
|
69
|
+
this.log(LogLevel.Info, args)
|
|
70
|
+
}
|
|
71
|
+
warn(...args: any[]) {
|
|
72
|
+
this.log(LogLevel.Warning, args)
|
|
73
|
+
}
|
|
74
|
+
error(...args: any[]) {
|
|
75
|
+
this.log(LogLevel.Error, args)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 提供一套默认配置好的 logger
|
|
81
|
+
*/
|
|
82
|
+
const defaultLogger = new Logger()
|
|
83
|
+
export { defaultLogger as logger }
|
|
84
|
+
|
|
85
|
+
export function getLogger(name: string) {
|
|
86
|
+
return defaultLogger.getChild(name)
|
|
87
|
+
}
|
package/src/url.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL 工具函数
|
|
3
|
+
* 部分灵感来自:https://www.npmjs.com/package/qs
|
|
4
|
+
*
|
|
5
|
+
* [名词定义]
|
|
6
|
+
* query: 指 a=1&b=2 格式的“查询字符串”或此类字符串的解析结果。
|
|
7
|
+
* search: URL 中的 search 部分,与 location.search 一致,空字符串或以 '?' 开头。
|
|
8
|
+
* hash: URL 中的 hash 部分,与 location.hash 一直,空字符串或以 '#' 开头。
|
|
9
|
+
*/
|
|
10
|
+
import isPlainObject from 'lodash/isPlainObject.js'
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
将 URL 里的 query 内容解析成对象
|
|
14
|
+
|
|
15
|
+
array:
|
|
16
|
+
- 若开启,支持解析 a[]=1&a[]=2 格式的参数,会解析成一个数组 { a: ['1', '2'] }
|
|
17
|
+
- 否则把 `a[]` 整体当做一个参数名 { 'a[]': '2' }
|
|
18
|
+
|
|
19
|
+
strict:
|
|
20
|
+
是否开启“严格模式”(默认不开启)。在非严格模式下,会做很多兼容处理:
|
|
21
|
+
1. 支持直接传入 query string(a=1&b=2);严格模式下,则需在开头补充一个 ?(?a=1&b=2)
|
|
22
|
+
2. hash 里的内容也会被解析,以兼容拼接错误的 URL(把 query 拼到了 hash 后面)。
|
|
23
|
+
3. 出现多个 ? 符号时,会把 ? 也当做 & 分隔符(index.html?a=1&b=2?c=3)
|
|
24
|
+
|
|
25
|
+
小技巧:
|
|
26
|
+
如果明确只想解析 hash 或 search 里的内容,可以传入 location.search / hash 而不是传入整个 location.href
|
|
27
|
+
|
|
28
|
+
此函数不会对 query 值进行 decode,需自定处理
|
|
29
|
+
*/
|
|
30
|
+
function parseQuery(url: string, options?: { array?: false, strict?: boolean }): Record<string, string> // prettier-ignore
|
|
31
|
+
function parseQuery(url: string, options: { array: true, strict?: boolean }): Record<string, string | string[]> // prettier-ignore
|
|
32
|
+
function parseQuery(
|
|
33
|
+
url: string,
|
|
34
|
+
options?: { array?: boolean; strict?: boolean },
|
|
35
|
+
): Record<string, string | string[]> {
|
|
36
|
+
if (!url) return {}
|
|
37
|
+
const { array = false, strict = false } = options ?? {}
|
|
38
|
+
|
|
39
|
+
const queryString = strict
|
|
40
|
+
? (/^[^?#]*\?(.+?)(\?|#|$)/.exec(url) ?? ['', ''])[1] // 排除 hash、重复的 ? 符号
|
|
41
|
+
: url
|
|
42
|
+
|
|
43
|
+
const query: { [name: string]: string | string[] } = {}
|
|
44
|
+
const reg = /([^#?&]*)=([^#?&]*)/g
|
|
45
|
+
let re = reg.exec(queryString)
|
|
46
|
+
while (re) {
|
|
47
|
+
const [name, value] = [re[1]!, re[2]!]
|
|
48
|
+
if (name.endsWith('[]') && array) {
|
|
49
|
+
const realName = name.slice(0, -2)
|
|
50
|
+
if (Array.isArray(query[realName])) (query[realName] as string[]).push(value)
|
|
51
|
+
else query[realName] = [value]
|
|
52
|
+
} else {
|
|
53
|
+
query[name] = value
|
|
54
|
+
}
|
|
55
|
+
re = reg.exec(queryString)
|
|
56
|
+
}
|
|
57
|
+
return query
|
|
58
|
+
}
|
|
59
|
+
export { parseQuery }
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
取 query 中指定参数的值
|
|
63
|
+
- 参数存在,返回参数值(可能是空字符串);不存在返回 null
|
|
64
|
+
- 解析 query 固定基于 parseQuery() 的 { array: false, strict: false } 规则
|
|
65
|
+
- 和 parseQuery() 一样,url 可以根据需要传 location.href/search/hash
|
|
66
|
+
*/
|
|
67
|
+
export function getQueryParam(name: string, url: string): string | null {
|
|
68
|
+
const query = parseQuery(url)
|
|
69
|
+
return typeof query[name] === 'string' ? query[name]! : null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
把对象合并成 query string。
|
|
74
|
+
支持字符串、数值、布尔值(不建议,考虑用 0 和 1 代替),数组会转换成 name[]=value 的格式
|
|
75
|
+
*/
|
|
76
|
+
type StringifyVal = string | number | boolean
|
|
77
|
+
export function stringifyQuery(obj: { [key: string]: StringifyVal | StringifyVal[] | undefined }) {
|
|
78
|
+
if (!isPlainObject(obj)) return ''
|
|
79
|
+
return (
|
|
80
|
+
Object.entries(obj)
|
|
81
|
+
// 过滤值为 undefined 的项目,使其完全不出现在最终的 query 中
|
|
82
|
+
.filter((entry): entry is [string, StringifyVal | StringifyVal[]] => entry[1] !== undefined)
|
|
83
|
+
.map(([name, value]) => stringifyQueryItem(name, value))
|
|
84
|
+
.join('&')
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
function stringifyQueryItem(name: string, value: StringifyVal | StringifyVal[]): string {
|
|
88
|
+
if (Array.isArray(value))
|
|
89
|
+
return value.map(subValue => stringifyQueryItem(`${name}[]`, subValue)).join('&')
|
|
90
|
+
return `${name}=${value}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 拆分 URL 的各个部分(search 不带 '?',hash 不带 '#')
|
|
95
|
+
*/
|
|
96
|
+
export function splitUrl(url: string): { base: string; search: string; hash: string } {
|
|
97
|
+
let hashIndex = url.indexOf('#')
|
|
98
|
+
if (hashIndex === -1) hashIndex = url.length
|
|
99
|
+
|
|
100
|
+
let searchIndex = url.slice(0, hashIndex).indexOf('?')
|
|
101
|
+
if (searchIndex === -1) searchIndex = hashIndex
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
base: url.slice(0, searchIndex),
|
|
105
|
+
search: url.slice(searchIndex + 1, hashIndex),
|
|
106
|
+
hash: url.slice(hashIndex + 1),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
把指定 search 和 hash 内容合并到 url 上
|
|
112
|
+
|
|
113
|
+
search object 与现有 search 合并,替换同名项(值为数组的,用新数组代替老的,不会合并数组)
|
|
114
|
+
hash string 不带 # 号;若 url 已有 hash,会用此值代替
|
|
115
|
+
*/
|
|
116
|
+
export function combineUrl(
|
|
117
|
+
origUrl: string,
|
|
118
|
+
search?: Record<string, string | string[]>,
|
|
119
|
+
hash?: string,
|
|
120
|
+
) {
|
|
121
|
+
search = search ?? {}
|
|
122
|
+
hash = hash ?? ''
|
|
123
|
+
|
|
124
|
+
// 拆分原 url 的 search、hash
|
|
125
|
+
const { base, search: origSearch, hash: origHash } = splitUrl(origUrl)
|
|
126
|
+
|
|
127
|
+
// 拼接新 URL
|
|
128
|
+
let newUrl = base
|
|
129
|
+
const newSearch = stringifyQuery({ ...parseQuery(origSearch), ...search })
|
|
130
|
+
const newHash = hash || origHash
|
|
131
|
+
if (newSearch) newUrl += `?${newSearch}`
|
|
132
|
+
if (newHash) newUrl += `#${newHash}`
|
|
133
|
+
return newUrl
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/*
|
|
137
|
+
decodeURIComponent() 对于不规范编码的字符串可能会报错(例如字符串里出现了“%”)
|
|
138
|
+
用此函数代替可避免此问题
|
|
139
|
+
*/
|
|
140
|
+
export function safeDecode(content: string) {
|
|
141
|
+
try {
|
|
142
|
+
return decodeURIComponent(content)
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return content
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 将 URL 中的 http:// 协议改成 https://
|
|
150
|
+
*/
|
|
151
|
+
export function ensureHttps(url: string | undefined) {
|
|
152
|
+
return url?.replace(/http:\/\//g, 'https://')
|
|
153
|
+
}
|
package/url.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare function parseQuery(url: string, options?: {
|
|
2
|
+
array?: false;
|
|
3
|
+
strict?: boolean;
|
|
4
|
+
}): Record<string, string>;
|
|
5
|
+
declare function parseQuery(url: string, options: {
|
|
6
|
+
array: true;
|
|
7
|
+
strict?: boolean;
|
|
8
|
+
}): Record<string, string | string[]>;
|
|
9
|
+
export { parseQuery };
|
|
10
|
+
export declare function getQueryParam(name: string, url: string): string | null;
|
|
11
|
+
type StringifyVal = string | number | boolean;
|
|
12
|
+
export declare function stringifyQuery(obj: {
|
|
13
|
+
[key: string]: StringifyVal | StringifyVal[] | undefined;
|
|
14
|
+
}): string;
|
|
15
|
+
/**
|
|
16
|
+
* 拆分 URL 的各个部分(search 不带 '?',hash 不带 '#')
|
|
17
|
+
*/
|
|
18
|
+
export declare function splitUrl(url: string): {
|
|
19
|
+
base: string;
|
|
20
|
+
search: string;
|
|
21
|
+
hash: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function combineUrl(origUrl: string, search?: Record<string, string | string[]>, hash?: string): string;
|
|
24
|
+
export declare function safeDecode(content: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* 将 URL 中的 http:// 协议改成 https://
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureHttps(url: string | undefined): string | undefined;
|
package/url.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL 工具函数
|
|
3
|
+
* 部分灵感来自:https://www.npmjs.com/package/qs
|
|
4
|
+
*
|
|
5
|
+
* [名词定义]
|
|
6
|
+
* query: 指 a=1&b=2 格式的“查询字符串”或此类字符串的解析结果。
|
|
7
|
+
* search: URL 中的 search 部分,与 location.search 一致,空字符串或以 '?' 开头。
|
|
8
|
+
* hash: URL 中的 hash 部分,与 location.hash 一直,空字符串或以 '#' 开头。
|
|
9
|
+
*/
|
|
10
|
+
import isPlainObject from 'lodash/isPlainObject.js';
|
|
11
|
+
function parseQuery(url, options) {
|
|
12
|
+
if (!url)
|
|
13
|
+
return {};
|
|
14
|
+
const { array = false, strict = false } = options ?? {};
|
|
15
|
+
const queryString = strict
|
|
16
|
+
? (/^[^?#]*\?(.+?)(\?|#|$)/.exec(url) ?? ['', ''])[1] // 排除 hash、重复的 ? 符号
|
|
17
|
+
: url;
|
|
18
|
+
const query = {};
|
|
19
|
+
const reg = /([^#?&]*)=([^#?&]*)/g;
|
|
20
|
+
let re = reg.exec(queryString);
|
|
21
|
+
while (re) {
|
|
22
|
+
const [name, value] = [re[1], re[2]];
|
|
23
|
+
if (name.endsWith('[]') && array) {
|
|
24
|
+
const realName = name.slice(0, -2);
|
|
25
|
+
if (Array.isArray(query[realName]))
|
|
26
|
+
query[realName].push(value);
|
|
27
|
+
else
|
|
28
|
+
query[realName] = [value];
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
query[name] = value;
|
|
32
|
+
}
|
|
33
|
+
re = reg.exec(queryString);
|
|
34
|
+
}
|
|
35
|
+
return query;
|
|
36
|
+
}
|
|
37
|
+
export { parseQuery };
|
|
38
|
+
/*
|
|
39
|
+
取 query 中指定参数的值
|
|
40
|
+
- 参数存在,返回参数值(可能是空字符串);不存在返回 null
|
|
41
|
+
- 解析 query 固定基于 parseQuery() 的 { array: false, strict: false } 规则
|
|
42
|
+
- 和 parseQuery() 一样,url 可以根据需要传 location.href/search/hash
|
|
43
|
+
*/
|
|
44
|
+
export function getQueryParam(name, url) {
|
|
45
|
+
const query = parseQuery(url);
|
|
46
|
+
return typeof query[name] === 'string' ? query[name] : null;
|
|
47
|
+
}
|
|
48
|
+
export function stringifyQuery(obj) {
|
|
49
|
+
if (!isPlainObject(obj))
|
|
50
|
+
return '';
|
|
51
|
+
return (Object.entries(obj)
|
|
52
|
+
// 过滤值为 undefined 的项目,使其完全不出现在最终的 query 中
|
|
53
|
+
.filter((entry) => entry[1] !== undefined)
|
|
54
|
+
.map(([name, value]) => stringifyQueryItem(name, value))
|
|
55
|
+
.join('&'));
|
|
56
|
+
}
|
|
57
|
+
function stringifyQueryItem(name, value) {
|
|
58
|
+
if (Array.isArray(value))
|
|
59
|
+
return value.map(subValue => stringifyQueryItem(`${name}[]`, subValue)).join('&');
|
|
60
|
+
return `${name}=${value}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 拆分 URL 的各个部分(search 不带 '?',hash 不带 '#')
|
|
64
|
+
*/
|
|
65
|
+
export function splitUrl(url) {
|
|
66
|
+
let hashIndex = url.indexOf('#');
|
|
67
|
+
if (hashIndex === -1)
|
|
68
|
+
hashIndex = url.length;
|
|
69
|
+
let searchIndex = url.slice(0, hashIndex).indexOf('?');
|
|
70
|
+
if (searchIndex === -1)
|
|
71
|
+
searchIndex = hashIndex;
|
|
72
|
+
return {
|
|
73
|
+
base: url.slice(0, searchIndex),
|
|
74
|
+
search: url.slice(searchIndex + 1, hashIndex),
|
|
75
|
+
hash: url.slice(hashIndex + 1),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/*
|
|
79
|
+
把指定 search 和 hash 内容合并到 url 上
|
|
80
|
+
|
|
81
|
+
search object 与现有 search 合并,替换同名项(值为数组的,用新数组代替老的,不会合并数组)
|
|
82
|
+
hash string 不带 # 号;若 url 已有 hash,会用此值代替
|
|
83
|
+
*/
|
|
84
|
+
export function combineUrl(origUrl, search, hash) {
|
|
85
|
+
search = search ?? {};
|
|
86
|
+
hash = hash ?? '';
|
|
87
|
+
// 拆分原 url 的 search、hash
|
|
88
|
+
const { base, search: origSearch, hash: origHash } = splitUrl(origUrl);
|
|
89
|
+
// 拼接新 URL
|
|
90
|
+
let newUrl = base;
|
|
91
|
+
const newSearch = stringifyQuery({ ...parseQuery(origSearch), ...search });
|
|
92
|
+
const newHash = hash || origHash;
|
|
93
|
+
if (newSearch)
|
|
94
|
+
newUrl += `?${newSearch}`;
|
|
95
|
+
if (newHash)
|
|
96
|
+
newUrl += `#${newHash}`;
|
|
97
|
+
return newUrl;
|
|
98
|
+
}
|
|
99
|
+
/*
|
|
100
|
+
decodeURIComponent() 对于不规范编码的字符串可能会报错(例如字符串里出现了“%”)
|
|
101
|
+
用此函数代替可避免此问题
|
|
102
|
+
*/
|
|
103
|
+
export function safeDecode(content) {
|
|
104
|
+
try {
|
|
105
|
+
return decodeURIComponent(content);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
return content;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 将 URL 中的 http:// 协议改成 https://
|
|
113
|
+
*/
|
|
114
|
+
export function ensureHttps(url) {
|
|
115
|
+
return url?.replace(/http:\/\//g, 'https://');
|
|
116
|
+
}
|