oeos-components 0.4.8 → 0.4.11

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.
@@ -0,0 +1,111 @@
1
+ const n = `import dayjs from 'dayjs'
2
+ import type { ConfigType, OpUnitType, QUnitType } from 'dayjs'
3
+
4
+ type DayUnit = QUnitType | OpUnitType
5
+
6
+ /**
7
+ * 使用 \`dayjs\` 格式化日期。
8
+ *
9
+ * @param date 要格式化的日期值。为空时默认使用当前时间。
10
+ * @param format 输出格式,默认 \`YYYY-MM-DD HH:mm:ss\`。
11
+ * @returns 格式化后的日期字符串。
12
+ *
13
+ * @example
14
+ * formatDate('2026-04-24 12:30:45')
15
+ * // => '2026-04-24 12:30:45'
16
+ *
17
+ * @example
18
+ * formatDate('2026-04-24 12:30:45', 'YYYY/MM/DD')
19
+ * // => '2026/04/24'
20
+ */
21
+ export function formatDate(date?: ConfigType, format = 'YYYY-MM-DD HH:mm:ss'): string {
22
+ return dayjs(date || new Date()).format(format)
23
+ }
24
+
25
+ /**
26
+ * 将日期格式化为 \`YYYY-MM-DD\`。
27
+ *
28
+ * @param date 要格式化的日期值。
29
+ * @returns 形如 \`2026-04-24\` 的字符串。
30
+ *
31
+ * @example
32
+ * formatDateToDay(new Date())
33
+ *
34
+ * @example
35
+ * formatDateToDay('2026-04-24 12:30:45')
36
+ * // => '2026-04-24'
37
+ */
38
+ export const formatDateToDay = (date: ConfigType): string => formatDate(date, 'YYYY-MM-DD')
39
+
40
+ /**
41
+ * 将日期格式化为 \`YYYY-MM-DD HH:mm\`。
42
+ *
43
+ * @param date 要格式化的日期值。
44
+ * @returns 形如 \`2026-04-24 12:30\` 的字符串。
45
+ *
46
+ * @example
47
+ * formatDateToMinute(new Date())
48
+ *
49
+ * @example
50
+ * formatDateToMinute('2026-04-24 12:30:45')
51
+ * // => '2026-04-24 12:30'
52
+ */
53
+ export const formatDateToMinute = (date: ConfigType): string => formatDate(date, 'YYYY-MM-DD HH:mm')
54
+
55
+ /**
56
+ * 计算两个日期之间的差值。
57
+ *
58
+ * @param date1 起始时间。未传时返回 \`undefined\`。
59
+ * @param date2 对比时间,默认取当前时间。
60
+ * @param format 差值单位,默认 \`second\`。
61
+ * @returns 两个时间的差值。
62
+ *
63
+ * @example
64
+ * diffDate('2026-04-24 12:00:00', '2026-04-24 11:59:00')
65
+ * // => 60
66
+ *
67
+ * @example
68
+ * diffDate('2026-04-24 12:00:00', '2026-04-24 11:30:00', 'minute')
69
+ * // => 30
70
+ */
71
+ export function diffDate(date1: ConfigType, date2: ConfigType = dayjs(), format: DayUnit = 'second'): number | undefined {
72
+ if (!date1) return
73
+ return dayjs(date1).diff(dayjs(date2), format)
74
+ }
75
+
76
+ /**
77
+ * 将秒数转换成“几秒前 / 几分钟前 / 几小时前 / 几天前”这类相对时间文案。
78
+ *
79
+ * @param second 距离当前时间的秒数。
80
+ * @returns 中文相对时间描述。
81
+ *
82
+ * @example
83
+ * diffDateFromCurrent(45)
84
+ * // => '45秒前'
85
+ *
86
+ * @example
87
+ * diffDateFromCurrent(3600)
88
+ * // => '1小时前'
89
+ */
90
+ export function diffDateFromCurrent(second: number): string {
91
+ if (second >= 60 * 60 * 24 * 365) {
92
+ return \`\${parseInt(String(second / (60 * 60 * 24 * 365)))}年前\`
93
+ }
94
+ if (second >= 60 * 60 * 24 * 30) {
95
+ return \`\${parseInt(String(second / (60 * 60 * 24 * 30)))}月前\`
96
+ }
97
+ if (second >= 60 * 60 * 24) {
98
+ return \`\${parseInt(String(second / (60 * 60 * 24)))}天前\`
99
+ }
100
+ if (second >= 60 * 60) {
101
+ return \`\${parseInt(String(second / (60 * 60)))}小时前\`
102
+ }
103
+ if (second >= 60) {
104
+ return \`\${parseInt(String(second / 60))}分钟前\`
105
+ }
106
+ return \`\${parseInt(String(second))}秒前\`
107
+ }
108
+ `;
109
+ export {
110
+ n as default
111
+ };
@@ -0,0 +1,488 @@
1
+ const n = `import { getType } from './base'
2
+ import { isNumber, isStringNumber } from './is'
3
+
4
+ /**
5
+ * \`formatBytes\` 的配置项。
6
+ */
7
+ export interface FormatBytesOptions {
8
+ /**
9
+ * 小数位数,默认 \`2\`。
10
+ */
11
+ digit?: number
12
+ /**
13
+ * 是否加千分位,默认 \`false\`。
14
+ */
15
+ thousands?: boolean
16
+ /**
17
+ * 文本前缀,例如 \`$\`。
18
+ */
19
+ prefix?: string
20
+ /**
21
+ * 文本后缀,例如 \`/s\`。
22
+ */
23
+ suffix?: string
24
+ /**
25
+ * 取整方式,默认 \`floor\`。
26
+ */
27
+ roundType?: 'floor' | 'ceil' | 'round'
28
+ }
29
+
30
+ /**
31
+ * \`formatBytesConvert\` 的配置项。
32
+ */
33
+ export interface FormatBytesConvertOptions {
34
+ /**
35
+ * 是否使用千分位输出。
36
+ */
37
+ thounsands?: boolean
38
+ /**
39
+ * 保留小数位数。
40
+ */
41
+ digit?: number
42
+ }
43
+
44
+ /**
45
+ * \`formatImg\` 的配置项。
46
+ */
47
+ export interface FormatImgOptions {
48
+ /**
49
+ * 静态资源根目录,默认 \`assets/images\`。
50
+ */
51
+ basePath?: string
52
+ }
53
+
54
+ /**
55
+ * \`formatToFixed\` 的配置项。
56
+ */
57
+ export interface FormatToFixedOptions {
58
+ /**
59
+ * 小数位数,默认 \`2\`。
60
+ */
61
+ digit?: number
62
+ /**
63
+ * 文本前缀,例如 \`$\`。
64
+ */
65
+ prefix?: string
66
+ /**
67
+ * 文本后缀,例如 \`%\`。
68
+ */
69
+ suffix?: string
70
+ /**
71
+ * 是否保留原单位,默认 \`true\`。
72
+ */
73
+ unit?: boolean
74
+ /**
75
+ * 是否添加千分位,默认 \`false\`。
76
+ */
77
+ thousands?: boolean
78
+ }
79
+
80
+ export type FormatTimeInput = Date | string | number
81
+ export type FormatTimeFallback = string | ((time: FormatTimeInput) => string)
82
+
83
+ /**
84
+ * 将字节数格式化成更易读的单位字符串。
85
+ *
86
+ * @param bytes 字节数,支持数字和数字字符串。
87
+ * @param options 配置项。
88
+ * @returns 格式化后的字节字符串;如果输入不是合法数字,则原样返回。
89
+ *
90
+ * @example
91
+ * formatBytes(1024)
92
+ * // => '1.00 KB'
93
+ *
94
+ * @example
95
+ * formatBytes(1040000, {
96
+ * digit: 3,
97
+ * prefix: '$',
98
+ * suffix: '/s',
99
+ * roundType: 'round',
100
+ * thousands: true,
101
+ * })
102
+ * // => '$1,015.625 KB/s'
103
+ */
104
+ export function formatBytes(bytes: number | string, options: FormatBytesOptions = {}): string | number {
105
+ let { digit = 2, thousands = false, prefix = '', suffix = '', roundType = 'floor' } = options
106
+ if (isStringNumber(bytes as any) || isNumber(bytes)) {
107
+ bytes = Number(bytes)
108
+ } else {
109
+ return bytes
110
+ }
111
+
112
+ if (bytes <= 1) {
113
+ return Math[roundType](bytes * Math.pow(10, digit)) / Math.pow(10, digit) + ' B'
114
+ }
115
+
116
+ const k = 1024
117
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
118
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
119
+ const power = Math.pow(k, i)
120
+
121
+ let num = bytes / power
122
+ num = Math[roundType](num * Math.pow(10, digit)) / Math.pow(10, digit)
123
+
124
+ let res = num.toFixed(digit) + ' ' + sizes[i]
125
+ if (thousands) {
126
+ res = String(formatThousands(res))
127
+ }
128
+
129
+ return \`\${prefix}\${res}\${suffix}\`
130
+ }
131
+
132
+ /**
133
+ * 将带单位的字节字符串转换为原始字节数。
134
+ *
135
+ * @param oBytes 字节字符串,例如 \`1.5GB\`、\`1,024 KB\`,也支持纯数字。
136
+ * @param options 配置项。
137
+ * @returns 字节数;如启用了 \`digit\` 或 \`thounsands\`,返回格式化后的字符串。
138
+ *
139
+ * @example
140
+ * formatBytesConvert('0.5GB')
141
+ * // => 536870912
142
+ *
143
+ * @example
144
+ * formatBytesConvert('1,234 GB', { thounsands: true })
145
+ * // => '1,324,997,410,816'
146
+ */
147
+ export function formatBytesConvert(
148
+ oBytes: unknown,
149
+ options: FormatBytesConvertOptions = {},
150
+ ): string | number | undefined {
151
+ let { thounsands = false, digit = 0 } = options
152
+ let bytes = oBytes
153
+
154
+ const parseDigitThounsands = (val: unknown) => {
155
+ let finalRes = val as any
156
+ if (digit) {
157
+ finalRes = Number(finalRes).toFixed(digit)
158
+ }
159
+ if (thounsands) {
160
+ finalRes = formatThousands(finalRes)
161
+ }
162
+ return finalRes
163
+ }
164
+
165
+ if (isStringNumber(oBytes as string) || isNumber(oBytes) || getType(oBytes) !== 'string') {
166
+ return parseDigitThounsands(oBytes)
167
+ }
168
+ if (!oBytes) {
169
+ return parseDigitThounsands(oBytes)
170
+ }
171
+
172
+ const regex = /^\\d{1,3}(,\\d{3})*(\\.\\d+)?[a-zA-Z ]*$/
173
+ if (typeof oBytes === 'string' && regex.test(oBytes)) {
174
+ bytes = oBytes.replace(/,/g, '')
175
+ if (isStringNumber(bytes) || isNumber(bytes) || getType(bytes) !== 'string') {
176
+ return parseDigitThounsands(bytes)
177
+ }
178
+ }
179
+
180
+ const bytesRegex = /^(\\d+(?:\\.\\d+)?)\\s*([BKMGTPEZY]?B|Byte)$/i
181
+ const units = {
182
+ B: 1,
183
+ BYTE: 1,
184
+ KB: 1024,
185
+ MB: 1024 ** 2,
186
+ GB: 1024 ** 3,
187
+ TB: 1024 ** 4,
188
+ PB: 1024 ** 5,
189
+ EB: 1024 ** 6,
190
+ ZB: 1024 ** 7,
191
+ YB: 1024 ** 8,
192
+ }
193
+
194
+ if (typeof bytes !== 'string') {
195
+ return parseDigitThounsands(bytes)
196
+ }
197
+
198
+ const match = bytes.match(bytesRegex)
199
+ if (!match) {
200
+ console.warn("Invalid bytes format. Please provide a valid bytes string, like '100GB'.")
201
+ return
202
+ }
203
+
204
+ const size = parseFloat(match[1])
205
+ const unit = match[2].toUpperCase() as keyof typeof units
206
+ if (!Object.prototype.hasOwnProperty.call(units, unit)) {
207
+ console.warn(
208
+ "Invalid bytes unit. Please provide a valid unit, like 'B', 'BYTE', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', or 'YB'.",
209
+ )
210
+ return
211
+ }
212
+
213
+ return parseDigitThounsands(size * units[unit])
214
+ }
215
+
216
+ /**
217
+ * 为数字或数字字符串添加千分位分隔符。
218
+ *
219
+ * @param value 需要格式化的值。
220
+ * @returns 添加千分位后的字符串;如果不符合规则则原样返回。
221
+ *
222
+ * @example
223
+ * formatThousands(1234567)
224
+ * // => '1,234,567'
225
+ *
226
+ * @example
227
+ * formatThousands('1234.12MB')
228
+ * // => '1,234.12MB'
229
+ */
230
+ export function formatThousands(value: string | number): string | number {
231
+ const matches = ('' + value).match(/^([\\d,]+)(\\.?)(\\d+)?(\\D+)?$/)
232
+ if (!matches) {
233
+ return value
234
+ }
235
+
236
+ const numericString = matches[1].replace(/\\D/g, '')
237
+ const decimalString = matches[3] ? \`.\${matches[3]}\` : ''
238
+ const unit = matches[4] || ''
239
+ const numberWithSeparator = numericString.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')
240
+
241
+ return \`\${numberWithSeparator}\${decimalString}\${unit}\`
242
+ }
243
+
244
+ /**
245
+ * 将时间值格式化为指定模板字符串。
246
+ *
247
+ * 支持 \`Date\`、毫秒时间戳、秒级时间戳、ISO 字符串和普通日期字符串。
248
+ * 纯日期字符串(如 \`2022-03-04\`)会按本地时区的 \`00:00:00\` 解析。
249
+ *
250
+ * @param time 时间值,默认当前时间。
251
+ * @param cFormat 格式模板,默认 \`{y}-{m}-{d} {h}:{i}:{s}\`。
252
+ * @param fallback 非法日期时的兜底返回值,默认返回原始输入。
253
+ * @returns 格式化后的时间字符串。
254
+ *
255
+ * @example
256
+ * formatTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
257
+ *
258
+ * @example
259
+ * formatTime(1713926400, '{y}/{m}/{d}')
260
+ * // => '2024/04/24'
261
+ */
262
+ export function formatTime(
263
+ time: FormatTimeInput = new Date(),
264
+ cFormat = '{y}-{m}-{d} {h}:{i}:{s}',
265
+ fallback: FormatTimeFallback = (value) => String(value),
266
+ ): string {
267
+ let date: Date
268
+ const timeStr = String(time)
269
+
270
+ if (typeof time === 'object' && time instanceof Date) {
271
+ date = time
272
+ } else {
273
+ const isoRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,3})?)?$/
274
+ const dateOnlyRegex = /^(\\d{4})-(\\d{2})-(\\d{2})$/
275
+
276
+ if (isoRegex.test(timeStr)) {
277
+ date = new Date(time)
278
+ } else if (dateOnlyRegex.test(timeStr)) {
279
+ const [, year, month, day] = timeStr.match(dateOnlyRegex)!
280
+ date = new Date(Number(year), Number(month) - 1, Number(day))
281
+ } else if (timeStr.includes('.') && !isNaN(parseFloat(timeStr))) {
282
+ date = new Date(parseFloat(timeStr) * 1000)
283
+ } else if (/^\\d{10}$/.test(timeStr)) {
284
+ date = new Date(parseInt(timeStr) * 1000)
285
+ } else {
286
+ date = new Date(time)
287
+ }
288
+ }
289
+
290
+ if (isNaN(date.getTime())) {
291
+ return typeof fallback === 'function' ? fallback(time) : fallback
292
+ }
293
+
294
+ const formatObj = {
295
+ y: date.getFullYear(),
296
+ m: date.getMonth() + 1,
297
+ d: date.getDate(),
298
+ h: date.getHours(),
299
+ i: date.getMinutes(),
300
+ s: date.getSeconds(),
301
+ a: date.getDay(),
302
+ }
303
+
304
+ return cFormat.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
305
+ const value = formatObj[key as keyof typeof formatObj]
306
+
307
+ if (key === 'a') {
308
+ return ['日', '一', '二', '三', '四', '五', '六'][value]
309
+ }
310
+
311
+ if (result.length > 0 && value < 10) {
312
+ return '0' + value
313
+ }
314
+
315
+ return String(value)
316
+ })
317
+ }
318
+
319
+ /**
320
+ * 将持续时间时间戳格式化为“天 / 时 / 分 / 秒”文本。
321
+ *
322
+ * @param timestamp 持续时间,单位毫秒。
323
+ * @param cFormat 输出模板,默认 \`{d}天{h}时{i}分{s}秒\`。
324
+ * @returns 格式化后的持续时间文本。
325
+ *
326
+ * @example
327
+ * formatDurationTime(1162821)
328
+ * // => '19分24秒'
329
+ *
330
+ * @example
331
+ * formatDurationTime(5 * 60 * 1000, '{i}分{s}秒')
332
+ * // => '05分00秒'
333
+ */
334
+ export function formatDurationTime(timestamp: number, cFormat = '{d}天{h}时{i}分{s}秒'): string {
335
+ const secondsPerMinute = 60
336
+ const minutesPerHour = 60
337
+ const hoursPerDay = 24
338
+ let totalSeconds = Math.floor(timestamp / 1000)
339
+ let days = 0
340
+
341
+ if (cFormat.indexOf('d') !== -1) {
342
+ days = Math.floor(totalSeconds / (secondsPerMinute * minutesPerHour * hoursPerDay))
343
+ totalSeconds %= secondsPerMinute * minutesPerHour * hoursPerDay
344
+ }
345
+
346
+ const hours = Math.floor(totalSeconds / (secondsPerMinute * minutesPerHour))
347
+ totalSeconds %= secondsPerMinute * minutesPerHour
348
+ const minutes = Math.floor(totalSeconds / secondsPerMinute)
349
+ const seconds = totalSeconds % secondsPerMinute
350
+
351
+ const formatObj = {
352
+ d: days,
353
+ h: hours,
354
+ i: minutes,
355
+ s: seconds,
356
+ }
357
+
358
+ let parseFormat = cFormat
359
+ if (days === 0) {
360
+ parseFormat = cFormat.match(/{h}.*/g)?.[0] ?? ''
361
+ if (hours === 0) {
362
+ parseFormat = cFormat.match(/{i}.*/g)?.[0] ?? ''
363
+ if (minutes === 0) {
364
+ parseFormat = cFormat.match(/{s}.*/g)?.[0] ?? ''
365
+ }
366
+ }
367
+ }
368
+
369
+ return parseFormat.replace(/{(y|m|d|h|i|s)+}/g, (result, key) => {
370
+ let value = formatObj[key as keyof typeof formatObj]
371
+ if (result.length > 0 && value < 10 && value !== 0) {
372
+ return \`0\${value}\`
373
+ }
374
+ return String(value || '00')
375
+ })
376
+ }
377
+
378
+ /**
379
+ * 获取 \`assets\` 目录下的静态资源地址。
380
+ *
381
+ * @param photoName 文件名;未带后缀时会自动补 \`.png\`。
382
+ * @param addPath 额外子目录,例如 \`menu\`。
383
+ * @param options 配置项。
384
+ * @returns 静态资源的完整 URL;如果本身已是 \`http/https\` 地址,则原样返回。
385
+ *
386
+ * @example
387
+ * formatImg('logo')
388
+ *
389
+ * @example
390
+ * formatImg('avatar.png', 'user')
391
+ */
392
+ export function formatImg(
393
+ photoName: string,
394
+ addPath = '',
395
+ { basePath = 'assets/images' }: FormatImgOptions = {},
396
+ ): string {
397
+ if (photoName.startsWith('http') || photoName.startsWith('https')) {
398
+ return photoName
399
+ }
400
+ if (photoName.indexOf('.') === -1) {
401
+ photoName = photoName + '.png'
402
+ }
403
+ const addLastSlash = addPath.endsWith('/') || !addPath ? addPath : \`\${addPath}/\`
404
+ const addLastBasePathSlash = basePath.endsWith('/') || !basePath ? basePath : \`\${basePath}/\`
405
+ const mergeSrc = \`\${addLastSlash}\${photoName}\`
406
+ return new URL(\`../\${addLastBasePathSlash}\${mergeSrc}\`, import.meta.url).href
407
+ }
408
+
409
+ /**
410
+ * 按指定小数位格式化数字,并可附带前后缀、单位和千分位。
411
+ *
412
+ * @param value 需要格式化的值。
413
+ * @param options 小数位数或配置项。
414
+ * @returns 格式化后的字符串。
415
+ *
416
+ * @example
417
+ * formatToFixed('22.1', 2)
418
+ * // => '22.10'
419
+ *
420
+ * @example
421
+ * formatToFixed('22 TB', { digit: 2, unit: false, prefix: '$' })
422
+ * // => '$22.00'
423
+ */
424
+ export function formatToFixed(value: unknown, options?: FormatToFixedOptions | number): string {
425
+ if (typeof options === 'number') {
426
+ options = { digit: options }
427
+ }
428
+
429
+ const finalOptions: Required<FormatToFixedOptions> = {
430
+ digit: 2,
431
+ prefix: '',
432
+ suffix: '',
433
+ unit: true,
434
+ thousands: false,
435
+ ...options,
436
+ }
437
+ const { digit, prefix, suffix, unit, thousands } = finalOptions
438
+
439
+ const matches = ('' + value).match(/^([\\d,]+)(\\.?)(\\d+)?(\\D+)?$/)
440
+ if (!matches) {
441
+ return String(value)
442
+ }
443
+
444
+ const numericString = matches[1].replace(/\\D/g, '')
445
+ const decimalString = matches[3] ? \`.\${matches[3]}\` : ''
446
+ let finalUnit = matches[4] || ''
447
+ let res = numericString
448
+
449
+ if (isStringNumber(numericString) || isNumber(numericString)) {
450
+ res = Number(numericString + decimalString).toFixed(digit)
451
+ }
452
+
453
+ if (thousands) {
454
+ res = String(formatThousands(res))
455
+ }
456
+
457
+ if (!unit) {
458
+ finalUnit = ''
459
+ }
460
+
461
+ return \`\${prefix}\${res}\${finalUnit}\${suffix}\`
462
+ }
463
+
464
+ /**
465
+ * 将纯文本中的换行和制表符转换成可直接渲染的 HTML 片段。
466
+ *
467
+ * @param str 需要格式化的文本。
468
+ * @returns 字符串输入会返回转换后的 HTML;非字符串输入会原样返回。
469
+ *
470
+ * @example
471
+ * formatTextToHtml('第1行\\n\\t第2行')
472
+ * // => '第1行<br>&nbsp;&nbsp;&nbsp;&nbsp;第2行'
473
+ */
474
+ export function formatTextToHtml(str: string): string
475
+ export function formatTextToHtml<T>(str: T): T
476
+ export function formatTextToHtml(str: unknown): unknown {
477
+ if (!str || typeof str !== 'string') {
478
+ return str
479
+ }
480
+
481
+ str = str.replace(/\\n/g, '<br>')
482
+ str = str.replace(/\\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
483
+ return str
484
+ }
485
+ `;
486
+ export {
487
+ n as default
488
+ };
@@ -0,0 +1,10 @@
1
+ const o = `export * from './base'
2
+ export * from './day'
3
+ export * from './is'
4
+ export * from './ws'
5
+ export * from './format'
6
+ export * from './test'
7
+ `;
8
+ export {
9
+ o as default
10
+ };