@done-coding/admin-core 0.28.0 → 0.28.1-alpha.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.
Files changed (41) hide show
  1. package/es/bridge/badge.mjs +0 -1
  2. package/es/bridge/route.mjs +4 -1
  3. package/es/components/app-layout/AppHeader.vue.mjs +1 -1
  4. package/es/components/app-layout/AppHeader.vue2.mjs +2 -2
  5. package/es/components/app-layout/AppPage.vue.mjs +1 -1
  6. package/es/components/app-layout/AppPage.vue2.mjs +7 -7
  7. package/es/components/app-layout/app-page-geometry.mjs +2 -2
  8. package/es/components/display/BadgeMark.vue.mjs +1 -1
  9. package/es/components/display/BadgeMark.vue2.mjs +25 -15
  10. package/es/components/display/TabsHeader.vue.mjs +1 -1
  11. package/es/components/display/TabsHeader.vue2.mjs +1 -1
  12. package/es/components/display/TabsMain.vue.mjs +1 -1
  13. package/es/components/display/use-badge-mask.mjs +2 -3
  14. package/es/components/display/use-badge.mjs +36 -39
  15. package/es/components/form/FormMain.vue.mjs +1 -1
  16. package/es/components/form/FormSearch.vue.mjs +1 -1
  17. package/es/components/menu/MenuItemSub.vue.mjs +2 -2
  18. package/es/components/menu/MenuTree.vue.mjs +3 -3
  19. package/es/components/view-layout/ViewLayout.vue.mjs +1 -1
  20. package/es/components/view-layout/ViewLayout.vue2.mjs +1 -1
  21. package/es/config/badge-attr.mjs +6 -0
  22. package/es/hooks/use-theme-apply.mjs +48 -0
  23. package/es/index.mjs +3 -0
  24. package/es/style.css +97 -78
  25. package/es/utils/dom.mjs +12 -2
  26. package/package.json +2 -2
  27. package/src/bridge/docs/README.md +27 -2
  28. package/src/components/app-layout/docs/README-AppPage.md +1 -1
  29. package/src/components/display/docs/README-BadgeMark.md +24 -1
  30. package/src/components/page-layout/docs/README-AppPageListDetailLayout.md +1 -1
  31. package/src/components/view-layout/docs/README-ViewLayout.md +8 -2
  32. package/types/bridge/badge.d.ts +44 -7
  33. package/types/components/app-layout/app-page-geometry.d.ts +9 -2
  34. package/types/components/display/BadgeMark.vue.d.ts +5 -2
  35. package/types/components/display/use-badge.d.ts +40 -7
  36. package/types/config/badge-attr.d.ts +26 -0
  37. package/types/config/index.d.ts +1 -0
  38. package/types/hooks/menus-dispatch.d.ts +1 -0
  39. package/types/injectInfo.json.d.ts +1 -1
  40. package/types/types/route.d.ts +8 -0
  41. package/types/utils/dom.d.ts +10 -4
package/es/style.css CHANGED
@@ -66,34 +66,35 @@
66
66
  justify-content: flex-end;
67
67
  gap: 8px;
68
68
  }
69
- .badge-mark[data-v-4116ac36] .el-badge__content.is-fixed {
69
+ .badge-mark[data-v-18853ddc] .el-badge__content.is-fixed {
70
70
  right: 0;
71
71
  transform: translateY(-50%) translateX(calc(100% + 2px));
72
72
  }
73
- .badge-mark_middle[data-v-4116ac36] .el-badge__content.is-fixed {
73
+ .badge-mark_middle[data-v-18853ddc] .el-badge__content.is-fixed {
74
74
  top: 50%;
75
75
  }
76
- .badge-mark[data-v-4116ac36] .badge-mark__icon {
76
+ .badge-mark[data-v-18853ddc] .badge-mark__icon {
77
77
  display: block;
78
78
  font-size: 1em;
79
79
  line-height: 1;
80
80
  }
81
- .app-page[data-v-d875cc1d] {
82
- --dc-page-content-viewport-top: var(--b28df728);
83
- --dc-page-content-viewport-bottom: var(--d4142898);
84
- --dc-page-content-viewport-left: var(--v3063bfd0);
85
- --dc-page-content-viewport-right: var(--v472ac91a);
86
- --dc-page-content-viewport-width: var(--v469df1c6);
87
- --dc-page-content-viewport-height: var(--c0be3aa0);
81
+ .app-page[data-v-facaf577] {
82
+ --dc-page-content-viewport-top: var(--v14c1dc69);
83
+ --dc-page-content-viewport-bottom: var(--v6d1cb697);
84
+ --dc-page-content-viewport-left: var(--f914311a);
85
+ --dc-page-content-viewport-right: var(--v28c52fa0);
86
+ --dc-page-content-viewport-width: var(--v2838584c);
87
+ --dc-page-content-viewport-height: var(--v76c7ad93);
88
88
  }
89
- .app-page-shim[data-v-d875cc1d] {
89
+ .app-page-shim[data-v-facaf577] {
90
90
  display: flow-root;
91
- --app-viewport-max-height: var(--v52d7da39);
91
+ --app-viewport-max-height: var(--v9258c294);
92
92
  }
93
- .app-page-slot-shim[data-v-d875cc1d] {
93
+ .app-page-slot-shim[data-v-facaf577] {
94
94
  box-sizing: border-box;
95
95
  width: 100%;
96
96
  height: 100%;
97
+ border-radius: var(--dc-core-radius, var(--el-border-radius-base));
97
98
  }
98
99
  [data-v-64f355fe] .auto-refresh-label {
99
100
  display: flex;
@@ -130,7 +131,7 @@ to {
130
131
  text-align: center;
131
132
  font-variant-numeric: tabular-nums;
132
133
  }
133
- .view-layout-header[data-v-0507f728] {
134
+ .view-layout-header[data-v-e7dc3f12] {
134
135
  flex: 0 1 auto;
135
136
  min-width: 0;
136
137
  overflow: hidden;
@@ -141,38 +142,47 @@ to {
141
142
  white-space: nowrap;
142
143
  text-overflow: ellipsis;
143
144
  }
144
- html.dark .view-layout-toolbar[data-v-0507f728] {
145
- box-shadow: inset 0 -1px 0 0 var(--el-border-color-extra-light), inset 0 -6px 6px -6px var(--el-border-color-extra-light);
146
- }
147
- .view-layout[data-v-0507f728] {
145
+ .view-layout[data-v-e7dc3f12] {
148
146
  display: flex;
149
147
  flex-direction: column;
150
148
  }
151
- .view-layout-toolbar[data-v-0507f728] {
149
+ .view-layout-toolbar[data-v-e7dc3f12] {
152
150
  display: flex;
153
151
  justify-content: space-between;
154
152
  align-items: center;
155
- padding-bottom: 8px;
153
+ padding: 0 8px 8px;
154
+ position: relative;
156
155
  gap: 20px;
157
- box-shadow: inset 0 -1px 0 0 var(--el-border-color-darker), inset 0 -6px 6px -6px var(--el-border-color-darker);
156
+ background: color-mix(in srgb, var(--dc-core-surface, var(--el-bg-color)) 50%, var(--dc-core-ground, var(--el-bg-color-page)));
157
+ border-radius: 0;
158
158
  }
159
- .view-layout-toolbar__left[data-v-0507f728],
160
- .view-layout-toolbar__right[data-v-0507f728] {
159
+ .view-layout-toolbar[data-v-e7dc3f12]::before {
160
+ content: "";
161
+ position: absolute;
162
+ top: 0;
163
+ bottom: 0;
164
+ left: 0;
165
+ width: 3px;
166
+ border-radius: 3px;
167
+ background: var(--dc-core-primary-color, var(--el-color-primary));
168
+ }
169
+ .view-layout-toolbar__left[data-v-e7dc3f12],
170
+ .view-layout-toolbar__right[data-v-e7dc3f12] {
161
171
  display: flex;
162
172
  align-items: center;
163
173
  gap: 10px;
164
174
  }
165
- .view-layout-toolbar__left[data-v-0507f728] > *,
166
- .view-layout-toolbar__right[data-v-0507f728] > * {
175
+ .view-layout-toolbar__left[data-v-e7dc3f12] > *,
176
+ .view-layout-toolbar__right[data-v-e7dc3f12] > * {
167
177
  flex: none;
168
178
  }
169
- .view-layout-toolbar__left .view-layout-header[data-v-0507f728] {
179
+ .view-layout-toolbar__left .view-layout-header[data-v-e7dc3f12] {
170
180
  flex: 1 1 auto;
171
181
  }
172
- .view-layout-toolbar__right[data-v-0507f728] {
182
+ .view-layout-toolbar__right[data-v-e7dc3f12] {
173
183
  flex: none;
174
184
  }
175
- .view-layout-content[data-v-0507f728] {
185
+ .view-layout-content[data-v-e7dc3f12] {
176
186
  overflow: auto;
177
187
  }
178
188
  .dc-app-page-detail__title[data-v-4426d26a] {
@@ -232,33 +242,33 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
232
242
  opacity: 0.3;
233
243
  }
234
244
  }
235
- [data-v-6ea6d3c8] .el-row > .el-col > .el-form-item {
236
- margin-bottom: 18px;
245
+ [data-v-ba043bbe] .el-row > .el-col > .el-form-item {
246
+ margin-bottom: var(--dc-core-form-item-gap, 18px);
237
247
  }
238
- [data-v-6ea6d3c8] .el-row > .el-col > .el-form-item.dc-form-item-collapsed,[data-v-6ea6d3c8] .el-row > .el-col:has(> .el-form-item.dc-form-item-collapsed) {
248
+ [data-v-ba043bbe] .el-row > .el-col > .el-form-item.dc-form-item-collapsed,[data-v-ba043bbe] .el-row > .el-col:has(> .el-form-item.dc-form-item-collapsed) {
239
249
  display: none;
240
250
  }
241
- [data-v-6ea6d3c8] .el-row > .el-col > .el-form-item.dc-form-item-pseudo {
251
+ [data-v-ba043bbe] .el-row > .el-col > .el-form-item.dc-form-item-pseudo {
242
252
  margin-bottom: 8px;
243
253
  }
244
- .dc-form-main[data-v-6ea6d3c8] {
254
+ .dc-form-main[data-v-ba043bbe] {
245
255
  position: relative;
246
256
  }
247
- .dc-form-main .form-ai-fill-anchor[data-v-6ea6d3c8] {
257
+ .dc-form-main .form-ai-fill-anchor[data-v-ba043bbe] {
248
258
  opacity: 0.45;
249
259
  transition: opacity var(--dc-core-motion-duration, 0.2s) ease;
250
260
  }
251
- .dc-form-main:hover .form-ai-fill-anchor[data-v-6ea6d3c8],
252
- .dc-form-main:focus-within .form-ai-fill-anchor[data-v-6ea6d3c8],
253
- .dc-form-main .form-ai-fill-anchor[data-v-6ea6d3c8]:hover,
254
- .dc-form-main .form-ai-fill-anchor[data-v-6ea6d3c8]:focus-within {
261
+ .dc-form-main:hover .form-ai-fill-anchor[data-v-ba043bbe],
262
+ .dc-form-main:focus-within .form-ai-fill-anchor[data-v-ba043bbe],
263
+ .dc-form-main .form-ai-fill-anchor[data-v-ba043bbe]:hover,
264
+ .dc-form-main .form-ai-fill-anchor[data-v-ba043bbe]:focus-within {
255
265
  opacity: 1;
256
266
  }
257
- [data-v-6ea6d3c8] .el-col.dc-form-item-ai-filled > .el-form-item {
258
- animation: dc-form-ai-filled-flash-6ea6d3c8 1.6s ease-out;
267
+ [data-v-ba043bbe] .el-col.dc-form-item-ai-filled > .el-form-item {
268
+ animation: dc-form-ai-filled-flash-ba043bbe 1.6s ease-out;
259
269
  border-radius: 4px;
260
270
  }
261
- @keyframes dc-form-ai-filled-flash-6ea6d3c8 {
271
+ @keyframes dc-form-ai-filled-flash-ba043bbe {
262
272
  0% {
263
273
  background-color: var(--dc-core-primary-color-light-8, var(--el-color-primary-light-8));
264
274
  }
@@ -385,16 +395,23 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
385
395
  .panel-main-item-col:not(:last-of-type) .panel-main-item-col-wrap_card[data-v-e74d05c3] {
386
396
  margin-bottom: var(--v0799966c);
387
397
  }
388
- .form-search_show[data-v-c1fe91f1] {
398
+ .form-search[data-v-e52f9711] {
399
+ display: flow-root;
400
+ }
401
+ .form-search_show[data-v-e52f9711] {
389
402
  margin-bottom: 10px;
390
403
  }
391
- .form-search .btn-box[data-v-c1fe91f1] {
404
+ .form-search[data-v-e52f9711] > .el-form {
405
+ margin-bottom: calc(-1 * var(--dc-core-form-item-gap, 18px));
406
+ }
407
+ .form-search .btn-box[data-v-e52f9711] {
392
408
  display: flex;
393
409
  align-items: center;
394
410
  gap: 8px;
395
411
  justify-content: flex-end;
412
+ margin-bottom: var(--dc-core-form-item-gap, 18px);
396
413
  }
397
- .form-search .btn-box-expand-icon[data-v-c1fe91f1] {
414
+ .form-search .btn-box-expand-icon[data-v-e52f9711] {
398
415
  margin-right: 2px;
399
416
  }
400
417
  .form-verify-image[data-v-7a70397e] .el-input__suffix-inner * {
@@ -417,11 +434,13 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
417
434
  height: 100%;
418
435
  background-color: #f4f4f4;
419
436
  }
420
- .tabs-main-bar[data-v-14448dd1] {
437
+ .tabs-main-bar[data-v-2fbdd074] {
421
438
  position: relative;
422
439
  display: flex;
423
440
  align-items: center;
424
441
  /* ============ Level —— 只控字号 ============ */
442
+ /* 卡片变体的内缩距离:**同心圆角公式 R₂ = R₁ + D 的那个 D**。
443
+ 容器 padding 与外圆角 [MUST] 共用它,[MUST NOT] 两处各写一个数(改一处就不同心了)。 */
425
444
  /* ============ Variant: card —— 分段块(segmented / pill) ============
426
445
  容器一条**面色底**,选中项一块**浅色圆角块** + 主色字。对比来自「块」本身,
427
446
  不依赖与下方 content 的连通 —— 故在「各模块成面 + gap 露底」的布局里同样成立。
@@ -435,77 +454,77 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
435
454
  /* ============ Variant: indicator-compact —— 紧凑 1px 下划线 ============ */
436
455
  /* ============ Variant: plain —— 无装饰(active 加粗 + primary 字色) ============ */
437
456
  }
438
- .tabs-main-bar-item[data-v-14448dd1] {
457
+ .tabs-main-bar-item[data-v-2fbdd074] {
439
458
  border: none;
440
459
  background: transparent;
441
460
  cursor: pointer;
442
461
  color: var(--el-text-color-regular);
443
462
  transition: background 0.2s, color 0.2s, box-shadow 0.2s;
444
463
  }
445
- .tabs-main-bar-indicator[data-v-14448dd1] {
464
+ .tabs-main-bar-indicator[data-v-2fbdd074] {
446
465
  position: absolute;
447
466
  left: 0;
448
467
  background: var(--dc-core-primary-color);
449
468
  transition: transform 0.2s, width 0.2s;
450
469
  }
451
- .tabs-main-bar--l1 .tabs-main-bar-item[data-v-14448dd1] {
470
+ .tabs-main-bar--l1 .tabs-main-bar-item[data-v-2fbdd074] {
452
471
  font-size: 16px;
453
472
  }
454
- .tabs-main-bar--l2 .tabs-main-bar-item[data-v-14448dd1] {
473
+ .tabs-main-bar--l2 .tabs-main-bar-item[data-v-2fbdd074] {
455
474
  font-size: 14px;
456
475
  }
457
- .tabs-main-bar--l3 .tabs-main-bar-item[data-v-14448dd1] {
476
+ .tabs-main-bar--l3 .tabs-main-bar-item[data-v-2fbdd074] {
458
477
  font-size: 12px;
459
478
  }
460
- .tabs-main-bar--l4 .tabs-main-bar-item[data-v-14448dd1] {
479
+ .tabs-main-bar--l4 .tabs-main-bar-item[data-v-2fbdd074] {
461
480
  font-size: 12px;
462
481
  }
463
- .tabs-main-bar--variant-card[data-v-14448dd1] {
482
+ .tabs-main-bar--variant-card[data-v-2fbdd074] {
464
483
  gap: 2px;
465
484
  align-items: stretch;
466
- padding: 4px 4px 0;
485
+ padding: 8px 8px 0;
467
486
  background: var(--el-bg-color);
468
- border-radius: var(--dc-core-radius, var(--el-border-radius-base)) var(--dc-core-radius, var(--el-border-radius-base)) 0 0;
487
+ border-radius: calc(var(--dc-core-radius, var(--el-border-radius-base)) + 8px) calc(var(--dc-core-radius, var(--el-border-radius-base)) + 8px) 0 0;
469
488
  }
470
- .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-14448dd1] {
489
+ .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-2fbdd074] {
471
490
  display: flex;
472
491
  align-items: center;
473
492
  padding: 6px 16px;
474
493
  line-height: 1.4;
475
494
  border-radius: var(--dc-core-radius, var(--el-border-radius-base)) var(--dc-core-radius, var(--el-border-radius-base)) 0 0;
476
495
  }
477
- .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-14448dd1]:hover:not(.tabs-main-bar-item_active) {
496
+ .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-2fbdd074]:hover:not(.tabs-main-bar-item_active) {
478
497
  color: var(--dc-core-primary-color);
479
498
  }
480
- .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-14448dd1] {
499
+ .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-2fbdd074] {
481
500
  color: var(--dc-core-primary-color);
482
501
  font-weight: 500;
483
- background: var(--v72da80bd);
502
+ background: var(--v0fab19c0);
484
503
  }
485
- .tabs-main-bar--variant-indicator .tabs-main-bar-item[data-v-14448dd1] {
504
+ .tabs-main-bar--variant-indicator .tabs-main-bar-item[data-v-2fbdd074] {
486
505
  padding: 8px 16px;
487
506
  }
488
- .tabs-main-bar--variant-indicator .tabs-main-bar-item_active[data-v-14448dd1] {
507
+ .tabs-main-bar--variant-indicator .tabs-main-bar-item_active[data-v-2fbdd074] {
489
508
  color: var(--dc-core-primary-color);
490
509
  }
491
- .tabs-main-bar--variant-indicator .tabs-main-bar-indicator[data-v-14448dd1] {
510
+ .tabs-main-bar--variant-indicator .tabs-main-bar-indicator[data-v-2fbdd074] {
492
511
  bottom: 0;
493
512
  height: 2px;
494
513
  }
495
- .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item[data-v-14448dd1] {
514
+ .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item[data-v-2fbdd074] {
496
515
  padding: 6px 10px;
497
516
  }
498
- .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item_active[data-v-14448dd1] {
517
+ .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item_active[data-v-2fbdd074] {
499
518
  color: var(--dc-core-primary-color);
500
519
  }
501
- .tabs-main-bar--variant-indicator-compact .tabs-main-bar-indicator[data-v-14448dd1] {
520
+ .tabs-main-bar--variant-indicator-compact .tabs-main-bar-indicator[data-v-2fbdd074] {
502
521
  bottom: 0;
503
522
  height: 1px;
504
523
  }
505
- .tabs-main-bar--variant-plain .tabs-main-bar-item[data-v-14448dd1] {
524
+ .tabs-main-bar--variant-plain .tabs-main-bar-item[data-v-2fbdd074] {
506
525
  padding: 4px 8px;
507
526
  }
508
- .tabs-main-bar--variant-plain .tabs-main-bar-item_active[data-v-14448dd1] {
527
+ .tabs-main-bar--variant-plain .tabs-main-bar-item_active[data-v-2fbdd074] {
509
528
  color: var(--dc-core-primary-color);
510
529
  font-weight: 600;
511
530
  }
@@ -631,7 +650,7 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
631
650
  height: 100%;
632
651
  padding: 0 20px;
633
652
  }
634
- .app-header[data-v-9ff1c70b] {
653
+ .app-header[data-v-fe33f01b] {
635
654
  transition-duration: 0.3s;
636
655
  transition-property: height;
637
656
  transition-timing-function: linear;
@@ -639,13 +658,13 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
639
658
  box-shadow: var(--dc-core-shadow, var(--el-box-shadow));
640
659
  z-index: 1;
641
660
  }
642
- .app-header-module[data-v-9ff1c70b] {
661
+ .app-header-module[data-v-fe33f01b] {
643
662
  display: flex;
644
663
  flex: 1;
645
664
  height: 100%;
646
665
  overflow-x: auto;
647
666
  }
648
- .app-header-module-item[data-v-9ff1c70b] {
667
+ .app-header-module-item[data-v-fe33f01b] {
649
668
  display: flex;
650
669
  justify-content: center;
651
670
  align-items: center;
@@ -653,24 +672,24 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
653
672
  white-space: nowrap;
654
673
  height: 100%;
655
674
  }
656
- .app-header-module-item[data-v-9ff1c70b]:hover,
657
- .app-header-module-item_active[data-v-9ff1c70b] {
658
- color: var(--v0b60c1aa);
675
+ .app-header-module-item[data-v-fe33f01b]:hover,
676
+ .app-header-module-item_active[data-v-fe33f01b] {
677
+ color: var(--v5a41bfe6);
659
678
  }
660
- .app-header-module-item_active[data-v-9ff1c70b] {
679
+ .app-header-module-item_active[data-v-fe33f01b] {
661
680
  position: relative;
662
681
  }
663
- .app-header-module-item_active[data-v-9ff1c70b]::before {
682
+ .app-header-module-item_active[data-v-fe33f01b]::before {
664
683
  content: "";
665
684
  position: absolute;
666
685
  left: 50%;
667
686
  top: 0;
668
687
  width: 100%;
669
688
  height: 3px;
670
- background-color: var(--v0b60c1aa);
689
+ background-color: var(--v5a41bfe6);
671
690
  transform: translateX(-50%);
672
691
  }
673
- .app-header-shim[data-v-9ff1c70b] {
692
+ .app-header-shim[data-v-fe33f01b] {
674
693
  width: 100%;
675
694
  height: 100%;
676
695
  display: flex;
@@ -680,10 +699,10 @@ html.dark .view-layout-toolbar[data-v-0507f728] {
680
699
  font-size: 18px;
681
700
  font-weight: bold;
682
701
  }
683
- .app-header-module-item__icon[data-v-9ff1c70b] {
702
+ .app-header-module-item__icon[data-v-fe33f01b] {
684
703
  margin-right: 8px;
685
704
  }
686
- .app-header-right[data-v-9ff1c70b] {
705
+ .app-header-right[data-v-fe33f01b] {
687
706
  padding-left: 80px;
688
707
  display: flex;
689
708
  align-items: center;
package/es/utils/dom.mjs CHANGED
@@ -20,10 +20,20 @@ function domClosestStickyScrollport(el) {
20
20
  }
21
21
  return void 0;
22
22
  }
23
+ function domPreferredScrollBehavior() {
24
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
25
+ return "smooth";
26
+ }
27
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth";
28
+ }
23
29
  function domScrollIntoViewport(target, options = {}) {
24
30
  if (!target || typeof target.scrollIntoView !== "function") return;
25
- const { behavior = "auto", block = "nearest", inline = "nearest" } = options;
26
- target.scrollIntoView({ behavior, block, inline });
31
+ const { block = "nearest", inline = "nearest" } = options;
32
+ target.scrollIntoView({
33
+ behavior: options.behavior ?? domPreferredScrollBehavior(),
34
+ block,
35
+ inline
36
+ });
27
37
  }
28
38
  export {
29
39
  domClosestScrollableAncestor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@done-coding/admin-core",
3
- "version": "0.28.0",
3
+ "version": "0.28.1-alpha.0",
4
4
  "description": "内部前端库",
5
5
  "private": false,
6
6
  "main": "es/index.mjs",
@@ -83,5 +83,5 @@
83
83
  "dependencies": {
84
84
  "@tanstack/vue-virtual": "^3.13.35"
85
85
  },
86
- "gitHead": "94210a8d00374756d42018048143cf4b5c0799fe"
86
+ "gitHead": "89cee2eb458d349b9a6f2f1acb6088f811cd5e8d"
87
87
  }
@@ -86,18 +86,26 @@ type BadgeEntry = {
86
86
  expireAt?: number;
87
87
  /** 面向 agent / AI 对话的语义描述(core 不消费,纯透传;自由文本) */
88
88
  description?: string;
89
+ /** 本条标的版本号(core 不消费,纯透传);消费方据它做「版本化的已读记录」 */
90
+ version?: string | number;
89
91
  } & (
90
92
  | { kind: "count"; value: number; max?: number } // 数字角标
91
93
  | { kind: "new" } // 新上线
92
94
  | { kind: "updated" } // 更新
93
95
  | { kind: "beta" } // 内测:能用,不稳定
94
- | { kind: "wip" } // 开发:不可用(可用性由 app 自行处置)
96
+ | { kind: "wip" } // 开发中:仅提示,**不拦点击**
95
97
  | { kind: "custom"; text: string; type?: BadgeProps["type"] }
98
+ | { kind: "anchor" } // 伪标:不渲染角标,只留 DOM 挂点供引导定位
96
99
  );
97
100
  ```
98
101
 
99
102
  内置五档的文案与语义色由 core 统一定,保证跨 app 一致;`custom` 只在内置档不够用时用。
100
103
 
104
+ **`version` 的用处**:消费方(如 `forge-plugin-guide`)把已读记录的 key 取成 `key@version`
105
+ ⇒ **功能有实质更新时改一下版本号,引导 / 提示就对所有人重新出现一次**;不改则关过的人不再被打扰。
106
+ 不填时退化为裸 `key`。与 `expireAt`(时效)、`bizKey`(业务标识)三者正交,
107
+ [MUST NOT] 拿它做灰度 / 权限判断。
108
+
101
109
  `custom.type` 取 element-plus 的语义色档(`primary` / `success` / `warning` / `danger` / `info`),随主题亮暗自适配。[MUST NOT] 传色值字面量——规则 14,写死色切主题不响应。
102
110
 
103
111
  ### API
@@ -124,10 +132,27 @@ type BadgeEntry = {
124
132
 
125
133
  | 消费点 | key 取值 |
126
134
  | --- | --- |
127
- | 菜单项(AppSidebar 菜单树 / AppHeader 模块条) | **路由 `path`**,无需另配 |
135
+ | 菜单项(AppSidebar 菜单树 / AppHeader 模块条) | **路由 `name` 优先、`path` 兜底**(两者都查,先命中先用),无需另配 |
128
136
  | `ActionBtn` | 显式 `badgeKey`(按钮没有 path) |
129
137
  | `TabsMain` 的 tab 标题(tab-bar / tile 卡片 / tile 角落 nav 三处同步) | `TabsMainItem.badgeKey`。[MUST NOT] 复用 `item.key`——那是**局部唯一**(v-model 值 + 槽名),跨页会串标 |
130
138
 
139
+ #### 菜单侧为什么 `name` 优先
140
+
141
+ 看标的**数据来源**:有 `expireAt` 的静态标(随版本写死的 NEW)在前端代码里写,用什么 key 都行;
142
+ 而**无 `expireAt` 的动态标(待办数 / 未开通)由服务端下发、app 轮询 `setAll`** —— 那才是主场景,
143
+ 而**服务端不该知道前端的 URL 结构**。`path` 是展示层产物:加一层路由前缀、把模块挪一级、
144
+ 路径国际化,服务端那张 key 表就整体失效,且**静默失效**(标不显示而已,不报错、不告警)。
145
+ `name` 是路由的逻辑标识,正是为这种跨层引用存在。
146
+
147
+ `path` [MUST NOT] 拿掉:`name` 在 vue-router 里是**可选的**,没配 name 的路由拿不到 key
148
+ 就直接挂不上标;留 path 兜底也让本能力天然 additive —— 现有以 path 注册的一行都不用动。
149
+
150
+ key 空间不会串:`name` 惯例是 camelCase、`path` 必带 `/`。
151
+ [MUST NOT] 为此引入「带前缀区分类型」之类的 key 编码 —— 那是给零概率问题上税。
152
+
153
+ ⚠️ vue-router 的 `RouteRecordName = string | symbol`,**symbol 不能当 key**;
154
+ `bridge.getRoutes()` 的解析期已过滤(`typeof name === "string" ? name : undefined`)。
155
+
131
156
  ### `description`:让这张表可被 agent 读
132
157
 
133
158
  `description` 是 **core 完全不消费**的纯透传字段,供应用层做「本期上了什么功能」这类 AI 对话时,让模型知道某个 key 指向什么、怎么用、什么时候还不该用。
@@ -62,7 +62,7 @@
62
62
  | `contentCentered` | `boolean` | `false` | 默认 slot 内容限宽居中(超宽屏适配);同步收窄对后代公布的内容几何(见下) |
63
63
  | `contentMaxWidth` | `number` | `1200` | 限宽值(px),仅 `contentCentered` 开启时生效 |
64
64
  | `gap` | `number` | `8` | 间距基数 px(缺省读 bridge `APP_LAYOUT_GAP_CONFIG.size`)。**槽 ↔ 内容**与**槽 ↔ 槽**都取由它派生的「外圈档」(`基数 × 2`,随密度缩放),两处同式同源 |
65
- | `topBg` / `bottomBg` / `leftBg` / `rightBg` | `string` | `var(--el-bg-color)`(面色) | 各槽 shim 背景;传 `"transparent"` 可关 |
65
+ | `topBg` / `bottomBg` / `leftBg` / `rightBg` | `string` | `var(--el-bg-color)`(面色) | 各槽 shim 背景;传 `"transparent"` 可关。**shim 自带圆角**(`--dc-core-radius`,随主题;无 bg 的槽透明、圆角无视觉影响) |
66
66
 
67
67
  > **五个背景 prop 的契约(分两组,判据是「块」还是「容器」)**
68
68
  >
@@ -108,8 +108,31 @@ bridge.badge.set("/order/list", { kind: "new" });
108
108
  | `new` | 新 | `success` 绿 | 新功能上线是**正面资讯**(Atlassian success 档即覆盖 "added")。[MUST NOT] 复用红——会与 `count` 撞色,扫视时分不清「有新东西」和「有待办」 |
109
109
  | `updated` | 更新 | `primary` 主色 | 中性被动通知,不喧宾夺主 |
110
110
  | `beta` | 内测 | `warning` 橙 | 能用但**不稳定需谨慎**,正是 warning 语义 |
111
- | `wip` | 开发 | `info` 灰 | 不可用,中性、不引导点击 |
111
+ | `wip` | 开发 | `info` 灰 | 尚不完善,中性、不引导点击。**仅提示,不拦点击** |
112
112
  | `custom` | 自定 | 缺省 `primary` | 内置档不够用时才用;`type` 只收语义档 |
113
+ | `anchor` | —— | —— | **伪标 / 纯锚点**:不渲染角标,只留 DOM 挂点供引导等消费方定位 |
114
+
115
+ ### `anchor`:只要挂点、不要角标
116
+
117
+ 想给某个菜单项 / 按钮做新手引导,但它本身没有「新 / 更新」之类的标要显示时用它。
118
+ [MUST NOT] 为此硬挂一个 `new` —— 那会在界面上多出一个用户看不懂的角标。
119
+
120
+ 🔴 实现上走 `ElBadge` 的 `hidden`(藏角标、留挂点),**[MUST NOT] 改成「不渲染」**:
121
+ 本组件是 `v-if="resolved"`,不渲染会连 `data-dc-badge-key` 一起没掉,
122
+ 消费方反而**定位不到**,与这档的用途正好相反。
123
+
124
+ ## 遮挡:只有 `locked` 一档拦点击
125
+
126
+ 挂 `locked` 的宿主会被**铺一层透明遮罩吃掉点击**,同时打 `tabindex="-1"` +
127
+ `aria-disabled="true"`(移出 Tab 序列,但读屏仍能读到该项并报告为不可用 ——
128
+ 「未开通」是要让用户知道存在且可申请的功能,不是要藏起来)。
129
+
130
+ **其余全部 kind 照常可点**,含 `wip`:**开发中 ≠ 不可达** —— 半成品页面让人点进去看是
131
+ 正常预期,而未开通是**权限边界**,才必须拦。
132
+
133
+ 点击被拦时走 `entry.onTrigger`;未配则出默认提示「请联系平台开通功能」。
134
+ ⚠️ `onTrigger` 只在**拦得住**时才有触发点 ⇒ 给 `wip` 配它不会被调用。
135
+ 要拦某项就改挂 `locked`,[MUST NOT] 指望用 `onTrigger` 把可点档变成不可点。
113
136
 
114
137
  ## 反模式 / 注意
115
138
 
@@ -103,7 +103,7 @@ watch(isOutOfSync, (v) => v && refreshList());
103
103
  | `autoActiveFirst` | `boolean` | 跟随形态 | 列表出数后自动激活第一条。**split 默认 `true`**(宽屏右半屏不该空着)、**sheet 默认 `false`**(窄屏自动弹抽屉是打扰)。⚠️ 详情若有副作用(如「打开即标记已读」)[MUST] 显式关掉 |
104
104
  | `queryKey` | `string` | 无 | 传了才把 `activeId` 同步进 URL query(**`replace` 不 `push`**)。复原仍走不变式校验 |
105
105
  | `detailApi` | `(params?: { id }) => Promise<R>` | 无 | 传了则内置 `ViewLayout`(零配置拿到内置导航 + `refreshToken` 接线);不传则 `#detail` 原样渲染、门面零请求 |
106
- | `detailTitle` | `string \| ((detailData, record) => string)` | 无 | 详情区标题。字符串固定 / 函数按数据算:`detailData` = 详情接口数据(**未传 `detailApi` 时恒 `undefined`**)、`record` = 当前项的列表 item(「详情即列表数据」形态靠它出标题)。⚠️ 渲染在**详情区顶部**(两形态一致),**不是** sheet 弹层的 header —— 详情数据只在详情区内部可得,弹层那层拿不到。不传则不渲染,业务照旧可在 `#detail` 自绘 |
106
+ | `detailTitle` | `string \| ((detailData, record) => string)` | 无 | 详情区标题。字符串固定 / 函数按数据算:`detailData` = 详情接口数据(**未传 `detailApi` 时恒 `undefined`**)、`record` = 当前项的列表 item(「详情即列表数据」形态靠它出标题)。⚠️ **两形态落点不同**(v0.28.0 ⓤⓤ②:标题一律归「最外层容器的标题位」):split 档渲染在**详情区顶部**;sheet 档渲染进**弹层自己的 header**(不再重复出现在正文里)。由此 sheet 档的函数形态**只拿得到 `record`、`detailData` 恒 `undefined`**(详情数据在 `ViewLayout` 内部,弹层壳那层拿不到)——需要详情数据驱动的标题,[MUST] 改在 `#detail` 内自绘。不传则不渲染,业务照旧可在 `#detail` 自绘 |
107
107
  | `viewLayoutProps` | `Omit<ViewLayoutProps, "api" \| "params" \| "refreshToken">` | 无 | 透传给内置 `ViewLayout` 的 props——**详情侧的业务操作按钮走这里**:`{ leftExtraButtons: [...] }` 落到 ViewLayout 工具条左侧、与右侧刷新按钮组同一行(`rightExtraButtons` 则置于 `AutoRefreshGroup` 之前)。🔴 `api` / `params` / `refreshToken` 三键被 `Omit`:它们分别由 `detailApi` / `{ id: activeId }` / `activeId` 占位,是「详情跟着当前项走」这条不变式的载体,放开任一个都能让主干**静默失效**(页面看着正常,详情停在上一条)。**未传 `detailApi` 时无内置 ViewLayout,本项无落点** |
108
108
  | `sheetLayerType` | `"drawer" \| "modal"` | `"drawer"` | sheet 形态的弹层承载:抽屉 / 居中弹窗。透传 `ModalConfirm.layerType` |
109
109
  | `sheetSize` | `string \| number` | `"70%"` | sheet 弹层尺寸(透传 `ModalConfirm.width`):drawer 下是抽屉尺寸、modal 下是弹窗宽度 |
@@ -75,8 +75,14 @@
75
75
  「内容的第一行」而非「容器的标题位」——位置与 `ListLayout` 对不齐,主题也无从定向。
76
76
  `AppPageDetail` 原先即如此,已归位(见 migrations)。
77
77
 
78
- 样式口径同 `ListLayout` header:**露底不成面**(两卡模型里 header 不是卡),
79
- 只给字号 / 字重 / 外距,[MUST NOT] 加背景与描边。
78
+ 标题本身只给字号 / 字重,[MUST NOT] 自带背景与描边 —— 它坐在 toolbar 这条**分区条**里,
79
+ 底色由条统一给。
80
+
81
+ **toolbar 是「条」(bar) 不是「卡」(card)**(甲方 2026-09-16 定性):底色取「面与底的中点」
82
+ (`color-mix(in srgb, --dc-core-surface 50%, --dc-core-ground)`,亮暗自动跟随),
83
+ 但守三条纪律 —— **贴边**(零左右呼吸,否则按钮与下方内容区左边界错位,而内容区的面由
84
+ 消费方自持、padding 不受本组件控制)· **通栏**(零圆角)· **不加高**(只留既有的 8px 下呼吸)。
85
+ 此前「工具条成卡」被否的根因不是「有面」,而是面顺带塞进 12px 内呼吸把 32px 的条撑到 56px。
80
86
 
81
87
  ## API
82
88
  > ⚠️ API 以 types.ts 与组件源码为真相源;与文档冲突时以源码为准。
@@ -53,12 +53,12 @@ export type BadgeEntry = {
53
53
  /**
54
54
  * 被遮挡的标被触发时的回调(点宿主 / 点角标都算触发)。
55
55
  *
56
- * **[MUST] 只有 `wip` / `locked` 两档会调用它**——只有这两档会遮挡宿主交互,
57
- * 其余 kind 是纯展示、宿主照常可点,没有"被拦下的那次点击"可供回调。
58
- * 字段放在共享段(而非收窄进那两个 kind)是甲方口径:所有 kind 都可配、非必传。
56
+ * **[MUST] 只有 `locked` 一档会调用它**——只有这一档会遮挡宿主交互,
57
+ * 其余 kind(含 `wip`)是纯展示、宿主照常可点,没有"被拦下的那次点击"可供回调。
58
+ * 字段放在共享段(而非收窄进 `locked`)是甲方口径:所有 kind 都可配、非必传。
59
59
  *
60
- * 缺省时由 core 出默认提示(`locked` →「请联系平台开通功能」/ `wip` →
61
- * 「功能正在加紧开发中」)。要改文案就自己配本回调去弹,core [MUST NOT] 再开覆盖字段。
60
+ * 缺省时由 core 出默认提示(`locked` →「请联系平台开通功能」)。
61
+ * 要改文案就自己配本回调去弹,core [MUST NOT] 再开覆盖字段。
62
62
  */
63
63
  onTrigger?: (entry: BadgeEntry) => void;
64
64
  /**
@@ -72,6 +72,23 @@ export type BadgeEntry = {
72
72
  * 聚合与数据来源全在 app(同本文件开头的分工)。
73
73
  */
74
74
  bizKey?: string;
75
+ /**
76
+ * 本条标的版本号——**core 不消费、纯透传**,供消费方做「版本化的已读记录」。
77
+ *
78
+ * 典型用法(`forge-plugin-guide`):引导弹窗被用户关掉后记一条「看过了」,
79
+ * 进度 key 取 **`key@version`**;不填版本时退化为裸 `key`。
80
+ * ⇒ **功能有实质更新时改一下版本号,引导就会对所有人重新出现一次**,
81
+ * 而不改版本则关过的人永远不再被打扰。
82
+ *
83
+ * [MUST NOT] 拿它做灰度 / 权限判断——它只是「这条标的第几版」,
84
+ * 与 {@link BadgeEntry.expireAt}(时效)、{@link BadgeEntry.bizKey}(业务标识)三者正交。
85
+ *
86
+ * 收 `number` 是刻意的:后端常直接发数字版本,强制消费方转字符串是无谓摩擦;
87
+ * 拼 key 时统一走模板串,`1` 与 `"1"` 拼出同一个 key、无歧义。
88
+ *
89
+ * @example { kind: "new", description: "批量导出…", version: 2 }
90
+ */
91
+ version?: string | number;
75
92
  } & ({
76
93
  /** 数字角标 */
77
94
  kind: "count";
@@ -91,6 +108,20 @@ export type BadgeEntry = {
91
108
  kind: "deprecated";
92
109
  } | {
93
110
  kind: "locked";
111
+ }
112
+ /**
113
+ * **伪标 / 纯锚点**:不渲染任何角标,只在 DOM 上留一个带 key 的挂点,
114
+ * 供 `forge-plugin-guide` 这类消费方**定位**到这个宿主。
115
+ *
116
+ * 用途:想给某个菜单项 / 按钮做新手引导,但它本身**没有**「新 / 更新」之类的标要显示。
117
+ * [MUST NOT] 为此硬挂一个 `new` 标——那会在界面上多出一个用户看不懂的角标。
118
+ *
119
+ * 🔴 它**不进自动串**:guide 的 `badgeKinds` 默认只收资讯档(`new` / `updated`),
120
+ * 本档要由**显式 `steps`** 用 `badgeKey` 引用才会成为一步 —— 这正是它的定位,
121
+ * 「我只提供锚点,跑不跑、什么时候跑由编排决定」。
122
+ */
123
+ | {
124
+ kind: "anchor";
94
125
  } | {
95
126
  /** 自定义标:内置五档不够用时才用 */
96
127
  kind: "custom";
@@ -104,8 +135,14 @@ export type BadgeEntry = {
104
135
  });
105
136
  /** 内置标(非 count / custom)的 kind 联合——文案与语义色由 core 统一定,保证跨 app 一致 */
106
137
  export type BadgeBuiltinKind = "new" | "updated" | "beta" | "wip" | "deprecated" | "locked";
107
- /** 会遮挡宿主交互的 kind——[MUST] 与 `use-badge-mask` 的判据同源,[MUST NOT] 两处各写一份 */
108
- export declare const BADGE_MASK_KINDS: readonly ["wip", "locked"];
138
+ /**
139
+ * 会遮挡宿主交互的 kind——[MUST] `use-badge-mask` 的判据同源,[MUST NOT] 两处各写一份。
140
+ *
141
+ * 🔴 **只有 `locked` 一档**(甲方 2026-09-16:「我期望只有未开通才点不进去」):
142
+ * `wip`「开发中」**不拦** —— 开发中 ≠ 不可达,半成品页面让人点进去看是正常预期;
143
+ * 未开通是**权限边界**,才必须拦。要拦某项就改挂 `locked`。
144
+ */
145
+ export declare const BADGE_MASK_KINDS: readonly ["locked"];
109
146
  /** bridge 徽标 hook 工厂入参 */
110
147
  export interface BridgeBadgeInit {
111
148
  /** 初始标记表(如随版本发布的静态 NEW 标);缺省空表 */
@@ -82,7 +82,14 @@ export declare function computeAppPageSlotStyles(rect: AppPageRectInset, slotSiz
82
82
  * - **朝内容侧**:间隔改由**默认槽**让出(见 {@link appPageSlotInset}),
83
83
  * 槽这边 [MUST] 归零,否则两边各让一份 = 双份。
84
84
  *
85
- * 保留本函数而非删掉挂点:槽内层 shim 仍是布局盒(`left/right` 靠它填满竖轨),
86
- * 且留一个显式的「四边为 0 是刻意的」出口,比让下一个人从空 div 里猜要好。
85
+ * 保留本函数而非删掉挂点:槽内层 shim 仍是布局盒(`left/right` 靠它填满竖轨)。
86
+ *
87
+ * 🔴 **2026-09-16 起改为四边 `8px`**(甲方定):槽拿到自己的背景色与圆角之后,它已经是
88
+ * 一个**有形的块**而非纯占位轨 —— 内容贴着块边缘(还会被圆角切角)不成样子,故给块内呼吸。
89
+ *
90
+ * ⚠️ 这**不构成上文的「双份」**:让位量取槽的**实测尺寸**(`clientWidth/Height` 含 padding),
91
+ * 槽变胖多少默认内容就多让多少 ⇒「槽边缘 ↔ 默认内容」恒为一个外圈档不变,新增的 8px 落在
92
+ * **槽内**(槽边缘 ↔ 槽内内容)。上文「朝内容侧 [MUST] 归零」针对的是旧式做法 ——
93
+ * 那时槽无背景、padding 直接充当间隔,才会与让位叠成双份。
87
94
  */
88
95
  export declare function computeAppPageSlotShimStyles(): AppPageSlotStyles;