oeos-components 0.4.8 → 0.4.10
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/dist/base-fB92g3RO.js +1419 -0
- package/dist/day-BvhluvJ1.js +111 -0
- package/dist/format-C8GLxsTu.js +488 -0
- package/dist/index-wRPkJ8A4.js +10 -0
- package/dist/is-DCY_JULj.js +298 -0
- package/dist/oeos-components-es.js +5298 -5
- package/dist/oeos-components-umd.js +2583 -24
- package/dist/style.css +1 -1
- package/dist/test-BeRG3MRO.js +18 -0
- package/dist/ws-CdL4IiDX.js +252 -0
- package/package.json +1 -1
- package/dist/index-CAJ0GRWZ.js +0 -6790
- package/dist/index-lVUKuuOW.js +0 -1105
|
@@ -0,0 +1,1419 @@
|
|
|
1
|
+
const n = `import { unref, isRef, toRaw } from '@vue/reactivity'
|
|
2
|
+
import type { AppContext, VNode } from 'vue'
|
|
3
|
+
import type { Ref } from '@vue/reactivity'
|
|
4
|
+
import { consola } from 'consola'
|
|
5
|
+
import { cloneDeep } from 'es-toolkit' // 这里不要lodash-es的原因是, 体积太大, 超过500kb无法打包
|
|
6
|
+
import { formatTime } from './format'
|
|
7
|
+
import { ElMessage, ElMessageBox, type ElMessageBoxOptions, type MessageOptions } from 'element-plus'
|
|
8
|
+
|
|
9
|
+
type Func<Args extends any[] = any[], Return = any> = (...args: Args) => Return
|
|
10
|
+
type StorageValue = unknown
|
|
11
|
+
type StorageMap = Record<string, any>
|
|
12
|
+
type MaybeRef<T> = T | Ref<T>
|
|
13
|
+
type WidthStyleResult = { width: string }
|
|
14
|
+
type ValidateTriggerType = 'blur' | 'change'
|
|
15
|
+
type ValidateInput = ValidateRules | ValidatePrimitiveValue
|
|
16
|
+
type ValidateRuleResult = {
|
|
17
|
+
required?: boolean
|
|
18
|
+
message?: string
|
|
19
|
+
trigger?: ValidateTriggerType[]
|
|
20
|
+
validator?: (rule: any, value: any, callback: (error?: Error) => void) => void
|
|
21
|
+
min?: number
|
|
22
|
+
max?: number
|
|
23
|
+
}
|
|
24
|
+
type ValidatePrimitiveValue = string | number | boolean | null | undefined
|
|
25
|
+
type UuidOptionItem<T = any> = { label: string; value: T }
|
|
26
|
+
|
|
27
|
+
interface ToastOptions extends Partial<MessageOptions> {
|
|
28
|
+
/**
|
|
29
|
+
* 调用前是否先关闭全部消息提示。
|
|
30
|
+
*/
|
|
31
|
+
closeAll?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ClearStorageExcludeOptions {
|
|
35
|
+
/**
|
|
36
|
+
* 清空时需要保留的 key 列表。
|
|
37
|
+
*/
|
|
38
|
+
exclude: string[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type ClearStorageInput = string | string[] | ClearStorageExcludeOptions
|
|
42
|
+
|
|
43
|
+
interface ValidFormOptions {
|
|
44
|
+
/**
|
|
45
|
+
* 校验失败时的提示文案。
|
|
46
|
+
*/
|
|
47
|
+
message?: string
|
|
48
|
+
/**
|
|
49
|
+
* 是否在提示文案后追加字段名。
|
|
50
|
+
*/
|
|
51
|
+
detail?: boolean
|
|
52
|
+
/**
|
|
53
|
+
* 是否显示错误提示。
|
|
54
|
+
*/
|
|
55
|
+
showMessage?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface FormValidateTarget {
|
|
59
|
+
validate: (callback: (valid: boolean, status: StorageMap) => void) => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface UuidOptions {
|
|
63
|
+
/**
|
|
64
|
+
* \`email\` 模式下的邮箱后缀,默认 \`@qq.com\`。
|
|
65
|
+
*/
|
|
66
|
+
emailStr?: string
|
|
67
|
+
/**
|
|
68
|
+
* \`time\` 模式下追加的时间格式,默认 \`{y}-{m}-{d} {h}:{i}:{s}\`。
|
|
69
|
+
*/
|
|
70
|
+
timeStr?: string
|
|
71
|
+
/**
|
|
72
|
+
* 生成结果前缀。
|
|
73
|
+
*/
|
|
74
|
+
startStr?: string
|
|
75
|
+
/**
|
|
76
|
+
* 数组选项模式下的固定索引;不传时随机取值。
|
|
77
|
+
*/
|
|
78
|
+
optionsIndex?: number | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ValidateRules {
|
|
82
|
+
/**
|
|
83
|
+
* 校验失败提示文案。
|
|
84
|
+
*/
|
|
85
|
+
message?: string
|
|
86
|
+
/**
|
|
87
|
+
* 最小值或最小长度。
|
|
88
|
+
*/
|
|
89
|
+
min?: number
|
|
90
|
+
/**
|
|
91
|
+
* 最大值或最大长度。
|
|
92
|
+
*/
|
|
93
|
+
max?: number
|
|
94
|
+
/**
|
|
95
|
+
* 对比值,例如 \`same\` 校验时使用。
|
|
96
|
+
*/
|
|
97
|
+
value?: any
|
|
98
|
+
/**
|
|
99
|
+
* 自定义正则。
|
|
100
|
+
*/
|
|
101
|
+
reg?: RegExp
|
|
102
|
+
/**
|
|
103
|
+
* 是否必填,默认 \`true\`。
|
|
104
|
+
*/
|
|
105
|
+
required?: boolean
|
|
106
|
+
/**
|
|
107
|
+
* 触发时机,常用 \`['blur', 'change']\`。
|
|
108
|
+
*/
|
|
109
|
+
trigger?: ValidateTriggerType[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface CopyOptions extends ToastOptions {
|
|
113
|
+
/**
|
|
114
|
+
* 是否隐藏复制成功提示。
|
|
115
|
+
*/
|
|
116
|
+
hideToast?: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type WidthInput = string | number | Ref<string | number>
|
|
120
|
+
type ConfirmMessage = string | VNode | (() => VNode)
|
|
121
|
+
type ConfirmAppendTarget = string | HTMLElement | null
|
|
122
|
+
|
|
123
|
+
interface ConfirmOptions extends ElMessageBoxOptions {
|
|
124
|
+
/**
|
|
125
|
+
* 追加到的挂载节点。支持 css 选择器、DOM 节点或 \`null\`。
|
|
126
|
+
*/
|
|
127
|
+
appendTo?: ConfirmAppendTarget
|
|
128
|
+
/**
|
|
129
|
+
* 手动传入 appContext,处理多应用或嵌套弹窗场景。
|
|
130
|
+
*/
|
|
131
|
+
appContext?: AppContext | null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface TryCatchResult<T> {
|
|
135
|
+
/**
|
|
136
|
+
* 执行成功时返回的数据;失败时为 \`null\`。
|
|
137
|
+
*/
|
|
138
|
+
data: T | null
|
|
139
|
+
/**
|
|
140
|
+
* 执行失败时返回的异常;成功时为 \`null\`。
|
|
141
|
+
*/
|
|
142
|
+
error: any
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type TryCatchTask<T> = Promise<T> | (() => T | Promise<T>)
|
|
146
|
+
|
|
147
|
+
const DEFAULT_CONFIRM_BUTTON_CLASS = 'o-message-box__confirm-btn'
|
|
148
|
+
const DEFAULT_CANCEL_BUTTON_CLASS = 'o-message-box__cancel-btn'
|
|
149
|
+
|
|
150
|
+
function _getBrowserStorage(isSession = false): Storage | null {
|
|
151
|
+
if (typeof window === 'undefined') {
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
return isSession ? window.sessionStorage : window.localStorage
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _parseStorageValue<T = any>(value: string | null): T | string | number | null {
|
|
163
|
+
if (value == null) {
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(value)
|
|
169
|
+
if (typeof parsed !== 'number') {
|
|
170
|
+
return parsed
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {}
|
|
173
|
+
|
|
174
|
+
return value
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 显示消息提示。
|
|
179
|
+
*
|
|
180
|
+
* 支持三种常见写法:
|
|
181
|
+
* 1. \`$toast('保存成功')\`
|
|
182
|
+
* 2. \`$toast('保存失败', 'e')\`
|
|
183
|
+
* 3. \`$toast({ message: '自定义', type: 'warning' })\`
|
|
184
|
+
*
|
|
185
|
+
* @param message 提示内容,支持纯文本、VNode、渲染函数,或完整配置对象。
|
|
186
|
+
* @param type 提示类型,支持 \`success/info/error/warning\` 和简写 \`s/i/e/w\`,也支持直接传配置对象。
|
|
187
|
+
* @param otherParams 额外配置,例如 \`duration\`、\`customClass\`、\`closeAll\`。
|
|
188
|
+
* @returns 无返回值。
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* $toast('保存成功')
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* $toast('保存失败', 'e')
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* $toast({
|
|
198
|
+
* message: '自定义提示',
|
|
199
|
+
* type: 'warning',
|
|
200
|
+
* duration: 1000,
|
|
201
|
+
* closeAll: true,
|
|
202
|
+
* })
|
|
203
|
+
*/
|
|
204
|
+
type MessageType = 'success' | 'info' | 'error' | 'warning'
|
|
205
|
+
type ShortType = 's' | 'i' | 'e' | 'w'
|
|
206
|
+
type ToastType = MessageType | ShortType
|
|
207
|
+
|
|
208
|
+
export function $toast(
|
|
209
|
+
message: string | ToastOptions | VNode | (() => VNode),
|
|
210
|
+
type: ToastType | ToastOptions = 'success',
|
|
211
|
+
otherParams: ToastOptions = {},
|
|
212
|
+
): void {
|
|
213
|
+
const typeMap: Record<ShortType, MessageType> = {
|
|
214
|
+
s: 'success',
|
|
215
|
+
i: 'info',
|
|
216
|
+
e: 'error',
|
|
217
|
+
w: 'warning',
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isShortType(t: any): t is ShortType {
|
|
221
|
+
return ['s', 'i', 'e', 'w'].includes(t)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isToastOptions(obj: any): obj is ToastOptions {
|
|
225
|
+
return typeof obj === 'object' && obj !== null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Case 1: message is options object
|
|
229
|
+
if (isToastOptions(message)) {
|
|
230
|
+
if (message.closeAll) {
|
|
231
|
+
ElMessage.closeAll()
|
|
232
|
+
}
|
|
233
|
+
message.customClass = message.customClass === 'el' ? '' : 'o-antd-message'
|
|
234
|
+
ElMessage(message)
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Case 2: type is options object
|
|
239
|
+
if (isToastOptions(type)) {
|
|
240
|
+
if (type.closeAll) {
|
|
241
|
+
ElMessage.closeAll()
|
|
242
|
+
}
|
|
243
|
+
type.customClass = type.customClass === 'el' ? '' : 'o-antd-message'
|
|
244
|
+
ElMessage({
|
|
245
|
+
message,
|
|
246
|
+
type: 'success',
|
|
247
|
+
...type,
|
|
248
|
+
})
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Case 3: regular message with type and options
|
|
253
|
+
if (otherParams.closeAll) {
|
|
254
|
+
ElMessage.closeAll()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const resolvedType = isShortType(type) ? typeMap[type] : type
|
|
258
|
+
|
|
259
|
+
otherParams.customClass = otherParams.customClass === 'el' ? '' : 'o-antd-message'
|
|
260
|
+
ElMessage({
|
|
261
|
+
message,
|
|
262
|
+
type: resolvedType,
|
|
263
|
+
...otherParams,
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* \`$toast.success(...)\` 的快捷调用。
|
|
269
|
+
*/
|
|
270
|
+
$toast.success = (message: string | ToastOptions | VNode | (() => VNode), otherParams: ToastOptions = {}) =>
|
|
271
|
+
$toast(message, 'success', otherParams)
|
|
272
|
+
/**
|
|
273
|
+
* \`$toast.info(...)\` 的快捷调用。
|
|
274
|
+
*/
|
|
275
|
+
$toast.info = (message: string | ToastOptions | VNode | (() => VNode), otherParams: ToastOptions = {}) =>
|
|
276
|
+
$toast(message, 'info', otherParams)
|
|
277
|
+
/**
|
|
278
|
+
* \`$toast.error(...)\` 的快捷调用。
|
|
279
|
+
*/
|
|
280
|
+
$toast.error = (message: string | ToastOptions | VNode | (() => VNode), otherParams: ToastOptions = {}) =>
|
|
281
|
+
$toast(message, 'error', otherParams)
|
|
282
|
+
/**
|
|
283
|
+
* \`$toast.warning(...)\` 的快捷调用。
|
|
284
|
+
*/
|
|
285
|
+
$toast.warning = (message: string | ToastOptions | VNode | (() => VNode), otherParams: ToastOptions = {}) =>
|
|
286
|
+
$toast(message, 'warning', otherParams)
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 写入浏览器缓存。
|
|
290
|
+
*
|
|
291
|
+
* @param storageName 缓存 key。
|
|
292
|
+
* @param params 要保存的值;对象和数组会自动序列化。
|
|
293
|
+
* @param isSession 是否写入 \`sessionStorage\`;默认写入 \`localStorage\`。
|
|
294
|
+
* 在 SSR / Node 环境下会自动跳过,不会抛错。
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* setStorage('token', 'abc123')
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* setStorage('userInfo', { id: 1, name: 'andy' }, true)
|
|
301
|
+
*/
|
|
302
|
+
export function setStorage(storageName: string, params: StorageValue, isSession = false): void {
|
|
303
|
+
const storage = _getBrowserStorage(isSession)
|
|
304
|
+
if (!storage) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let handleParams
|
|
309
|
+
if (typeof params === 'number' || typeof params === 'string') {
|
|
310
|
+
handleParams = params
|
|
311
|
+
} else {
|
|
312
|
+
handleParams = JSON.stringify(params)
|
|
313
|
+
}
|
|
314
|
+
storage.setItem(storageName, handleParams)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 读取浏览器缓存。
|
|
319
|
+
*
|
|
320
|
+
* @param data 缓存 key。
|
|
321
|
+
* @param isSession 是否从 \`sessionStorage\` 读取;默认从 \`localStorage\` 读取。
|
|
322
|
+
* @returns 读取到的缓存值;不存在或在 SSR 环境下时返回 \`null\`。
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* const token = getStorage('token')
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* const userInfo = getStorage('userInfo', true)
|
|
329
|
+
*/
|
|
330
|
+
export function getStorage<T = any>(data: string, isSession = false): T | string | number | null {
|
|
331
|
+
const storage = _getBrowserStorage(isSession)
|
|
332
|
+
if (!storage) {
|
|
333
|
+
return null
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return _parseStorageValue<T>(storage.getItem(data))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 清空浏览器缓存。
|
|
341
|
+
*
|
|
342
|
+
* @param str 要清空的 key;不传时清空全部,传 \`exclude\` 时表示保留指定 key。
|
|
343
|
+
* @returns 无返回值。
|
|
344
|
+
* 在 SSR / Node 环境下会自动跳过。
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* clearStorage()
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* clearStorage('loginId')
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* clearStorage({ exclude: ['token', 'theme'] })
|
|
354
|
+
*/
|
|
355
|
+
export function clearStorage(str: ClearStorageInput = ''): void {
|
|
356
|
+
const sessionStorageRef = _getBrowserStorage(true)
|
|
357
|
+
const localStorageRef = _getBrowserStorage(false)
|
|
358
|
+
|
|
359
|
+
if (!sessionStorageRef && !localStorageRef) {
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (isEmpty(str)) {
|
|
364
|
+
sessionStorageRef?.clear()
|
|
365
|
+
localStorageRef?.clear()
|
|
366
|
+
}
|
|
367
|
+
if (!isEmpty(str) && getType(str) !== 'object') {
|
|
368
|
+
let strArr = Array.isArray(str) ? str : [str]
|
|
369
|
+
for (let i = 0; i < strArr.length; i++) {
|
|
370
|
+
sessionStorageRef?.removeItem(strArr[i])
|
|
371
|
+
localStorageRef?.removeItem(strArr[i])
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (_isObjectWithExclude(str)) {
|
|
375
|
+
if (!isEmpty(str.exclude) && getType(str) === 'object') {
|
|
376
|
+
let sessionStorageObj = {}
|
|
377
|
+
let localStorageObj = {}
|
|
378
|
+
for (const key in str.exclude) {
|
|
379
|
+
if (Object.prototype.hasOwnProperty.call(str.exclude, key)) {
|
|
380
|
+
const name = str.exclude[key]
|
|
381
|
+
if (getStorage(name)) {
|
|
382
|
+
localStorageObj[name] = getStorage(name)
|
|
383
|
+
}
|
|
384
|
+
if (getStorage(name, true)) {
|
|
385
|
+
sessionStorageObj[name] = getStorage(name, true)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
sessionStorageRef?.clear()
|
|
390
|
+
localStorageRef?.clear()
|
|
391
|
+
for (const key in sessionStorageObj) {
|
|
392
|
+
setStorage(key, sessionStorageObj[key], true)
|
|
393
|
+
}
|
|
394
|
+
for (const key in localStorageObj) {
|
|
395
|
+
setStorage(key, localStorageObj[key])
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// 自定义类型守卫函数
|
|
401
|
+
function _isObjectWithExclude(obj: ClearStorageInput): obj is ClearStorageExcludeOptions {
|
|
402
|
+
return typeof obj === 'object' && obj !== null && 'exclude' in obj && typeof obj.exclude === 'object'
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 将 Element Plus 表单校验封装为 Promise。
|
|
407
|
+
*
|
|
408
|
+
* @param ref 表单实例或表单实例的 \`ref\`。
|
|
409
|
+
* @param options 校验配置。
|
|
410
|
+
* @returns 校验通过时返回表单状态对象,失败时 reject 对应状态对象。
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* await validForm(formRef)
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* await validForm(formRef, { message: '请检查表单信息', detail: true })
|
|
417
|
+
*/
|
|
418
|
+
export function validForm(
|
|
419
|
+
ref: MaybeRef<FormValidateTarget>,
|
|
420
|
+
{ message = '表单校验错误, 请检查', detail = false, showMessage = true }: ValidFormOptions = {},
|
|
421
|
+
): Promise<StorageMap> {
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
unref(ref).validate((valid, status) => {
|
|
424
|
+
if (valid) {
|
|
425
|
+
resolve(status)
|
|
426
|
+
} else {
|
|
427
|
+
if (message && showMessage) {
|
|
428
|
+
let errorText = Object.keys(status)
|
|
429
|
+
let toastMessage = message
|
|
430
|
+
if (detail) {
|
|
431
|
+
toastMessage = message + errorText.join(',')
|
|
432
|
+
}
|
|
433
|
+
$toast(toastMessage, 'e')
|
|
434
|
+
}
|
|
435
|
+
reject(status)
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function isPlainObject(data: any): boolean {
|
|
442
|
+
if (Object.prototype.toString.call(data) !== '[object Object]') {
|
|
443
|
+
return false
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const proto = Object.getPrototypeOf(data)
|
|
447
|
+
return proto === Object.prototype || proto === null
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 判断值是否为空。
|
|
452
|
+
*
|
|
453
|
+
* 默认采用更安全的“结构空值”语义:
|
|
454
|
+
* \`undefined\`、\`null\`、空字符串、空数组、空对象、\`NaN\`、空 \`Map\` / \`Set\`、无效日期会返回 \`true\`。
|
|
455
|
+
*
|
|
456
|
+
* @param data 要判断的值。
|
|
457
|
+
* @param strict 是否使用严格模式。默认 \`true\`;传 \`false\` 时会沿用旧语义,把 \`0\` 和 \`false\` 也视为空值。
|
|
458
|
+
* @returns 是否为空。
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* isEmpty(' ')
|
|
462
|
+
* // => true
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* isEmpty(0)
|
|
466
|
+
* // => false
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* isEmpty(0, false)
|
|
470
|
+
* // => true
|
|
471
|
+
*/
|
|
472
|
+
export function isEmpty(data: any, strict = true): boolean {
|
|
473
|
+
if (isRef(data)) {
|
|
474
|
+
data = unref(data)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 处理 null/undefined
|
|
478
|
+
if (data == null) return true
|
|
479
|
+
// 如果是日期对象,检查它是否是有效的日期
|
|
480
|
+
if (data instanceof Date) {
|
|
481
|
+
return Number.isNaN(data.getTime())
|
|
482
|
+
}
|
|
483
|
+
// 处理基础类型
|
|
484
|
+
switch (typeof data) {
|
|
485
|
+
case 'string':
|
|
486
|
+
return data.trim().length === 0
|
|
487
|
+
case 'boolean':
|
|
488
|
+
return strict ? false : !data
|
|
489
|
+
case 'number':
|
|
490
|
+
return Number.isNaN(data) || (!strict && data === 0)
|
|
491
|
+
case 'symbol':
|
|
492
|
+
return false
|
|
493
|
+
case 'bigint':
|
|
494
|
+
return strict ? false : data === BigInt(0)
|
|
495
|
+
case 'function':
|
|
496
|
+
return false
|
|
497
|
+
}
|
|
498
|
+
// 处理集合类型
|
|
499
|
+
if (data instanceof Map || data instanceof Set) return data.size === 0
|
|
500
|
+
// 处理数组
|
|
501
|
+
if (Array.isArray(data)) {
|
|
502
|
+
return data.length === 0
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// 只把 plain object 当作普通对象判断,避免误伤 Promise / RegExp / Error 等实例
|
|
506
|
+
if (isPlainObject(data)) {
|
|
507
|
+
return Reflect.ownKeys(data).length === 0
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return false
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* 合并两个对象。
|
|
515
|
+
*
|
|
516
|
+
* 当同名字段同时有值时,以第二个对象为准;当其中一个字段为空时,保留有值的一侧。
|
|
517
|
+
*
|
|
518
|
+
* @param obj1 第一个对象。
|
|
519
|
+
* @param obj2 第二个对象。
|
|
520
|
+
* @returns 合并后的对象。
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* merge(
|
|
524
|
+
* { name: '', age: 18, city: 'beijing' },
|
|
525
|
+
* { name: 'andy', age: 20, city: '' },
|
|
526
|
+
* )
|
|
527
|
+
*/
|
|
528
|
+
export function merge<T extends StorageMap, U extends StorageMap>(obj1: T, obj2: U): T & U {
|
|
529
|
+
let merged = { ...obj1, ...obj2 }
|
|
530
|
+
for (let key in merged) {
|
|
531
|
+
if (!isEmpty(obj1[key]) && !isEmpty(obj2[key])) {
|
|
532
|
+
merged[key] = obj2[key]
|
|
533
|
+
} else if (isEmpty(obj1[key]) && !isEmpty(obj2[key])) {
|
|
534
|
+
merged[key] = obj2[key]
|
|
535
|
+
} else if (!isEmpty(obj1[key]) && isEmpty(obj2[key])) {
|
|
536
|
+
merged[key] = obj1[key]
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return merged as T & U
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* 深拷贝数据;传入数组时可按次数重复展开。
|
|
544
|
+
*
|
|
545
|
+
* @param data 要克隆的数据。
|
|
546
|
+
* @param times 当 \`data\` 是数组时的复制次数,默认 \`1\`。
|
|
547
|
+
* @returns 克隆后的数据。
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* clone({ name: 'andy', info: { id: 1 } })
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* clone([1, 2, { name: 'andy' }], 2)
|
|
554
|
+
* // => [1, 2, { name: 'andy' }, 1, 2, { name: 'andy' }]
|
|
555
|
+
*/
|
|
556
|
+
export function clone<T>(data: T[], times?: number): T[]
|
|
557
|
+
export function clone<T>(data: T, times?: number): T
|
|
558
|
+
export function clone<T>(data: T, times = 1): T {
|
|
559
|
+
if (isRef(data)) {
|
|
560
|
+
data = unref(data)
|
|
561
|
+
}
|
|
562
|
+
// Check if the data is not an array
|
|
563
|
+
if (getType(data) !== 'array') {
|
|
564
|
+
// If not an array, return a deep clone of the data
|
|
565
|
+
return cloneDeep(data)
|
|
566
|
+
}
|
|
567
|
+
const clonedData = cloneDeep(data)
|
|
568
|
+
const result: typeof clonedData = []
|
|
569
|
+
for (let i = 0; i < times; i++) {
|
|
570
|
+
result.push(...clonedData)
|
|
571
|
+
}
|
|
572
|
+
return result
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* 生成随机字符串,也支持手机号、邮箱、时间、数字、IP、端口等特殊模式。
|
|
577
|
+
*
|
|
578
|
+
* @param type 生成模式,支持空字符串、\`phone\`、\`email\`、\`time\`、\`number\`、\`ip\`、\`port\`,也支持传选项数组。
|
|
579
|
+
* @param length 随机字符串或数字的长度,默认 \`4\`。
|
|
580
|
+
* @param options 额外配置。
|
|
581
|
+
* @returns 生成结果。
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* uuid()
|
|
585
|
+
* // => 'aB3d'
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* uuid('phone')
|
|
589
|
+
* // => '13603312460'
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* uuid('time', 0, { startStr: 'andy', timeStr: '{h}:{i}:{s}' })
|
|
593
|
+
*/
|
|
594
|
+
export function uuid(
|
|
595
|
+
type: string | Array<UuidOptionItem> = '',
|
|
596
|
+
length = 4,
|
|
597
|
+
options: UuidOptions = {},
|
|
598
|
+
): string | number | any {
|
|
599
|
+
const { emailStr = '@qq.com', timeStr = '{y}-{m}-{d} {h}:{i}:{s}', startStr = '', optionsIndex = null } = options
|
|
600
|
+
|
|
601
|
+
// 辅助函数:判断是否为ref对象
|
|
602
|
+
function isRef(obj: any): obj is Ref<any> {
|
|
603
|
+
return obj && typeof obj === 'object' && obj._isRef === true
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// 辅助函数:获取ref的实际值
|
|
607
|
+
function unref<T>(ref: Ref<T> | T): T {
|
|
608
|
+
return isRef(ref) ? ref.value : ref
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// 辅助函数:生成随机数
|
|
612
|
+
function random(min: number, max: number): number {
|
|
613
|
+
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// 解包可能为ref的参数
|
|
617
|
+
type = unref(type)
|
|
618
|
+
|
|
619
|
+
// 处理数组类型参数(下拉框选项)
|
|
620
|
+
if (Array.isArray(type)) {
|
|
621
|
+
if (type.length === 0) return ''
|
|
622
|
+
|
|
623
|
+
const randIndex = optionsIndex ?? random(0, type.length - 1)
|
|
624
|
+
const selectedItem = type[randIndex]
|
|
625
|
+
|
|
626
|
+
// 如果数组项是对象且有value属性,则返回value
|
|
627
|
+
if (typeof selectedItem === 'object' && selectedItem !== null && 'value' in selectedItem) {
|
|
628
|
+
return selectedItem.value
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// 否则直接返回数组项
|
|
632
|
+
return selectedItem
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// 生成随机字符串
|
|
636
|
+
let randomChars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
|
|
637
|
+
let result = startStr
|
|
638
|
+
|
|
639
|
+
// 生成手机号
|
|
640
|
+
if (type === 'phone') {
|
|
641
|
+
const prefixes = ['130', '131', '132', '133', '135', '136', '137', '138', '170', '187', '189']
|
|
642
|
+
result = prefixes[random(0, prefixes.length - 1)]
|
|
643
|
+
|
|
644
|
+
for (let i = 0; i < 8; i++) {
|
|
645
|
+
result += Math.floor(Math.random() * 10)
|
|
646
|
+
}
|
|
647
|
+
return result
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// 生成邮箱
|
|
651
|
+
if (type === 'email') {
|
|
652
|
+
result = uuid(startStr, length) + emailStr
|
|
653
|
+
return result
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// 生成时间
|
|
657
|
+
if (type === 'time') {
|
|
658
|
+
return uuid(startStr, length, options) + ' ' + formatTime(new Date(), timeStr)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// 生成数字
|
|
662
|
+
if (type === 'number') {
|
|
663
|
+
const numChars = '123456789'
|
|
664
|
+
result = ''
|
|
665
|
+
|
|
666
|
+
for (let i = 0; i < length; i++) {
|
|
667
|
+
result += numChars[random(0, numChars.length - 1)]
|
|
668
|
+
}
|
|
669
|
+
return Number(result)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// 生成IP地址
|
|
673
|
+
if (type === 'ip') {
|
|
674
|
+
const randomNum = random(1, 99)
|
|
675
|
+
return \`10.0.11.\${randomNum}\`
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// 生成端口号
|
|
679
|
+
if (type === 'port') {
|
|
680
|
+
return random(1, 65535)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// 生成普通随机字符串
|
|
684
|
+
for (let i = 0; i < length; i++) {
|
|
685
|
+
result += randomChars[random(0, randomChars.length - 1)]
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return result
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* 获取值的原始类型名称,返回值统一为小写字符串。
|
|
693
|
+
*
|
|
694
|
+
* @param type 要判断的值。
|
|
695
|
+
* @returns 类型名称,例如 \`array\`、\`date\`、\`object\`、\`null\`。
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* getType(new RegExp())
|
|
699
|
+
* // => 'regexp'
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* getType([])
|
|
703
|
+
* // => 'array'
|
|
704
|
+
*/
|
|
705
|
+
export function getType(type: unknown): string {
|
|
706
|
+
if (typeof type === 'object') {
|
|
707
|
+
const objType = Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
|
|
708
|
+
return objType
|
|
709
|
+
} else {
|
|
710
|
+
return typeof type
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* 一个辅助函数,用于在代码中创建一个暂停(延迟)。
|
|
716
|
+
* 它返回一个 Promise,你可以在 \`await\` 后使用它来实现类似 "sleep" 的效果。
|
|
717
|
+
*
|
|
718
|
+
* @param delay - 等待的毫秒数。默认值为 0,表示不延迟。
|
|
719
|
+
* @param fn - (可选) 一个在延迟结束后立即执行的函数。
|
|
720
|
+
*
|
|
721
|
+
* @returns 一个 Promise,当延迟结束后解析(resolve)。
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* // 基本用法:延迟 2 秒后打印消息
|
|
725
|
+
* console.log('开始');
|
|
726
|
+
* await sleep(2000);
|
|
727
|
+
* console.log('2秒后执行');
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* // 带回调函数的用法:延迟 1 秒后执行清理工作
|
|
731
|
+
* sleep(1000, () => {
|
|
732
|
+
* console.log('执行清理操作...');
|
|
733
|
+
* // 清理代码...
|
|
734
|
+
* });
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* // 在循环中使用:每次迭代后延迟 500 毫秒
|
|
738
|
+
* for (let i = 0; i < 5; i++) {
|
|
739
|
+
* console.log(\`当前值: \${i}\`);
|
|
740
|
+
* await sleep(500);
|
|
741
|
+
* }
|
|
742
|
+
*/
|
|
743
|
+
export function sleep(delay: number = 0, fn?: () => void) {
|
|
744
|
+
return new Promise<void>((resolve) =>
|
|
745
|
+
setTimeout(() => {
|
|
746
|
+
fn?.()
|
|
747
|
+
resolve()
|
|
748
|
+
}, delay),
|
|
749
|
+
)
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* 为 \`validate\` 预置默认触发时机 \`['blur', 'change']\`。
|
|
754
|
+
*
|
|
755
|
+
* @param type 校验类型。
|
|
756
|
+
* @param rules 校验规则。
|
|
757
|
+
* @param pureValid 是否直接返回布尔值。
|
|
758
|
+
* @returns 与 \`validate\` 一致。
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* const rule = validateTrigger('required', { message: '请输入名称' })
|
|
762
|
+
*/
|
|
763
|
+
export function validateTrigger(type = 'required', rules: ValidateRules = {}, pureValid = false) {
|
|
764
|
+
let mergeRules = {
|
|
765
|
+
trigger: ['blur', 'change'] as ValidateTriggerType[],
|
|
766
|
+
...rules,
|
|
767
|
+
}
|
|
768
|
+
return validate(type, mergeRules, pureValid)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* 生成 Element Plus 表单校验规则,或直接执行纯校验。
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* const mobileRule = validate('mobile')
|
|
776
|
+
*
|
|
777
|
+
* @example
|
|
778
|
+
* const rangeRule = validate('between', { min: 1, max: 99 })
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* const isIp = validate('ip', '192.168.1.1', true)
|
|
782
|
+
*
|
|
783
|
+
* @param type 校验类型;如果传入的值不在内置类型列表中,则会直接作为错误提示文案使用。
|
|
784
|
+
* @param rules 校验规则配置,或在 \`pureValid=true\` 时直接传待校验值。
|
|
785
|
+
* @param pureValid 是否直接返回布尔值。
|
|
786
|
+
* @returns \`pureValid=true\` 时返回布尔值,否则返回 Element Plus 规则对象。
|
|
787
|
+
*/
|
|
788
|
+
// 定义验证类型枚举
|
|
789
|
+
enum ValidateType {
|
|
790
|
+
REQUIRED = 'required',
|
|
791
|
+
PASSWORD = 'password',
|
|
792
|
+
NUMBER = 'number',
|
|
793
|
+
POSITIVE = 'positive',
|
|
794
|
+
ZERO_POSITIVE = 'zeroPositive',
|
|
795
|
+
INTEGER = 'integer',
|
|
796
|
+
DECIMAL = 'decimal',
|
|
797
|
+
MOBILE = 'mobile',
|
|
798
|
+
EMAIL = 'email',
|
|
799
|
+
IP = 'ip',
|
|
800
|
+
PORT = 'port',
|
|
801
|
+
BETWEEN = 'between',
|
|
802
|
+
LENGTH = 'length',
|
|
803
|
+
SAME = 'same',
|
|
804
|
+
CUSTOM = 'custom',
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export function validate(type: string = 'required', rules: ValidateInput = {}, pureValid = false): ValidateRuleResult | boolean {
|
|
808
|
+
const rulesObject: ValidateRules = typeof rules === 'object' && rules !== null ? rules : {}
|
|
809
|
+
let trigger = rulesObject.trigger || []
|
|
810
|
+
// 使用枚举值组成的联合类型来确保类型安全
|
|
811
|
+
const typeMaps = Object.values(ValidateType) as string[]
|
|
812
|
+
let parseRequired = rulesObject.required ?? true
|
|
813
|
+
|
|
814
|
+
// 如果不包含typeMaps中的类型, 直接将第一个参数作为message
|
|
815
|
+
if (!typeMaps.includes(type)) {
|
|
816
|
+
return {
|
|
817
|
+
required: parseRequired,
|
|
818
|
+
message: type,
|
|
819
|
+
trigger: trigger,
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (type === ValidateType.REQUIRED) {
|
|
823
|
+
return {
|
|
824
|
+
required: parseRequired,
|
|
825
|
+
message: rulesObject.message ?? '请输入',
|
|
826
|
+
trigger: trigger,
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// validator: this.validateName,
|
|
831
|
+
if (type === ValidateType.PASSWORD) {
|
|
832
|
+
const validateName = (rule: any, value: any, callback: (error?: Error) => void) => {
|
|
833
|
+
let validFlag = /^[a-zA-Z0-9_-]+$/.test(value)
|
|
834
|
+
if (!validFlag) {
|
|
835
|
+
callback(new Error(rulesObject.message || '密码只能由英文、数字、下划线、中划线组成'))
|
|
836
|
+
} else {
|
|
837
|
+
callback()
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
validator: validateName,
|
|
842
|
+
trigger: trigger,
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (type === ValidateType.POSITIVE || type === ValidateType.NUMBER) {
|
|
846
|
+
// 正整数
|
|
847
|
+
return _validValue(rules, '请输入正整数', pureValid, /^[1-9]+\\d*$/)
|
|
848
|
+
}
|
|
849
|
+
if (type === ValidateType.ZERO_POSITIVE) {
|
|
850
|
+
// 正整数且包含0
|
|
851
|
+
return _validValue(rules, '请输入非负整数', pureValid, /^(0|[1-9]+\\d*)$/)
|
|
852
|
+
}
|
|
853
|
+
// 整数, 包含负数和0
|
|
854
|
+
if (type === ValidateType.INTEGER) {
|
|
855
|
+
return _validValue(rules, '请输入整数', pureValid, /^(0|[-]?[1-9]\\d*)$/)
|
|
856
|
+
}
|
|
857
|
+
// 非负数, 整数和最多2位小数
|
|
858
|
+
if (type === ValidateType.DECIMAL) {
|
|
859
|
+
return _validValue(rules, '请输入非负数字, 包含小数且最多2位', pureValid, /(0|[1-9]\\d*)(\\.\\d{1, 2})?|0\\.\\d{1,2}/)
|
|
860
|
+
}
|
|
861
|
+
if (type === ValidateType.MOBILE) {
|
|
862
|
+
return _validValue(rules, '请输入正确的手机号', pureValid, /^[1][0-9]{10}$/)
|
|
863
|
+
}
|
|
864
|
+
if (type === ValidateType.EMAIL) {
|
|
865
|
+
return _validValue(rules, '请输入正确的email', pureValid, /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/)
|
|
866
|
+
}
|
|
867
|
+
if (type === ValidateType.IP) {
|
|
868
|
+
return _validValue(
|
|
869
|
+
rules,
|
|
870
|
+
'请输入正确的ip地址',
|
|
871
|
+
pureValid,
|
|
872
|
+
/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
|
873
|
+
)
|
|
874
|
+
}
|
|
875
|
+
if (type === ValidateType.PORT) {
|
|
876
|
+
return _validValue(
|
|
877
|
+
rules,
|
|
878
|
+
'请输入1-65535的端口号',
|
|
879
|
+
pureValid,
|
|
880
|
+
/^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-5][0-5][0-3][0-5])$/,
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
if (type === ValidateType.BETWEEN) {
|
|
884
|
+
let min = rulesObject.min
|
|
885
|
+
let max = rulesObject.max
|
|
886
|
+
const validateBetween = (rule: any, value: any, callback: (error?: Error) => void) => {
|
|
887
|
+
let validFlag = /^-?[0-9]+$/.test(value)
|
|
888
|
+
if (!validFlag) {
|
|
889
|
+
callback(new Error('请输入数字'))
|
|
890
|
+
}
|
|
891
|
+
if (value < min && min !== undefined) {
|
|
892
|
+
callback(new Error(\`数字不能小于\${min}\`))
|
|
893
|
+
}
|
|
894
|
+
if (value > max && max !== undefined) {
|
|
895
|
+
callback(new Error(\`数字不能大于\${max}\`))
|
|
896
|
+
}
|
|
897
|
+
callback()
|
|
898
|
+
}
|
|
899
|
+
return {
|
|
900
|
+
validator: validateBetween,
|
|
901
|
+
trigger: trigger,
|
|
902
|
+
required: parseRequired,
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (type === ValidateType.LENGTH) {
|
|
906
|
+
return {
|
|
907
|
+
min: rulesObject.min,
|
|
908
|
+
max: rulesObject.max,
|
|
909
|
+
message: rulesObject.message ?? \`请输入\${rulesObject.min}到\${rulesObject.max}个字符\`,
|
|
910
|
+
trigger: trigger,
|
|
911
|
+
required: parseRequired,
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (type === ValidateType.SAME) {
|
|
916
|
+
const validateSame = (rule: any, value: any, callback: (error?: Error) => void) => {
|
|
917
|
+
let isSame = value === rulesObject.value
|
|
918
|
+
if (!isSame) {
|
|
919
|
+
const errMessage = rulesObject.message || '密码和确认密码要一致'
|
|
920
|
+
callback(new Error(errMessage))
|
|
921
|
+
}
|
|
922
|
+
if (parseRequired && !value) {
|
|
923
|
+
callback(new Error(rulesObject.message || '请输入'))
|
|
924
|
+
}
|
|
925
|
+
callback()
|
|
926
|
+
}
|
|
927
|
+
let res = {
|
|
928
|
+
validator: validateSame,
|
|
929
|
+
trigger: trigger,
|
|
930
|
+
required: parseRequired,
|
|
931
|
+
}
|
|
932
|
+
return res
|
|
933
|
+
}
|
|
934
|
+
if (type === ValidateType.CUSTOM) {
|
|
935
|
+
// _validValue(rules, '请输入正确的手机号', pureValid, /^[1][0-9]{10}$/)
|
|
936
|
+
if (pureValid) {
|
|
937
|
+
return _validValue(rulesObject.value, rulesObject.message, pureValid, rulesObject.reg!)
|
|
938
|
+
} else {
|
|
939
|
+
return _validValue(rulesObject, rulesObject.message, pureValid, rulesObject.reg!)
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function _validValue(rules: any, msg: string | undefined, pureValid: boolean, reg: RegExp) {
|
|
944
|
+
if (pureValid === true) {
|
|
945
|
+
return reg.test(rules)
|
|
946
|
+
}
|
|
947
|
+
const validatePhone = (rule: any, value: any, callback: (error?: Error) => void) => {
|
|
948
|
+
let validFlag = reg.test(value)
|
|
949
|
+
if (!validFlag) {
|
|
950
|
+
callback(new Error(rules.message ?? msg))
|
|
951
|
+
} else {
|
|
952
|
+
callback()
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
validator: validatePhone,
|
|
957
|
+
required: rules.required ?? true,
|
|
958
|
+
trigger: trigger,
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* 复制文本到剪贴板。
|
|
965
|
+
*
|
|
966
|
+
* @param text 需要复制的文本。
|
|
967
|
+
* @param toastParams 提示配置;传 \`hideToast: true\` 时不显示成功提示。
|
|
968
|
+
* @returns 是否复制成功。
|
|
969
|
+
*
|
|
970
|
+
* @example
|
|
971
|
+
* copy('这是要复制的文本')
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* copy('静默复制', { hideToast: true })
|
|
975
|
+
*/
|
|
976
|
+
export const copy = (text: string, toastParams: CopyOptions = {}): boolean => {
|
|
977
|
+
const textarea = document.createElement('textarea')
|
|
978
|
+
textarea.value = text
|
|
979
|
+
textarea.style.position = 'fixed'
|
|
980
|
+
document.body.appendChild(textarea)
|
|
981
|
+
textarea.select()
|
|
982
|
+
document.execCommand('copy')
|
|
983
|
+
document.body.removeChild(textarea)
|
|
984
|
+
if (!toastParams.hideToast) {
|
|
985
|
+
// 确保我们传递给$toast的是一个有效的ToastOptions对象
|
|
986
|
+
const { hideToast, ...toastOptions } = toastParams
|
|
987
|
+
$toast(text + '复制成功', toastOptions)
|
|
988
|
+
return true
|
|
989
|
+
}
|
|
990
|
+
return true
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* 带变量名的日志输出封装。
|
|
995
|
+
*
|
|
996
|
+
* @param variableStr 变量名或标签名。
|
|
997
|
+
* @param variable 需要打印的值。
|
|
998
|
+
* @param otherInfo 附加信息,常用于手动传入文件路径。
|
|
999
|
+
*
|
|
1000
|
+
* @example
|
|
1001
|
+
* const formData = { id: 1, name: 'andy' }
|
|
1002
|
+
* log('formData', formData)
|
|
1003
|
+
*/
|
|
1004
|
+
export function log(variableStr: string, variable: unknown, otherInfo = ''): void {
|
|
1005
|
+
const stack = new Error().stack.split('\\n')[2].trim() // 获取调用堆栈的第二行
|
|
1006
|
+
const matchResult = stack.match(/\\((.*):(\\d+):(\\d+)\\)/)
|
|
1007
|
+
let fileInfo = ''
|
|
1008
|
+
try {
|
|
1009
|
+
if (matchResult && otherInfo) {
|
|
1010
|
+
const lineNumber = matchResult[2]
|
|
1011
|
+
fileInfo = \`vscode://file\${JSON.parse(otherInfo)}:\${lineNumber}\`
|
|
1012
|
+
}
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
fileInfo = otherInfo
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (isRef(variable)) {
|
|
1018
|
+
let unrefVariable = unref(variable)
|
|
1019
|
+
_log(toRaw(unrefVariable))
|
|
1020
|
+
} else {
|
|
1021
|
+
_log(variable)
|
|
1022
|
+
}
|
|
1023
|
+
function _log(consoleData) {
|
|
1024
|
+
if (getType(consoleData) === 'object' || getType(consoleData) === 'array') {
|
|
1025
|
+
consola.log(
|
|
1026
|
+
\`%c\${variableStr} \`,
|
|
1027
|
+
'background:#fff; color: blue;font-size: 0.8em',
|
|
1028
|
+
JSON.stringify(consoleData, null, '\\t'),
|
|
1029
|
+
\`\${fileInfo}\`,
|
|
1030
|
+
)
|
|
1031
|
+
} else {
|
|
1032
|
+
consola.log(\`%c\${variableStr} \`, 'background:#fff; color: blue;font-size: 0.8em', consoleData, \`\${fileInfo}\`)
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function getType(type) {
|
|
1036
|
+
if (typeof type === 'object') {
|
|
1037
|
+
const objType = Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
|
|
1038
|
+
return objType
|
|
1039
|
+
} else {
|
|
1040
|
+
return typeof type
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* 生成指定范围内的随机整数。
|
|
1047
|
+
*
|
|
1048
|
+
* @param min 最小值,默认 \`0\`。
|
|
1049
|
+
* @param max 最大值,默认 \`10\`。
|
|
1050
|
+
* @returns 随机整数。
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* random()
|
|
1054
|
+
*
|
|
1055
|
+
* @example
|
|
1056
|
+
* random(100, 999)
|
|
1057
|
+
*/
|
|
1058
|
+
export function random(min = 0, max = 10): number {
|
|
1059
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* 将驼峰命名转换为连接符命名。
|
|
1064
|
+
*
|
|
1065
|
+
* @param text 要转换的文本。
|
|
1066
|
+
* @param connect 连接符,默认 \`-\`。
|
|
1067
|
+
* @returns 转换后的文本。
|
|
1068
|
+
*
|
|
1069
|
+
* @example
|
|
1070
|
+
* toLine('NameAndy')
|
|
1071
|
+
* // => 'name-andy'
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* toLine('CompTitle', '_')
|
|
1075
|
+
* // => 'comp_title'
|
|
1076
|
+
*/
|
|
1077
|
+
export function toLine(text: string, connect = '-'): string {
|
|
1078
|
+
let translateText = text
|
|
1079
|
+
.replace(/([A-Z])/g, (match, p1, offset, origin) => {
|
|
1080
|
+
if (offset === 0) {
|
|
1081
|
+
return \`\${match.toLocaleLowerCase()}\`
|
|
1082
|
+
} else {
|
|
1083
|
+
return \`\${connect}\${match.toLocaleLowerCase()}\`
|
|
1084
|
+
}
|
|
1085
|
+
})
|
|
1086
|
+
.toLocaleLowerCase()
|
|
1087
|
+
return translateText
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const _CSS_UNIT_RE = /^[0-9]+(\\.[0-9]+)?(px|%|em|rem|vw|vh|ch)$/
|
|
1091
|
+
/**
|
|
1092
|
+
* 将宽度值规范化为可直接用于样式的结果。
|
|
1093
|
+
*
|
|
1094
|
+
* @param initValue 宽度值,支持数字、数字字符串、CSS 长度字符串和 \`ref\`。
|
|
1095
|
+
* @param isBase 为 \`true\` 时直接返回宽度字符串;否则返回 \`{ width }\` 对象。
|
|
1096
|
+
* @returns 宽度字符串或宽度样式对象;无效值返回空字符串或空对象。
|
|
1097
|
+
*
|
|
1098
|
+
* @example
|
|
1099
|
+
* processWidth(200)
|
|
1100
|
+
* // => { width: '200px' }
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* processWidth('50%', true)
|
|
1104
|
+
* // => '50%'
|
|
1105
|
+
*/
|
|
1106
|
+
export function processWidth(initValue: WidthInput, isBase: true): string
|
|
1107
|
+
export function processWidth(initValue: WidthInput, isBase?: false): WidthStyleResult | {}
|
|
1108
|
+
export function processWidth(initValue: WidthInput, isBase = false): WidthStyleResult | {} | string {
|
|
1109
|
+
const raw = unref(initValue)
|
|
1110
|
+
|
|
1111
|
+
if (!raw) {
|
|
1112
|
+
return isBase ? '' : {}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const str = typeof raw === 'number' ? \`\${raw}\` : raw
|
|
1116
|
+
|
|
1117
|
+
let res: string
|
|
1118
|
+
if (!isNaN(Number(str))) {
|
|
1119
|
+
res = str + 'px'
|
|
1120
|
+
} else if (_CSS_UNIT_RE.test(str)) {
|
|
1121
|
+
res = str
|
|
1122
|
+
} else {
|
|
1123
|
+
return isBase ? '' : {}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return isBase ? res : { width: res }
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* 创建节流函数。
|
|
1131
|
+
*
|
|
1132
|
+
* @param fn 需要节流执行的函数。
|
|
1133
|
+
* @param delay 节流间隔,单位毫秒,默认 \`1000\`。
|
|
1134
|
+
* @returns 节流后的函数。
|
|
1135
|
+
*
|
|
1136
|
+
* @example
|
|
1137
|
+
* const onResize = throttle(() => {
|
|
1138
|
+
* console.log('resize')
|
|
1139
|
+
* }, 300)
|
|
1140
|
+
*/
|
|
1141
|
+
export function throttle<T extends Func>(fn: T, delay = 1000): (...args: Parameters<T>) => void {
|
|
1142
|
+
// last为上一次触发毁掉的时间,timer是定时器
|
|
1143
|
+
let last = 0
|
|
1144
|
+
let timer: ReturnType<typeof setTimeout> | undefined = undefined
|
|
1145
|
+
// 将throttle处理结果当做函数返回
|
|
1146
|
+
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
|
|
1147
|
+
// 保留调用时的this上下文
|
|
1148
|
+
let context = this
|
|
1149
|
+
// 记录本次触发回调的时间
|
|
1150
|
+
let now = +new Date()
|
|
1151
|
+
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
|
|
1152
|
+
if (now - last < delay) {
|
|
1153
|
+
// 如果时间间隔小于设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
|
|
1154
|
+
clearTimeout(timer)
|
|
1155
|
+
timer = setTimeout(function () {
|
|
1156
|
+
last = now
|
|
1157
|
+
fn.apply(context, args as any)
|
|
1158
|
+
}, delay)
|
|
1159
|
+
} else {
|
|
1160
|
+
// 如果时间间隔超出了设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
|
|
1161
|
+
last = now
|
|
1162
|
+
fn.apply(context, args as any)
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* 统一处理 Promise 或任务函数执行结果。
|
|
1169
|
+
*
|
|
1170
|
+
* 推荐优先传入函数,这样可以同时捕获同步 \`throw\` 和异步 \`reject\`。
|
|
1171
|
+
*
|
|
1172
|
+
* @param task Promise,或返回值 / Promise 的函数。
|
|
1173
|
+
* @param sendLoading 可选的 loading \`ref\`。
|
|
1174
|
+
* @returns 包含 \`data\` 和 \`error\` 的结果对象。
|
|
1175
|
+
*
|
|
1176
|
+
* @example
|
|
1177
|
+
* const loading = ref(false)
|
|
1178
|
+
* const { data, error } = await tryCatch(() => fetchUserData(), loading)
|
|
1179
|
+
*
|
|
1180
|
+
* @example
|
|
1181
|
+
* const { data, error } = await tryCatch(Promise.resolve({ id: 1 }))
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* const { data, error } = await tryCatch(() => {
|
|
1185
|
+
* if (!form.name) throw new Error('请输入名称')
|
|
1186
|
+
* return submitForm(form)
|
|
1187
|
+
* })
|
|
1188
|
+
*/
|
|
1189
|
+
export function tryCatch<T>(
|
|
1190
|
+
task: Promise<T>,
|
|
1191
|
+
sendLoading?: Ref<boolean> | null,
|
|
1192
|
+
): Promise<TryCatchResult<T>>
|
|
1193
|
+
export function tryCatch<T>(
|
|
1194
|
+
task: () => T | Promise<T>,
|
|
1195
|
+
sendLoading?: Ref<boolean> | null,
|
|
1196
|
+
): Promise<TryCatchResult<T>>
|
|
1197
|
+
export async function tryCatch<T>(
|
|
1198
|
+
task: TryCatchTask<T>,
|
|
1199
|
+
sendLoading?: Ref<boolean> | null,
|
|
1200
|
+
): Promise<TryCatchResult<T>> {
|
|
1201
|
+
const updateLoading = (value: boolean): void => {
|
|
1202
|
+
if (isRef(sendLoading)) {
|
|
1203
|
+
sendLoading.value = value
|
|
1204
|
+
} else if (sendLoading !== undefined && sendLoading !== null) {
|
|
1205
|
+
console.warn('Cannot modify non-ref sendLoading directly!')
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
updateLoading(true)
|
|
1210
|
+
|
|
1211
|
+
try {
|
|
1212
|
+
const data = typeof task === 'function' ? await task() : await task
|
|
1213
|
+
return { data, error: null }
|
|
1214
|
+
} catch (error: any) {
|
|
1215
|
+
return { data: null, error }
|
|
1216
|
+
} finally {
|
|
1217
|
+
updateLoading(false)
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* 防抖函数返回值类型。
|
|
1223
|
+
*/
|
|
1224
|
+
type DebouncedFunction<T extends Func> = ((...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>) & {
|
|
1225
|
+
cancel: () => void
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* 创建防抖函数。
|
|
1230
|
+
*
|
|
1231
|
+
* @param func 需要防抖执行的函数。
|
|
1232
|
+
* @param delay 延迟时间,单位毫秒,默认 \`500\`。
|
|
1233
|
+
* @param immediate 是否在第一次调用时立即执行。
|
|
1234
|
+
* @param resultCallback 每次真正执行后触发的结果回调。
|
|
1235
|
+
* @returns 带 \`cancel()\` 方法的防抖函数。
|
|
1236
|
+
*
|
|
1237
|
+
* @example
|
|
1238
|
+
* const search = debounce((keyword: string) => {
|
|
1239
|
+
* return keyword.trim()
|
|
1240
|
+
* }, 300)
|
|
1241
|
+
*
|
|
1242
|
+
* await search('oeos')
|
|
1243
|
+
*/
|
|
1244
|
+
export function debounce<T extends Func>(
|
|
1245
|
+
func: T,
|
|
1246
|
+
delay: number = 500,
|
|
1247
|
+
immediate?: boolean,
|
|
1248
|
+
resultCallback?: (result: ReturnType<T>) => void,
|
|
1249
|
+
): DebouncedFunction<T> {
|
|
1250
|
+
let timer: null | ReturnType<typeof setTimeout> = null
|
|
1251
|
+
let isInvoke = false
|
|
1252
|
+
const _debounce = function (this: ThisParameterType<T>, ...args: Parameters<T>) {
|
|
1253
|
+
return new Promise((resolve, reject) => {
|
|
1254
|
+
if (timer) clearTimeout(timer)
|
|
1255
|
+
if (immediate && !isInvoke) {
|
|
1256
|
+
try {
|
|
1257
|
+
const result = func.apply(this, args)
|
|
1258
|
+
if (resultCallback) resultCallback(result)
|
|
1259
|
+
resolve(result)
|
|
1260
|
+
} catch (e) {
|
|
1261
|
+
reject(e)
|
|
1262
|
+
}
|
|
1263
|
+
isInvoke = true
|
|
1264
|
+
} else {
|
|
1265
|
+
timer = setTimeout(() => {
|
|
1266
|
+
try {
|
|
1267
|
+
const result = func.apply(this, args)
|
|
1268
|
+
if (resultCallback) resultCallback(result)
|
|
1269
|
+
resolve(result)
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
reject(e)
|
|
1272
|
+
}
|
|
1273
|
+
isInvoke = false
|
|
1274
|
+
timer = null
|
|
1275
|
+
}, delay)
|
|
1276
|
+
}
|
|
1277
|
+
})
|
|
1278
|
+
} as DebouncedFunction<T>
|
|
1279
|
+
_debounce.cancel = function () {
|
|
1280
|
+
if (timer) clearTimeout(timer)
|
|
1281
|
+
isInvoke = false
|
|
1282
|
+
timer = null
|
|
1283
|
+
}
|
|
1284
|
+
return _debounce
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* 打开确认框。
|
|
1289
|
+
*
|
|
1290
|
+
* 相比直接调用 \`ElMessageBox.confirm\`,这里额外处理了默认参数、嵌套弹窗挂载点和 \`appContext\`。
|
|
1291
|
+
*
|
|
1292
|
+
* @param message 确认框内容,支持字符串、VNode 或渲染函数。
|
|
1293
|
+
* @param options MessageBox 配置项。
|
|
1294
|
+
* @param appContext 可选的 Vue 应用上下文。
|
|
1295
|
+
* @returns \`ElMessageBox.confirm\` 返回的 Promise。
|
|
1296
|
+
*
|
|
1297
|
+
* @example
|
|
1298
|
+
* await confirm('确定删除吗?')
|
|
1299
|
+
*
|
|
1300
|
+
* @example
|
|
1301
|
+
* await confirm('确认提交?', {
|
|
1302
|
+
* showCancelButton: true,
|
|
1303
|
+
* appendTo: '#dialogRoot',
|
|
1304
|
+
* })
|
|
1305
|
+
*/
|
|
1306
|
+
export function confirm(message: ConfirmMessage, options: ConfirmOptions = {}, appContext: AppContext | null = null) {
|
|
1307
|
+
const resolvedMessage = typeof message === 'function' ? message() : message
|
|
1308
|
+
const resolvedAppendTo = _resolveAppendTarget(options?.appendTo)
|
|
1309
|
+
const resolvedAppContext = _resolveAppContext(options?.appContext || appContext)
|
|
1310
|
+
|
|
1311
|
+
const mergeOptions = {
|
|
1312
|
+
title: '提示',
|
|
1313
|
+
draggable: true,
|
|
1314
|
+
showCancelButton: true,
|
|
1315
|
+
cancelButtonText: '取消',
|
|
1316
|
+
confirmButtonText: '确定',
|
|
1317
|
+
dangerouslyUseHTMLString: true,
|
|
1318
|
+
...options,
|
|
1319
|
+
appendTo: resolvedAppendTo,
|
|
1320
|
+
appContext: resolvedAppContext,
|
|
1321
|
+
confirmButtonClass: _mergeClassNames(DEFAULT_CONFIRM_BUTTON_CLASS, options?.confirmButtonClass),
|
|
1322
|
+
cancelButtonClass: _mergeClassNames(DEFAULT_CANCEL_BUTTON_CLASS, options?.cancelButtonClass),
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
return ElMessageBox.confirm(resolvedMessage, mergeOptions)
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function _mergeClassNames(...classNames: Array<string | undefined | null | false>) {
|
|
1329
|
+
return classNames.filter(Boolean).join(' ')
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function _resolveAppendTarget(appendTo?: ConfirmAppendTarget) {
|
|
1333
|
+
if (typeof document === 'undefined') {
|
|
1334
|
+
return appendTo
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (appendTo instanceof HTMLElement) {
|
|
1338
|
+
return appendTo
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (typeof appendTo === 'string' && appendTo.trim()) {
|
|
1342
|
+
const rawSelector = appendTo.trim()
|
|
1343
|
+
|
|
1344
|
+
try {
|
|
1345
|
+
const selector =
|
|
1346
|
+
rawSelector.startsWith('#') || rawSelector.startsWith('.') || rawSelector.startsWith('[')
|
|
1347
|
+
? rawSelector
|
|
1348
|
+
: \`#\${rawSelector}\`
|
|
1349
|
+
|
|
1350
|
+
return document.querySelector(selector) || appendTo
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
return appendTo
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const overlayList = Array.from(document.querySelectorAll<HTMLElement>('.el-overlay'))
|
|
1357
|
+
const dialogOverlay = overlayList
|
|
1358
|
+
.filter((item) => {
|
|
1359
|
+
if (!item.isConnected || item.style.display === 'none') {
|
|
1360
|
+
return false
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const containsDialog = item.querySelector('.el-dialog, .el-drawer')
|
|
1364
|
+
const containsMessageBox = item.querySelector('.el-message-box')
|
|
1365
|
+
|
|
1366
|
+
return !!containsDialog && !containsMessageBox
|
|
1367
|
+
})
|
|
1368
|
+
.at(-1)
|
|
1369
|
+
|
|
1370
|
+
return dialogOverlay || appendTo
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function _resolveAppContext(appContext: AppContext | null | undefined) {
|
|
1374
|
+
if (appContext) {
|
|
1375
|
+
return appContext
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (typeof document === 'undefined') {
|
|
1379
|
+
return ElMessageBox.install?.context || ElMessageBox._context
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
return ElMessageBox.install?.context || ElMessageBox._context || document.querySelector('#app')?._vue_app?._context
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* 读取根节点上的 CSS 自定义变量。
|
|
1387
|
+
*
|
|
1388
|
+
* @param propertyName CSS 变量名,例如 \`--blue\`。
|
|
1389
|
+
* @returns CSS 变量的值。
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
* getVariable('--vp-c-brand-1')
|
|
1393
|
+
*/
|
|
1394
|
+
export function getVariable(propertyName: string, fallback = ''): string {
|
|
1395
|
+
if (typeof document === 'undefined') {
|
|
1396
|
+
return fallback
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const res = getComputedStyle(document.documentElement).getPropertyValue(propertyName).trim()
|
|
1400
|
+
return res || fallback
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
declare const __OEOS_UTILS_BUILD_TIME__: string
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* 返回当前 utils 包的构建时间。
|
|
1407
|
+
*
|
|
1408
|
+
* @returns 构建时间字符串。
|
|
1409
|
+
*
|
|
1410
|
+
* @example
|
|
1411
|
+
* test()
|
|
1412
|
+
*/
|
|
1413
|
+
export function test(): string {
|
|
1414
|
+
return \`build time: \${__OEOS_UTILS_BUILD_TIME__}\`
|
|
1415
|
+
}
|
|
1416
|
+
`;
|
|
1417
|
+
export {
|
|
1418
|
+
n as default
|
|
1419
|
+
};
|