vanilla-jui 1.6.5 → 1.7.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/dist/index.d.ts +25 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/css/layout.css +13 -2
- package/src/css/themes/default/menu.css +190 -62
- package/src/utilities/id.ts +73 -0
package/package.json
CHANGED
package/src/css/layout.css
CHANGED
|
@@ -136,11 +136,22 @@
|
|
|
136
136
|
var(--ui-bg-muted) 0%,
|
|
137
137
|
var(--ui-surface-subtle) 100%
|
|
138
138
|
);
|
|
139
|
+
color: var(--ui-fg-subtle);
|
|
140
|
+
font-size: var(--font-size-sm);
|
|
139
141
|
display: flex;
|
|
140
142
|
align-items: center;
|
|
141
143
|
justify-content: center;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* 图片容器 */
|
|
147
|
+
.image-container {
|
|
148
|
+
position: relative;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.image-container img {
|
|
152
|
+
position: absolute;
|
|
153
|
+
width: 100%;
|
|
154
|
+
height: 100%;
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
/* ==============================
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
border-radius: var(--menu-item-border-radius);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.j-menu
|
|
46
|
+
.j-menu > .menu > .menu-item > a {
|
|
47
47
|
font-weight: var(--font-medium);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
height: var(--menu-item-icon-size);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
.j-menu .menu .menu-item.menu-item-has-children>a::after {
|
|
56
|
+
.j-menu .menu .menu-item.menu-item-has-children > a::after {
|
|
57
57
|
content: '';
|
|
58
58
|
width: 0;
|
|
59
59
|
height: 0;
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
transition: transform 0.2s ease-in-out;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
.j-menu .menu .menu-item.menu-item-has-children:hover>a::after {
|
|
68
|
+
.j-menu .menu .menu-item.menu-item-has-children:hover > a::after {
|
|
69
69
|
transform: rotate(180deg);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
.j-menu .menu .menu-item .sub-menu {
|
|
73
|
-
transform: translateY(1px);
|
|
73
|
+
/* transform: translateY(1px); */
|
|
74
74
|
padding: var(--s2) 0;
|
|
75
75
|
background: var(--sub-menu-bg);
|
|
76
76
|
border-width: var(--sub-menu-border-width);
|
|
@@ -99,26 +99,39 @@
|
|
|
99
99
|
|
|
100
100
|
.j-menu .menu .menu-item .sub-menu .menu-item:hover a,
|
|
101
101
|
.j-menu .menu .menu-item a:hover,
|
|
102
|
-
.j-menu .menu .menu-item.menu-item-has-children:hover>a {
|
|
102
|
+
.j-menu .menu .menu-item.menu-item-has-children:hover > a {
|
|
103
103
|
text-decoration: none;
|
|
104
104
|
background: var(--tone-soft);
|
|
105
105
|
color: var(--tone-text);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
.j-menu .menu
|
|
109
|
-
.j-menu .menu
|
|
108
|
+
.j-menu .menu > .menu-item.current-menu-item > a,
|
|
109
|
+
.j-menu .menu > .menu-item.current-menu-ancestor > a {
|
|
110
110
|
color: var(--tone-solid);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
.j-menu
|
|
113
|
+
.j-menu
|
|
114
|
+
.menu
|
|
115
|
+
.menu-item.current-menu-item
|
|
116
|
+
> .sub-menu
|
|
117
|
+
> .menu-item:not(.current-menu-item)
|
|
118
|
+
a {
|
|
114
119
|
color: inherit;
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
.j-menu
|
|
122
|
+
.j-menu
|
|
123
|
+
.menu-item.current-menu-item
|
|
124
|
+
> .sub-menu
|
|
125
|
+
> .menu-item:not(.current-menu-item):hover
|
|
126
|
+
a {
|
|
118
127
|
color: var(--tone-fg);
|
|
119
128
|
}
|
|
120
129
|
|
|
121
|
-
.j-menu
|
|
130
|
+
.j-menu
|
|
131
|
+
.menu-item.menu-item-has-children:hover
|
|
132
|
+
> .sub-menu
|
|
133
|
+
> .menu-item.current-menu-item
|
|
134
|
+
a {
|
|
122
135
|
color: var(--tone-fg);
|
|
123
136
|
background-color: var(--tone-solid);
|
|
124
137
|
outline: 1px solid var(--tone-solid);
|
|
@@ -144,13 +157,13 @@
|
|
|
144
157
|
}
|
|
145
158
|
|
|
146
159
|
/* list card menu */
|
|
147
|
-
.j-menu .menu-item.menu-item-has-children.list-card-menu:hover
|
|
160
|
+
.j-menu .menu-item.menu-item-has-children.list-card-menu:hover > .sub-menu {
|
|
148
161
|
width: auto;
|
|
149
162
|
display: grid;
|
|
150
163
|
grid-template-columns: repeat(2, 1fr);
|
|
151
164
|
}
|
|
152
165
|
|
|
153
|
-
.j-menu .menu-item.list-card-menu
|
|
166
|
+
.j-menu .menu-item.list-card-menu > .sub-menu > .menu-item > a {
|
|
154
167
|
overflow: hidden;
|
|
155
168
|
display: -webkit-box;
|
|
156
169
|
-webkit-box-orient: vertical;
|
|
@@ -158,19 +171,21 @@
|
|
|
158
171
|
line-clamp: 1;
|
|
159
172
|
}
|
|
160
173
|
|
|
161
|
-
.j-menu .menu-item.menu-item-has-children.list-card-menu:hover
|
|
174
|
+
.j-menu .menu-item.menu-item-has-children.list-card-menu:hover > .sub-menu {
|
|
162
175
|
min-width: 240px;
|
|
163
176
|
max-width: 360px;
|
|
164
177
|
transform: translateX(-20%) translateY(1px);
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
.j-menu .menu-item.list-card-menu
|
|
180
|
+
.j-menu .menu-item.list-card-menu > .sub-menu > .menu-item > a {
|
|
168
181
|
padding: calc(var(--space) * 1.25) calc(var(--space) * 2.5);
|
|
169
182
|
line-height: 1.75;
|
|
170
183
|
}
|
|
171
184
|
|
|
172
185
|
/* advanced card menu */
|
|
173
|
-
.j-menu
|
|
186
|
+
.j-menu
|
|
187
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
188
|
+
> .sub-menu {
|
|
174
189
|
display: grid;
|
|
175
190
|
grid-template-columns: repeat(6, 1fr);
|
|
176
191
|
grid-template-rows: repeat(6, 1fr);
|
|
@@ -182,61 +197,126 @@
|
|
|
182
197
|
transform: translateX(-33.33%) translateY(1px);
|
|
183
198
|
}
|
|
184
199
|
|
|
185
|
-
.j-menu
|
|
200
|
+
.j-menu
|
|
201
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
202
|
+
> .sub-menu
|
|
203
|
+
img {
|
|
186
204
|
margin: 0;
|
|
187
205
|
}
|
|
188
206
|
|
|
189
|
-
.j-menu
|
|
207
|
+
.j-menu
|
|
208
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
209
|
+
> .sub-menu
|
|
210
|
+
> .menu-item:nth-child(1) {
|
|
190
211
|
grid-area: 1 / 1 / 2 / 3;
|
|
191
212
|
}
|
|
192
213
|
|
|
193
|
-
.j-menu
|
|
214
|
+
.j-menu
|
|
215
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
216
|
+
> .sub-menu
|
|
217
|
+
> .menu-item:nth-child(2) {
|
|
194
218
|
grid-area: 1 / 3 / 4 / 5;
|
|
195
219
|
}
|
|
196
220
|
|
|
197
|
-
.j-menu
|
|
221
|
+
.j-menu
|
|
222
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
223
|
+
> .sub-menu
|
|
224
|
+
> .menu-item:nth-child(3) {
|
|
198
225
|
grid-area: 1 / 5 / 4 / 7;
|
|
199
226
|
}
|
|
200
227
|
|
|
201
|
-
.j-menu
|
|
228
|
+
.j-menu
|
|
229
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
230
|
+
> .sub-menu
|
|
231
|
+
> .menu-item:nth-child(4) {
|
|
202
232
|
grid-area: 2 / 1 / 3 / 3;
|
|
203
233
|
}
|
|
204
234
|
|
|
205
|
-
.j-menu
|
|
235
|
+
.j-menu
|
|
236
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
237
|
+
> .sub-menu
|
|
238
|
+
> .menu-item:nth-child(5) {
|
|
206
239
|
grid-area: 3 / 1 / 4 / 3;
|
|
207
240
|
}
|
|
208
241
|
|
|
209
|
-
.j-menu
|
|
242
|
+
.j-menu
|
|
243
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
244
|
+
> .sub-menu
|
|
245
|
+
> .menu-item:nth-child(6) {
|
|
210
246
|
grid-area: 4 / 1 / 5 / 3;
|
|
211
247
|
}
|
|
212
248
|
|
|
213
|
-
.j-menu
|
|
249
|
+
.j-menu
|
|
250
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
251
|
+
> .sub-menu
|
|
252
|
+
> .menu-item:nth-child(7) {
|
|
214
253
|
grid-area: 4 / 3 / 7 / 5;
|
|
215
254
|
}
|
|
216
255
|
|
|
217
|
-
.j-menu
|
|
256
|
+
.j-menu
|
|
257
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
258
|
+
> .sub-menu
|
|
259
|
+
> .menu-item:nth-child(8) {
|
|
218
260
|
grid-area: 4 / 5 / 7 / 7;
|
|
219
261
|
}
|
|
220
262
|
|
|
221
|
-
.j-menu
|
|
263
|
+
.j-menu
|
|
264
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
265
|
+
> .sub-menu
|
|
266
|
+
> .menu-item:nth-child(9) {
|
|
222
267
|
grid-area: 5 / 1 / 6 / 3;
|
|
223
268
|
}
|
|
224
269
|
|
|
225
|
-
.j-menu
|
|
270
|
+
.j-menu
|
|
271
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
272
|
+
> .sub-menu
|
|
273
|
+
> .menu-item:nth-child(10) {
|
|
226
274
|
grid-area: 6 / 1 / 7 / 3;
|
|
227
275
|
}
|
|
228
276
|
|
|
229
|
-
.j-menu
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
277
|
+
.j-menu
|
|
278
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
279
|
+
> .sub-menu
|
|
280
|
+
> .menu-item:nth-child(2)
|
|
281
|
+
a,
|
|
282
|
+
.j-menu
|
|
283
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
284
|
+
> .sub-menu
|
|
285
|
+
> .menu-item:nth-child(3)
|
|
286
|
+
a,
|
|
287
|
+
.j-menu
|
|
288
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
289
|
+
> .sub-menu
|
|
290
|
+
> .menu-item:nth-child(7)
|
|
291
|
+
a,
|
|
292
|
+
.j-menu
|
|
293
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
294
|
+
> .sub-menu
|
|
295
|
+
> .menu-item:nth-child(8)
|
|
296
|
+
a {
|
|
233
297
|
padding: 0;
|
|
234
298
|
}
|
|
235
299
|
|
|
236
|
-
.j-menu
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
300
|
+
.j-menu
|
|
301
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
302
|
+
> .sub-menu
|
|
303
|
+
> .menu-item:nth-child(2)
|
|
304
|
+
a:hover,
|
|
305
|
+
.j-menu
|
|
306
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
307
|
+
> .sub-menu
|
|
308
|
+
> .menu-item:nth-child(3)
|
|
309
|
+
a:hover,
|
|
310
|
+
.j-menu
|
|
311
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
312
|
+
> .sub-menu
|
|
313
|
+
> .menu-item:nth-child(7)
|
|
314
|
+
a:hover,
|
|
315
|
+
.j-menu
|
|
316
|
+
.menu-item.menu-item-has-children.advanced-card-menu:hover
|
|
317
|
+
> .sub-menu
|
|
318
|
+
> .menu-item:nth-child(8)
|
|
319
|
+
a:hover {
|
|
240
320
|
background-color: transparent;
|
|
241
321
|
outline-color: transparent;
|
|
242
322
|
}
|
|
@@ -288,7 +368,10 @@
|
|
|
288
368
|
line-height: var(--line-height-paragraph);
|
|
289
369
|
}
|
|
290
370
|
|
|
291
|
-
.j-menu[data-menu-type='mobile']
|
|
371
|
+
.j-menu[data-menu-type='mobile']
|
|
372
|
+
.menu
|
|
373
|
+
.menu-item.menu-item-has-children
|
|
374
|
+
> a::after {
|
|
292
375
|
vertical-align: middle;
|
|
293
376
|
position: absolute;
|
|
294
377
|
right: 1rem;
|
|
@@ -298,7 +381,10 @@
|
|
|
298
381
|
border-top: 4px solid transparent;
|
|
299
382
|
}
|
|
300
383
|
|
|
301
|
-
.j-menu[data-menu-type='mobile']
|
|
384
|
+
.j-menu[data-menu-type='mobile']
|
|
385
|
+
.menu
|
|
386
|
+
.menu-item.menu-item-has-children:hover
|
|
387
|
+
> a::after {
|
|
302
388
|
transform: none;
|
|
303
389
|
}
|
|
304
390
|
|
|
@@ -316,7 +402,10 @@
|
|
|
316
402
|
}
|
|
317
403
|
|
|
318
404
|
.j-menu[data-menu-type='mobile'] .menu .menu-item a:hover,
|
|
319
|
-
.j-menu[data-menu-type='mobile']
|
|
405
|
+
.j-menu[data-menu-type='mobile']
|
|
406
|
+
.menu
|
|
407
|
+
.menu-item.menu-item-has-children:hover
|
|
408
|
+
> a,
|
|
320
409
|
.j-menu[data-menu-type='mobile'] .menu .sub-menu .menu-item:hover a {
|
|
321
410
|
background: inherit;
|
|
322
411
|
color: inherit;
|
|
@@ -326,7 +415,10 @@
|
|
|
326
415
|
background: var(--menu-item-active-bg);
|
|
327
416
|
}
|
|
328
417
|
|
|
329
|
-
.j-menu[data-menu-type='mobile']
|
|
418
|
+
.j-menu[data-menu-type='mobile']
|
|
419
|
+
.menu
|
|
420
|
+
.menu-item.menu-item-has-children.is-active
|
|
421
|
+
> .sub-menu {
|
|
330
422
|
visibility: visible;
|
|
331
423
|
opacity: 1;
|
|
332
424
|
z-index: var(--z-submenu);
|
|
@@ -338,12 +430,14 @@
|
|
|
338
430
|
*/
|
|
339
431
|
.j-menu[data-menu-type='bottom'] {
|
|
340
432
|
--menu-item-border-radius: none;
|
|
341
|
-
--sub-menu-bg: linear-gradient(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
433
|
+
--sub-menu-bg: linear-gradient(
|
|
434
|
+
to right,
|
|
435
|
+
var(--ui-surface-subtle) 20%,
|
|
436
|
+
var(--ui-border) 40%,
|
|
437
|
+
var(--ui-outline) 50%,
|
|
438
|
+
var(--ui-border) 60%,
|
|
439
|
+
var(--ui-surface-subtle) 80%
|
|
440
|
+
);
|
|
347
441
|
|
|
348
442
|
position: fixed;
|
|
349
443
|
bottom: 0;
|
|
@@ -358,12 +452,14 @@
|
|
|
358
452
|
justify-content: space-between;
|
|
359
453
|
gap: 1px;
|
|
360
454
|
border-top: 1px solid var(--ui-border-subtle);
|
|
361
|
-
background: linear-gradient(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
455
|
+
background: linear-gradient(
|
|
456
|
+
to bottom,
|
|
457
|
+
var(--ui-bg-subtle) 20%,
|
|
458
|
+
var(--ui-border) 40%,
|
|
459
|
+
var(--ui-outline) 50%,
|
|
460
|
+
var(--ui-border) 60%,
|
|
461
|
+
var(--ui-bg-subtle) 80%
|
|
462
|
+
);
|
|
367
463
|
}
|
|
368
464
|
|
|
369
465
|
.j-menu[data-menu-type='bottom'] .menu .menu-item {
|
|
@@ -381,7 +477,10 @@
|
|
|
381
477
|
color: var(--ui-fg);
|
|
382
478
|
}
|
|
383
479
|
|
|
384
|
-
.j-menu[data-menu-type='bottom']
|
|
480
|
+
.j-menu[data-menu-type='bottom']
|
|
481
|
+
> .menu
|
|
482
|
+
> .menu-item.menu-item-has-children
|
|
483
|
+
> a::after {
|
|
385
484
|
content: '☰';
|
|
386
485
|
font-size: var(--font-size-sm);
|
|
387
486
|
margin-left: unset;
|
|
@@ -391,17 +490,26 @@
|
|
|
391
490
|
transition: unset;
|
|
392
491
|
}
|
|
393
492
|
|
|
394
|
-
.j-menu[data-menu-type='bottom']
|
|
493
|
+
.j-menu[data-menu-type='bottom']
|
|
494
|
+
> .menu
|
|
495
|
+
.menu-item.menu-item-has-children:hover
|
|
496
|
+
> a::after {
|
|
395
497
|
transform: none;
|
|
396
498
|
}
|
|
397
499
|
|
|
398
|
-
.j-menu[data-menu-type='bottom'] .menu .menu-item.current-menu-parent>a,
|
|
399
|
-
.j-menu[data-menu-type='bottom'] .menu .menu-item.current-menu-item>a,
|
|
400
|
-
.j-menu[data-menu-type='bottom']
|
|
500
|
+
.j-menu[data-menu-type='bottom'] .menu .menu-item.current-menu-parent > a,
|
|
501
|
+
.j-menu[data-menu-type='bottom'] .menu .menu-item.current-menu-item > a,
|
|
502
|
+
.j-menu[data-menu-type='bottom']
|
|
503
|
+
.menu
|
|
504
|
+
.menu-item.menu-item-has-children.is-active
|
|
505
|
+
> a {
|
|
401
506
|
color: var(--tone-solid);
|
|
402
507
|
}
|
|
403
508
|
|
|
404
|
-
.j-menu[data-menu-type='bottom']
|
|
509
|
+
.j-menu[data-menu-type='bottom']
|
|
510
|
+
.menu
|
|
511
|
+
.menu-item.menu-item-has-children
|
|
512
|
+
> .sub-menu {
|
|
405
513
|
opacity: 0;
|
|
406
514
|
visibility: hidden;
|
|
407
515
|
display: flex;
|
|
@@ -423,28 +531,48 @@
|
|
|
423
531
|
z-index: -1;
|
|
424
532
|
}
|
|
425
533
|
|
|
426
|
-
.j-menu[data-menu-type='bottom']
|
|
534
|
+
.j-menu[data-menu-type='bottom']
|
|
535
|
+
.menu
|
|
536
|
+
.menu-item.menu-item-has-children.is-active
|
|
537
|
+
> .sub-menu {
|
|
427
538
|
opacity: 1;
|
|
428
539
|
visibility: visible;
|
|
429
540
|
transform: translateX(-50%) translateY(0%);
|
|
430
541
|
}
|
|
431
542
|
|
|
432
|
-
.j-menu[data-menu-type='bottom']
|
|
543
|
+
.j-menu[data-menu-type='bottom']
|
|
544
|
+
.menu
|
|
545
|
+
.menu-item.menu-item-has-children
|
|
546
|
+
> .sub-menu
|
|
547
|
+
> .menu-item {
|
|
433
548
|
white-space: normal;
|
|
434
549
|
border: 1px solid transparent;
|
|
435
550
|
}
|
|
436
551
|
|
|
437
|
-
.j-menu[data-menu-type='bottom']
|
|
552
|
+
.j-menu[data-menu-type='bottom']
|
|
553
|
+
.menu
|
|
554
|
+
.menu-item.menu-item-has-children
|
|
555
|
+
> .sub-menu
|
|
556
|
+
> .menu-item
|
|
557
|
+
> a {
|
|
438
558
|
min-height: var(--el-height-2xl);
|
|
439
559
|
justify-content: center;
|
|
440
560
|
}
|
|
441
561
|
|
|
442
|
-
.j-menu[data-menu-type='bottom']
|
|
562
|
+
.j-menu[data-menu-type='bottom']
|
|
563
|
+
.menu
|
|
564
|
+
.menu-item.menu-item-has-children
|
|
565
|
+
> .sub-menu
|
|
566
|
+
> .menu-item:first-of-type {
|
|
443
567
|
border-top-left-radius: var(--radius-md);
|
|
444
568
|
border-top-right-radius: var(--radius-md);
|
|
445
569
|
}
|
|
446
570
|
|
|
447
|
-
.j-menu[data-menu-type='bottom']
|
|
571
|
+
.j-menu[data-menu-type='bottom']
|
|
572
|
+
.menu
|
|
573
|
+
.menu-item.menu-item-has-children
|
|
574
|
+
> .sub-menu
|
|
575
|
+
> .menu-item:last-of-type {
|
|
448
576
|
border-bottom-left-radius: var(--radius-md);
|
|
449
577
|
border-bottom-right-radius: var(--radius-md);
|
|
450
578
|
}
|
|
@@ -515,4 +643,4 @@
|
|
|
515
643
|
color: var(--ui-fg-soft);
|
|
516
644
|
font-size: var(--font-size-sm);
|
|
517
645
|
}
|
|
518
|
-
}
|
|
646
|
+
}
|
package/src/utilities/id.ts
CHANGED
|
@@ -55,3 +55,76 @@ export function randomId(length: number = 8): string {
|
|
|
55
55
|
|
|
56
56
|
return base64.substring(0, length).replace(/\+/g, '-').replace(/\//g, '_');
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 查询参数值类型
|
|
61
|
+
*/
|
|
62
|
+
type QueryParamValue =
|
|
63
|
+
| string
|
|
64
|
+
| number
|
|
65
|
+
| boolean
|
|
66
|
+
| null
|
|
67
|
+
| undefined
|
|
68
|
+
| QueryParamValue[]
|
|
69
|
+
| { [key: string]: QueryParamValue };
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 查询参数对象类型
|
|
73
|
+
*/
|
|
74
|
+
type QueryParams = Record<string, QueryParamValue>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 对查询参数进行 SHA-256 哈希编码,生成用于缓存的 key
|
|
78
|
+
*
|
|
79
|
+
* @param params - 查询参数对象
|
|
80
|
+
* @param bytes - 取哈希前 N 字节,默认为 8,范围 1-32
|
|
81
|
+
* @returns 36 进制哈希字符串
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const key = await hashQueryParams({ page: 1, sort: 'desc' });
|
|
86
|
+
* // 返回: "2k7x9f3m4n5p"
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export async function hashQueryParams(
|
|
90
|
+
params: QueryParams,
|
|
91
|
+
bytes: number = 8
|
|
92
|
+
): Promise<string> {
|
|
93
|
+
// 1. 参数验证
|
|
94
|
+
if (typeof params !== 'object' || params === null) {
|
|
95
|
+
throw new TypeError('params must be a non-null object');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (bytes < 1 || bytes > 32) {
|
|
99
|
+
throw new RangeError('bytes must be between 1 and 32');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 2. 排序并规范化参数
|
|
103
|
+
const normalized: QueryParams = {};
|
|
104
|
+
const sortedKeys = Object.keys(params).sort();
|
|
105
|
+
|
|
106
|
+
for (const key of sortedKeys) {
|
|
107
|
+
const value = params[key];
|
|
108
|
+
// 字符串 trim 处理
|
|
109
|
+
normalized[key] = typeof value === 'string' ? value.trim() : value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 3. 序列化
|
|
113
|
+
const str = JSON.stringify(normalized);
|
|
114
|
+
const buffer = new TextEncoder().encode(str);
|
|
115
|
+
|
|
116
|
+
// 4. SHA-256 哈希
|
|
117
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|
|
118
|
+
|
|
119
|
+
// 5. 取前 N 字节(防越界)
|
|
120
|
+
const safeBytes = Math.min(bytes, hashBuffer.byteLength);
|
|
121
|
+
const slice = new Uint8Array(hashBuffer, 0, safeBytes);
|
|
122
|
+
|
|
123
|
+
// 6. 转为 BigInt 再转 36 进制
|
|
124
|
+
let num = 0n;
|
|
125
|
+
for (const byte of slice) {
|
|
126
|
+
num = (num << 8n) | BigInt(byte);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return num.toString(36);
|
|
130
|
+
}
|