@done-coding/admin-core 0.24.0 → 0.25.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 +27 -0
  2. package/es/bridge/index.mjs +4 -0
  3. package/es/components/app-layout/AppHeader.vue.mjs +1 -1
  4. package/es/components/app-layout/AppHeader.vue2.mjs +13 -3
  5. package/es/components/app-layout/AppLayout.vue.mjs +1 -1
  6. package/es/components/app-layout/AppLayout.vue2.mjs +6 -2
  7. package/es/components/display/ActionBtn.vue.mjs +21 -3
  8. package/es/components/display/BadgeMark.vue.mjs +7 -0
  9. package/es/components/display/BadgeMark.vue2.mjs +31 -0
  10. package/es/components/display/TabsHeader.vue.mjs +1 -1
  11. package/es/components/display/TabsHeader.vue2.mjs +16 -7
  12. package/es/components/display/TabsTile.vue.mjs +1 -1
  13. package/es/components/display/TabsTile.vue2.mjs +27 -7
  14. package/es/components/display/use-badge.mjs +62 -0
  15. package/es/components/menu/MenuItemSub.vue.mjs +22 -3
  16. package/es/components/menu/MenuTree.vue.mjs +34 -8
  17. package/es/index.mjs +62 -60
  18. package/es/store/app.mjs +16 -4
  19. package/es/style.css +61 -46
  20. package/package.json +2 -2
  21. package/src/bridge/README.md +1 -1
  22. package/src/bridge/docs/README.md +98 -2
  23. package/src/components/app-layout/README.md +1 -1
  24. package/src/components/app-layout/docs/README-AppHeader.md +8 -0
  25. package/src/components/app-layout/docs/README-AppLayout.md +24 -0
  26. package/src/components/display/README.md +7 -4
  27. package/src/components/display/docs/README-ActionBtn.md +1 -0
  28. package/src/components/display/docs/README-BadgeMark.md +125 -0
  29. package/src/components/display/docs/README-TabsMain.md +20 -1
  30. package/src/components/menu/README.md +8 -0
  31. package/src/components/menu/docs/README-MenuItemSub.md +4 -0
  32. package/src/components/menu/docs/README-MenuTree.md +10 -0
  33. package/types/bridge/badge.d.ts +112 -0
  34. package/types/bridge/index.d.ts +18 -0
  35. package/types/components/display/BadgeMark.vue.d.ts +57 -0
  36. package/types/components/display/index.d.ts +7 -0
  37. package/types/components/display/types.d.ts +18 -0
  38. package/types/components/display/use-badge.d.ts +19 -0
  39. package/types/injectInfo.json.d.ts +1 -1
  40. package/types/store/app.d.ts +22 -1
  41. package/types/types/route.d.ts +20 -0
package/es/index.mjs CHANGED
@@ -43,42 +43,43 @@ import { default as default40 } from "./components/app-layout/AppSidebar.vue.mjs
43
43
  import { default as default41 } from "./components/app-layout/AppTheme.vue.mjs";
44
44
  import { default as default42 } from "./components/misc/AutoRefresh.vue.mjs";
45
45
  import { default as default43 } from "./components/misc/AutoRefreshGroup.vue.mjs";
46
+ import { default as default44 } from "./components/display/BadgeMark.vue.mjs";
46
47
  import { CORE_VIEWPORT_DECOR_ZINDEX_ABOVE, CORE_VIEWPORT_DECOR_ZINDEX_BELOW } from "./components/app-layout/viewport.mjs";
47
- import { default as default44, default as default45 } from "./components/modal/ModalConfirm.vue.mjs";
48
+ import { default as default45, default as default46 } from "./components/modal/ModalConfirm.vue.mjs";
48
49
  import { DC_SLOT_ATTR, SlotRegion, parseSlotRegion } from "./config/slot-region.mjs";
49
- import { default as default46 } from "./components/data-view/DataGridView.vue.mjs";
50
+ import { default as default47 } from "./components/data-view/DataGridView.vue.mjs";
50
51
  import { EXPORT_MAX_LIMIT, OPERATE_COLUMN_PROP, exportCSV, pickExportColumns } from "./utils/export.mjs";
51
52
  import { FORM_CONFIG_SELECT_ALL_VALUE, getConfirmPasswordRule, passwordRule, setFormComponentType } from "./helpers/form.mjs";
52
53
  import { FORM_ITEM_CHANGE_LOADING, NEST_LAYOUT_TIERS, ceilToTier, generateFormData, getBlurSubmit, getChangeSubmit, getDatePickerShortcuts, getEnterSubmit, getPlaceholder, getVModelSugar, parseFormData, resolveFormLayout, resolveLayoutSpan, setInputComponent, setSelectComponent, stringifyFormData, swiftFormItemConfig } from "./components/form/utils.mjs";
53
- import { default as default47 } from "./components/form/FormActiveFilter.vue.mjs";
54
- import { default as default48 } from "./components/form/FormItemNestForm.vue.mjs";
55
- import { default as default49 } from "./components/form/FormItemNestFormList.vue.mjs";
56
- import { default as default50 } from "./components/form/FormMain.vue.mjs";
57
- import { default as default51 } from "./components/form/FormSearch.vue.mjs";
58
- import { default as default52 } from "./components/form/FormShadowCloneDispatch.vue.mjs";
59
- import { default as default53 } from "./components/form/FormSortSwitch.vue.mjs";
60
- import { default as default54 } from "./components/form/FormSortSwitchGroup.vue.mjs";
61
- import { default as default55 } from "./components/form/FormSubmitPanel.vue.mjs";
62
- import { default as default56 } from "./components/form/FormUpload.vue.mjs";
63
- import { default as default57 } from "./components/form/FormUploadAudio.vue.mjs";
64
- import { default as default58 } from "./components/form/FormUploadImage.vue.mjs";
65
- import { default as default59 } from "./components/form/FormUploadVideo.vue.mjs";
66
- import { default as default60 } from "./components/form/FormVerifyImage.vue.mjs";
67
- import { default as default61 } from "./components/data-view/InfiniteListView.vue.mjs";
68
- import { default as default62, default as default63 } from "./components/list-layout/ListLayout.vue.mjs";
69
- import { default as default64 } from "./components/modal/ModalVideoPreview.vue.mjs";
70
- import { default as default65 } from "./components/panel/PanelEditSwitch.vue.mjs";
71
- import { default as default66 } from "./components/panel/PanelItem.vue.mjs";
72
- import { default as default67 } from "./components/panel/PanelItemNestForm.vue.mjs";
73
- import { default as default68 } from "./components/panel/PanelMain.vue.mjs";
54
+ import { default as default48 } from "./components/form/FormActiveFilter.vue.mjs";
55
+ import { default as default49 } from "./components/form/FormItemNestForm.vue.mjs";
56
+ import { default as default50 } from "./components/form/FormItemNestFormList.vue.mjs";
57
+ import { default as default51 } from "./components/form/FormMain.vue.mjs";
58
+ import { default as default52 } from "./components/form/FormSearch.vue.mjs";
59
+ import { default as default53 } from "./components/form/FormShadowCloneDispatch.vue.mjs";
60
+ import { default as default54 } from "./components/form/FormSortSwitch.vue.mjs";
61
+ import { default as default55 } from "./components/form/FormSortSwitchGroup.vue.mjs";
62
+ import { default as default56 } from "./components/form/FormSubmitPanel.vue.mjs";
63
+ import { default as default57 } from "./components/form/FormUpload.vue.mjs";
64
+ import { default as default58 } from "./components/form/FormUploadAudio.vue.mjs";
65
+ import { default as default59 } from "./components/form/FormUploadImage.vue.mjs";
66
+ import { default as default60 } from "./components/form/FormUploadVideo.vue.mjs";
67
+ import { default as default61 } from "./components/form/FormVerifyImage.vue.mjs";
68
+ import { default as default62 } from "./components/data-view/InfiniteListView.vue.mjs";
69
+ import { default as default63, default as default64 } from "./components/list-layout/ListLayout.vue.mjs";
70
+ import { default as default65 } from "./components/modal/ModalVideoPreview.vue.mjs";
71
+ import { default as default66 } from "./components/panel/PanelEditSwitch.vue.mjs";
72
+ import { default as default67 } from "./components/panel/PanelItem.vue.mjs";
73
+ import { default as default68 } from "./components/panel/PanelItemNestForm.vue.mjs";
74
+ import { default as default69 } from "./components/panel/PanelMain.vue.mjs";
74
75
  import { ROUTE_MODULE_LEVEL, TabsMainReplaceQueryKey } from "./config/route.mjs";
75
- import { default as default69 } from "./components/slot-layout/SlotLayoutFlowAside.vue.mjs";
76
+ import { default as default70 } from "./components/slot-layout/SlotLayoutFlowAside.vue.mjs";
76
77
  import { TABLE_COLUMN_KEY_OPERATE, TABLE_COLUMN_KEY_SELECTION } from "./components/table/constants.mjs";
77
- import { default as default70 } from "./components/table/TableMain.vue.mjs";
78
- import { default as default71 } from "./components/table/TableSkeleton.vue.mjs";
79
- import { default as default72 } from "./components/table/TableToolbar.vue.mjs";
80
- import { default as default73 } from "./components/modal/VideoPreviewTrigger.vue.mjs";
81
- import { default as default74 } from "./components/view-layout/ViewLayout.vue.mjs";
78
+ import { default as default71 } from "./components/table/TableMain.vue.mjs";
79
+ import { default as default72 } from "./components/table/TableSkeleton.vue.mjs";
80
+ import { default as default73 } from "./components/table/TableToolbar.vue.mjs";
81
+ import { default as default74 } from "./components/modal/VideoPreviewTrigger.vue.mjs";
82
+ import { default as default75 } from "./components/view-layout/ViewLayout.vue.mjs";
82
83
  import { buildRhythmCss, useCoreThemeApply } from "./hooks/use-theme-apply.mjs";
83
84
  import { checkLogin } from "./router/guard/login.mjs";
84
85
  import { checkPermission } from "./router/guard/permission.mjs";
@@ -139,83 +140,84 @@ export {
139
140
  BODY_CONTENT_VIEWPORT_HEIGHT,
140
141
  BODY_CONTENT_VIEWPORT_INSET,
141
142
  BODY_CONTENT_VIEWPORT_RECT,
143
+ default44 as BadgeMark,
142
144
  default4 as BooleanTag,
143
145
  CORE_BRIDGE_KEY,
144
146
  CORE_VIEWPORT_DECOR_ZINDEX_ABOVE,
145
147
  CORE_VIEWPORT_DECOR_ZINDEX_BELOW,
146
- default44 as ConfirmModal,
148
+ default45 as ConfirmModal,
147
149
  DC_SLOT_ATTR,
148
- default46 as DataGridView,
150
+ default47 as DataGridView,
149
151
  default5 as DetailModal,
150
152
  EXPORT_MAX_LIMIT,
151
153
  FORM_CONFIG_SELECT_ALL_VALUE,
152
154
  FORM_ITEM_CHANGE_LOADING,
153
155
  FORM_NEST_REGISTRY_KEY,
154
- default47 as FormActiveFilter,
156
+ default48 as FormActiveFilter,
155
157
  default7 as FormInput,
156
158
  default8 as FormInputShadowClone,
157
- default48 as FormItemNestForm,
158
- default49 as FormItemNestFormList,
159
- default50 as FormMain,
159
+ default49 as FormItemNestForm,
160
+ default50 as FormItemNestFormList,
161
+ default51 as FormMain,
160
162
  default9 as FormModal,
161
163
  default11 as FormRadioGroup,
162
- default51 as FormSearch,
164
+ default52 as FormSearch,
163
165
  default12 as FormSelect,
164
166
  default13 as FormSelectShadowClone,
165
- default52 as FormShadowCloneDispatch,
166
- default53 as FormSortSwitch,
167
- default54 as FormSortSwitchGroup,
167
+ default53 as FormShadowCloneDispatch,
168
+ default54 as FormSortSwitch,
169
+ default55 as FormSortSwitchGroup,
168
170
  default14 as FormSubmitBtn,
169
- default55 as FormSubmitPanel,
171
+ default56 as FormSubmitPanel,
170
172
  default15 as FormTree,
171
- default56 as FormUpload,
172
- default57 as FormUploadAudio,
173
- default58 as FormUploadImage,
174
- default59 as FormUploadVideo,
173
+ default57 as FormUpload,
174
+ default58 as FormUploadAudio,
175
+ default59 as FormUploadImage,
176
+ default60 as FormUploadVideo,
175
177
  default16 as FormVerifyCode,
176
- default60 as FormVerifyImage,
178
+ default61 as FormVerifyImage,
177
179
  GLOBAL_MODAL_REMOTE_KEY,
178
180
  default17 as HeightProvider,
179
181
  default18 as ImagePreviewTrigger,
180
- default61 as InfiniteListView,
181
- default62 as ListLayout,
182
- default63 as ListPage,
182
+ default62 as InfiniteListView,
183
+ default63 as ListLayout,
184
+ default64 as ListPage,
183
185
  default19 as MenuItemSub,
184
186
  default20 as MenuTree,
185
- default45 as ModalConfirm,
187
+ default46 as ModalConfirm,
186
188
  default6 as ModalDetail,
187
189
  default10 as ModalForm,
188
190
  default21 as ModalImagePreview,
189
191
  default22 as ModalPorter,
190
192
  default23 as ModalShelf,
191
- default64 as ModalVideoPreview,
193
+ default65 as ModalVideoPreview,
192
194
  NEST_LAYOUT_SCALE_KEY,
193
195
  NEST_LAYOUT_TIERS,
194
196
  OPERATE_COLUMN_PROP,
195
- default65 as PanelEditSwitch,
196
- default66 as PanelItem,
197
- default67 as PanelItemNestForm,
197
+ default66 as PanelEditSwitch,
198
+ default67 as PanelItem,
199
+ default68 as PanelItemNestForm,
198
200
  default24 as PanelItemNestPanel,
199
- default68 as PanelMain,
201
+ default69 as PanelMain,
200
202
  ROUTE_MODULE_LEVEL,
201
203
  SCOPE_VIEWPORT_MAX_HEIGHT,
202
204
  default25 as SelectModule,
203
205
  default26 as ShadowClone,
204
- default69 as SlotLayoutFlowAside,
206
+ default70 as SlotLayoutFlowAside,
205
207
  default27 as SlotLayoutTemplate,
206
208
  SlotRegion,
207
209
  TABLE_COLUMN_KEY_OPERATE,
208
210
  TABLE_COLUMN_KEY_SELECTION,
209
211
  TABS_MAIN_VISUAL_LEVEL,
210
212
  default28 as TableEmpty,
211
- default70 as TableMain,
212
- default71 as TableSkeleton,
213
- default72 as TableToolbar,
213
+ default71 as TableMain,
214
+ default72 as TableSkeleton,
215
+ default73 as TableToolbar,
214
216
  default29 as TabsMain,
215
217
  TabsMainReplaceQueryKey,
216
218
  default30 as TriggerAutoImport,
217
- default73 as VideoPreviewTrigger,
218
- default74 as ViewLayout,
219
+ default74 as VideoPreviewTrigger,
220
+ default75 as ViewLayout,
219
221
  default31 as WatchSize,
220
222
  buildRhythmCss,
221
223
  ceilToTier,
package/es/store/app.mjs CHANGED
@@ -62,12 +62,22 @@ function createAppStore(deps) {
62
62
  return ((_a = pageLayout.value) == null ? void 0 : _a.bodyShimPadding) ?? bridge.APP_LAYOUT_BODY_CONFIG.shimPadding;
63
63
  }
64
64
  );
65
+ const viewportConfigEffective = computed(
66
+ () => {
67
+ var _a;
68
+ return ((_a = pageLayout.value) == null ? void 0 : _a.noViewportPadding) ? { paddingTop: 0, paddingRight: 0, paddingBottom: 0, paddingLeft: 0 } : bridge.APP_LAYOUT_VIEWPORT_CONFIG;
69
+ }
70
+ );
65
71
  const viewportPadding = computed(() => ({
66
- top: `${bridge.APP_LAYOUT_VIEWPORT_CONFIG.paddingTop}px`,
67
- right: `${bridge.APP_LAYOUT_VIEWPORT_CONFIG.paddingRight}px`,
68
- bottom: `${bridge.APP_LAYOUT_VIEWPORT_CONFIG.paddingBottom}px`,
69
- left: `${bridge.APP_LAYOUT_VIEWPORT_CONFIG.paddingLeft}px`
72
+ top: `${viewportConfigEffective.value.paddingTop}px`,
73
+ right: `${viewportConfigEffective.value.paddingRight}px`,
74
+ bottom: `${viewportConfigEffective.value.paddingBottom}px`,
75
+ left: `${viewportConfigEffective.value.paddingLeft}px`
70
76
  }));
77
+ const noAnimation = computed(() => {
78
+ var _a;
79
+ return ((_a = pageLayout.value) == null ? void 0 : _a.noAnimation) ?? false;
80
+ });
71
81
  const gapV = computed(() => bridge.APP_LAYOUT_GAP_CONFIG.size);
72
82
  const gpx = computed(() => `${gapV.value}px`);
73
83
  const headerStyle = computed(() => {
@@ -194,6 +204,8 @@ function createAppStore(deps) {
194
204
  bodyShimPadding,
195
205
  bodyShimStyle,
196
206
  viewportPadding,
207
+ viewportConfigEffective,
208
+ noAnimation,
197
209
  // 折叠态读 bridge 真相(架构反转,名字保留向后兼容);aside 对称新增。
198
210
  // sidebar 侧经 sidebarCollapsedEffective 单点(分组形态闸为 false,见其注释)。
199
211
  sidebarIsCollapse: sidebarCollapsedEffective,
package/es/style.css CHANGED
@@ -63,6 +63,13 @@
63
63
  justify-content: flex-end;
64
64
  gap: 8px;
65
65
  }
66
+ .badge-mark[data-v-12868883] .el-badge__content.is-fixed {
67
+ right: 0;
68
+ transform: translateY(-50%) translateX(calc(100% + 2px));
69
+ }
70
+ .badge-mark_middle[data-v-12868883] .el-badge__content.is-fixed {
71
+ top: 50%;
72
+ }
66
73
  .form-item-tip[data-v-8b508d64] {
67
74
  text-align: left;
68
75
  line-height: 1.5;
@@ -220,7 +227,7 @@
220
227
  height: 100%;
221
228
  background-color: #f4f4f4;
222
229
  }
223
- .tabs-main-bar[data-v-9d365b46] {
230
+ .tabs-main-bar[data-v-1f3b9f99] {
224
231
  position: relative;
225
232
  display: flex;
226
233
  align-items: center;
@@ -230,37 +237,37 @@
230
237
  /* ============ Variant: indicator-compact —— 紧凑 1px 下划线 ============ */
231
238
  /* ============ Variant: plain —— 无装饰(active 加粗 + primary 字色) ============ */
232
239
  }
233
- .tabs-main-bar-item[data-v-9d365b46] {
240
+ .tabs-main-bar-item[data-v-1f3b9f99] {
234
241
  border: none;
235
242
  background: transparent;
236
243
  cursor: pointer;
237
244
  color: var(--el-text-color-regular);
238
245
  transition: background 0.2s, color 0.2s, box-shadow 0.2s;
239
246
  }
240
- .tabs-main-bar-indicator[data-v-9d365b46] {
247
+ .tabs-main-bar-indicator[data-v-1f3b9f99] {
241
248
  position: absolute;
242
249
  left: 0;
243
250
  background: var(--dc-core-primary-color);
244
251
  transition: transform 0.2s, width 0.2s;
245
252
  }
246
- .tabs-main-bar--l1 .tabs-main-bar-item[data-v-9d365b46] {
253
+ .tabs-main-bar--l1 .tabs-main-bar-item[data-v-1f3b9f99] {
247
254
  font-size: 16px;
248
255
  }
249
- .tabs-main-bar--l2 .tabs-main-bar-item[data-v-9d365b46] {
256
+ .tabs-main-bar--l2 .tabs-main-bar-item[data-v-1f3b9f99] {
250
257
  font-size: 14px;
251
258
  }
252
- .tabs-main-bar--l3 .tabs-main-bar-item[data-v-9d365b46] {
259
+ .tabs-main-bar--l3 .tabs-main-bar-item[data-v-1f3b9f99] {
253
260
  font-size: 12px;
254
261
  }
255
- .tabs-main-bar--l4 .tabs-main-bar-item[data-v-9d365b46] {
262
+ .tabs-main-bar--l4 .tabs-main-bar-item[data-v-1f3b9f99] {
256
263
  font-size: 12px;
257
264
  }
258
- .tabs-main-bar--variant-card[data-v-9d365b46] {
265
+ .tabs-main-bar--variant-card[data-v-1f3b9f99] {
259
266
  position: relative;
260
267
  overflow: hidden;
261
268
  gap: 2px;
262
269
  }
263
- .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-9d365b46] {
270
+ .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-1f3b9f99] {
264
271
  position: relative;
265
272
  padding: 8px 20px;
266
273
  line-height: 1.4;
@@ -268,17 +275,17 @@
268
275
  border-bottom: 0;
269
276
  border-radius: 6px 6px 0 0;
270
277
  }
271
- .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-9d365b46]:hover:not(.tabs-main-bar-item_active) {
278
+ .tabs-main-bar--variant-card .tabs-main-bar-item[data-v-1f3b9f99]:hover:not(.tabs-main-bar-item_active) {
272
279
  color: var(--dc-core-primary-color);
273
280
  }
274
- .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-9d365b46] {
281
+ .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-1f3b9f99] {
275
282
  color: var(--dc-core-primary-color);
276
283
  border-color: var(--el-border-color-light);
277
284
  /* active item 两侧各延伸 100vw 灰线,共同模拟"全宽 separator 在 active item 处中断"
278
285
  active item 自身宽度内无线 → 视觉打通下方 content,完全 bg 无关 */
279
286
  }
280
- .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-9d365b46]::before,
281
- .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-9d365b46]::after {
287
+ .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-1f3b9f99]::before,
288
+ .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-1f3b9f99]::after {
282
289
  content: "";
283
290
  position: absolute;
284
291
  bottom: 0;
@@ -286,58 +293,58 @@
286
293
  height: 1px;
287
294
  background: var(--el-border-color-light);
288
295
  }
289
- .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-9d365b46]::before {
296
+ .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-1f3b9f99]::before {
290
297
  right: 100%;
291
298
  /* 从 active item 左缘向左延伸 */
292
299
  }
293
- .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-9d365b46]::after {
300
+ .tabs-main-bar--variant-card .tabs-main-bar-item_active[data-v-1f3b9f99]::after {
294
301
  left: 100%;
295
302
  /* 从 active item 右缘向右延伸 */
296
303
  }
297
- .tabs-main-bar--variant-indicator .tabs-main-bar-item[data-v-9d365b46] {
304
+ .tabs-main-bar--variant-indicator .tabs-main-bar-item[data-v-1f3b9f99] {
298
305
  padding: 8px 16px;
299
306
  }
300
- .tabs-main-bar--variant-indicator .tabs-main-bar-item_active[data-v-9d365b46] {
307
+ .tabs-main-bar--variant-indicator .tabs-main-bar-item_active[data-v-1f3b9f99] {
301
308
  color: var(--dc-core-primary-color);
302
309
  }
303
- .tabs-main-bar--variant-indicator .tabs-main-bar-indicator[data-v-9d365b46] {
310
+ .tabs-main-bar--variant-indicator .tabs-main-bar-indicator[data-v-1f3b9f99] {
304
311
  bottom: 0;
305
312
  height: 2px;
306
313
  }
307
- .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item[data-v-9d365b46] {
314
+ .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item[data-v-1f3b9f99] {
308
315
  padding: 6px 10px;
309
316
  }
310
- .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item_active[data-v-9d365b46] {
317
+ .tabs-main-bar--variant-indicator-compact .tabs-main-bar-item_active[data-v-1f3b9f99] {
311
318
  color: var(--dc-core-primary-color);
312
319
  }
313
- .tabs-main-bar--variant-indicator-compact .tabs-main-bar-indicator[data-v-9d365b46] {
320
+ .tabs-main-bar--variant-indicator-compact .tabs-main-bar-indicator[data-v-1f3b9f99] {
314
321
  bottom: 0;
315
322
  height: 1px;
316
323
  }
317
- .tabs-main-bar--variant-plain .tabs-main-bar-item[data-v-9d365b46] {
324
+ .tabs-main-bar--variant-plain .tabs-main-bar-item[data-v-1f3b9f99] {
318
325
  padding: 4px 8px;
319
326
  }
320
- .tabs-main-bar--variant-plain .tabs-main-bar-item_active[data-v-9d365b46] {
327
+ .tabs-main-bar--variant-plain .tabs-main-bar-item_active[data-v-1f3b9f99] {
321
328
  color: var(--dc-core-primary-color);
322
329
  font-weight: 600;
323
330
  }
324
- .dc-tabs-tile--nav-absolute[data-v-d9f0f521],
325
- .dc-tabs-tile--nav-fixed[data-v-d9f0f521] {
331
+ .dc-tabs-tile--nav-absolute[data-v-acb4bb13],
332
+ .dc-tabs-tile--nav-fixed[data-v-acb4bb13] {
326
333
  position: relative;
327
334
  }
328
- .dc-tabs-tile--nav-sticky[data-v-d9f0f521] {
335
+ .dc-tabs-tile--nav-sticky[data-v-acb4bb13] {
329
336
  display: flex;
330
337
  flex-direction: column;
331
338
  }
332
- .dc-tabs-tile__scroll[data-v-d9f0f521] {
339
+ .dc-tabs-tile__scroll[data-v-acb4bb13] {
333
340
  display: flex;
334
341
  flex-direction: column;
335
342
  gap: 12px;
336
343
  }
337
- .dc-tabs-tile__card[data-v-d9f0f521] {
344
+ .dc-tabs-tile__card[data-v-acb4bb13] {
338
345
  width: 100%;
339
346
  }
340
- .dc-tabs-tile__nav[data-v-d9f0f521] {
347
+ .dc-tabs-tile__nav[data-v-acb4bb13] {
341
348
  z-index: 2;
342
349
  max-height: 60%;
343
350
  overflow: auto;
@@ -442,7 +449,7 @@
442
449
  height: 100%;
443
450
  padding: 0 20px;
444
451
  }
445
- .app-header[data-v-c78820aa] {
452
+ .app-header[data-v-40b13903] {
446
453
  transition-duration: 0.3s;
447
454
  transition-property: height;
448
455
  transition-timing-function: linear;
@@ -450,13 +457,13 @@
450
457
  box-shadow: var(--dc-core-shadow, var(--el-box-shadow));
451
458
  z-index: 1;
452
459
  }
453
- .app-header-module[data-v-c78820aa] {
460
+ .app-header-module[data-v-40b13903] {
454
461
  display: flex;
455
462
  flex: 1;
456
463
  height: 100%;
457
464
  overflow-x: auto;
458
465
  }
459
- .app-header-module-item[data-v-c78820aa] {
466
+ .app-header-module-item[data-v-40b13903] {
460
467
  display: flex;
461
468
  justify-content: center;
462
469
  align-items: center;
@@ -464,24 +471,24 @@
464
471
  white-space: nowrap;
465
472
  height: 100%;
466
473
  }
467
- .app-header-module-item[data-v-c78820aa]:hover,
468
- .app-header-module-item_active[data-v-c78820aa] {
469
- color: var(--v8834c284);
474
+ .app-header-module-item[data-v-40b13903]:hover,
475
+ .app-header-module-item_active[data-v-40b13903] {
476
+ color: var(--v05959ac0);
470
477
  }
471
- .app-header-module-item_active[data-v-c78820aa] {
478
+ .app-header-module-item_active[data-v-40b13903] {
472
479
  position: relative;
473
480
  }
474
- .app-header-module-item_active[data-v-c78820aa]::before {
481
+ .app-header-module-item_active[data-v-40b13903]::before {
475
482
  content: "";
476
483
  position: absolute;
477
484
  left: 50%;
478
485
  top: 0;
479
486
  width: 100%;
480
487
  height: 3px;
481
- background-color: var(--v8834c284);
488
+ background-color: var(--v05959ac0);
482
489
  transform: translateX(-50%);
483
490
  }
484
- .app-header-shim[data-v-c78820aa] {
491
+ .app-header-shim[data-v-40b13903] {
485
492
  width: 100%;
486
493
  height: 100%;
487
494
  display: flex;
@@ -491,10 +498,10 @@
491
498
  font-size: 18px;
492
499
  font-weight: bold;
493
500
  }
494
- .app-header-module-item__icon[data-v-c78820aa] {
501
+ .app-header-module-item__icon[data-v-40b13903] {
495
502
  margin-right: 8px;
496
503
  }
497
- .app-header-right[data-v-c78820aa] {
504
+ .app-header-right[data-v-40b13903] {
498
505
  padding-left: 80px;
499
506
  display: flex;
500
507
  align-items: center;
@@ -593,13 +600,21 @@
593
600
  .dc-modal-video-preview__overlay {
594
601
  background: var(--el-overlay-color-dark, rgba(0, 0, 0, 0.7));
595
602
  }
596
- .app-layout[data-v-b45bcdfd] {
603
+ .app-layout[data-v-b68520cd] {
597
604
  position: relative;
598
605
  }
599
- .app-layout_noLogin[data-v-b45bcdfd] .app-body,
600
- .app-layout_noLogin[data-v-b45bcdfd] .app-sidebar,
601
- .app-layout_noLogin[data-v-b45bcdfd] .app-header,
602
- .app-layout_noLogin[data-v-b45bcdfd] .app-footer {
606
+ .app-layout_noLogin[data-v-b68520cd] .app-body,
607
+ .app-layout_noAnimation[data-v-b68520cd] .app-body,
608
+ .app-layout_noLogin[data-v-b68520cd] .app-sidebar,
609
+ .app-layout_noAnimation[data-v-b68520cd] .app-sidebar,
610
+ .app-layout_noLogin[data-v-b68520cd] .app-aside,
611
+ .app-layout_noAnimation[data-v-b68520cd] .app-aside,
612
+ .app-layout_noLogin[data-v-b68520cd] .app-header,
613
+ .app-layout_noAnimation[data-v-b68520cd] .app-header,
614
+ .app-layout_noLogin[data-v-b68520cd] .app-footer,
615
+ .app-layout_noAnimation[data-v-b68520cd] .app-footer,
616
+ .app-layout_noLogin[data-v-b68520cd] .app-collapse-toggle,
617
+ .app-layout_noAnimation[data-v-b68520cd] .app-collapse-toggle {
603
618
  transition: none !important;
604
619
  }
605
620
  .app-page[data-v-2359c6df] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@done-coding/admin-core",
3
- "version": "0.24.0",
3
+ "version": "0.25.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": "90ee71f640f4bea4039dd6e886c205815126a2f5"
86
+ "gitHead": "bb7ff7c309925c6b9738383a60e3a62a80ee99b4"
87
87
  }
@@ -1,5 +1,5 @@
1
1
  # bridge 模块(接线中枢)
2
2
 
3
- `createCoreBridge(options)` 产出 `CoreBridge` 单例——认证 / 布局 / 主题 / 路由派生的总接线口,经 `app.use(bridge)` 全局注入(`CORE_BRIDGE_KEY`)。
3
+ `createCoreBridge(options)` 产出 `CoreBridge` 单例——认证 / 布局 / 主题 / 徽标 / 路由派生的总接线口,经 `app.use(bridge)` 全局注入(`CORE_BRIDGE_KEY`)。
4
4
 
5
5
  > 详情见 [docs/README.md](./docs/README.md)(导出面 / 接线范式 / 认证与请求接线)
@@ -9,7 +9,8 @@ core 的「实例化中枢」:`createCoreBridge(options)` 产出 `CoreBridge`
9
9
  | 符号 | 职责 |
10
10
  | --- | --- |
11
11
  | `createCoreBridge` | 工厂:传入 `CoreBridgeInitOptions`(配置 schema + auth facade + 存储 + 主题初值等),产出 `CoreBridge` |
12
- | `CoreBridge` | 实例接口:`update()` 响应式更新 8 组配置视图 + `theme` 模块 + `layout` 折叠模块 + auth/api/路由派生方法 |
12
+ | `CoreBridge` | 实例接口:`update()` 响应式更新 8 组配置视图 + `theme` 模块 + `layout` 折叠模块 + `badge` 徽标注册表 + auth/api/路由派生方法 |
13
+ | `BadgeEntry` / `CoreBadgeModule` | 徽标注册表契约(见下「徽标注册表」章节) |
13
14
  | `CoreBridgeRegisterOptions` | 注册选项类型(配置键 → 默认值/访问器) |
14
15
  | `AppConfig` / `AppEnvInfo` | 配置 schema 顶层类型 |
15
16
  | `AppLayout*Config`(Header/Footer/Sidebar/Aside/Body/Viewport/Gap) | 布局 7 组配置视图类型(`update` 改它们即响应式生效) |
@@ -19,7 +20,7 @@ core 的「实例化中枢」:`createCoreBridge(options)` 产出 `CoreBridge`
19
20
  | `UserInfoAccess` / `UserInfoFieldAccessor` / `UserInfoPermissionAccessor` | userInfo 字段/权限访问器契约 |
20
21
  | `AppBaseInfo` 等类型别名 | 各配置视图的便捷类型 |
21
22
 
22
- **模块拆分**(规则 12:按耦合度独立文件):`theme/`(createBridgeThemeHook:风格 map + setStyle/setDark + 响应式 + 持久化)、`layout.ts`(折叠)、`plugin.ts`(插件注册)、`config-hook.ts`、`route.ts`(路由派生)、`state.ts`(useState 工厂)、`storage.ts`(命名空间存储)。
23
+ **模块拆分**(规则 12:按耦合度独立文件):`theme/`(createBridgeThemeHook:风格 map + setStyle/setDark + 响应式 + 持久化)、`layout.ts`(折叠)、`badge.ts`(徽标注册表)、`plugin.ts`(插件注册)、`config-hook.ts`、`route.ts`(路由派生)、`state.ts`(useState 工厂)、`storage.ts`(命名空间存储)。
23
24
 
24
25
  ## 快速上手(接线范式)
25
26
 
@@ -49,6 +50,101 @@ appCoreBridge.theme.setStyle("studio-stage");
49
50
  appCoreBridge.theme.setDark(true);
50
51
  ```
51
52
 
53
+ ## 徽标注册表(`bridge.badge`)
54
+
55
+ 一张扁平的 `key → 标记` 表;菜单项与 `ActionBtn` 按 key 查表渲染**绝对定位角标**(跟在文案右侧,**不压字**,且[MUST] 不改变宿主尺寸)。
56
+
57
+ **分工**:core **只负责展示**——聚合、归属、清标、数据来源全部在 app。
58
+
59
+ ### 契约
60
+
61
+ ```ts
62
+ type BadgeEntry = {
63
+ /** 展示截止的绝对时间(epoch ms);缺省或 0 = 永久展示 */
64
+ expireAt?: number;
65
+ /** 面向 agent / AI 对话的语义描述(core 不消费,纯透传;自由文本) */
66
+ description?: string;
67
+ } & (
68
+ | { kind: "count"; value: number; max?: number } // 数字角标
69
+ | { kind: "new" } // 新上线
70
+ | { kind: "updated" } // 更新
71
+ | { kind: "beta" } // 内测:能用,不稳定
72
+ | { kind: "wip" } // 开发:不可用(可用性由 app 自行处置)
73
+ | { kind: "custom"; text: string; type?: BadgeProps["type"] }
74
+ );
75
+ ```
76
+
77
+ 内置五档的文案与语义色由 core 统一定,保证跨 app 一致;`custom` 只在内置档不够用时用。
78
+
79
+ `custom.type` 取 element-plus 的语义色档(`primary` / `success` / `warning` / `danger` / `info`),随主题亮暗自适配。[MUST NOT] 传色值字面量——规则 14,写死色切主题不响应。
80
+
81
+ ### API
82
+
83
+ | 成员 | 用途 |
84
+ | --- | --- |
85
+ | `marks` | 全部标记(响应式只读;内部 `shallowRef`,每次操作产新对象) |
86
+ | `setAll(map)` | **整体替换** —— 轮询主路径,服务端本轮撤下的标随之消失 |
87
+ | `set(key, entry)` | 单点写入 / 覆盖 |
88
+ | `remove(key)` | 单点移除 |
89
+
90
+ ⚠️ [MUST NOT] 用 `bridge.update()` 维护徽标:它是 `Object.assign` **浅合并**,轮询场景下服务端已撤下的标会永远留在表里。徽标独立走 `bridge.badge`。
91
+
92
+ ### 两条失效路径(正交,可共存)
93
+
94
+ | 标记有无 `expireAt` | 怎么消失 | 谁负责 |
95
+ | --- | --- | --- |
96
+ | **有**(如随版本写死到某日的 NEW) | core 内部 60s tick 到点自动落 | core,app 不用管 |
97
+ | **无**(如服务端来的待办数 / Beta 标) | app 下一轮 `setAll` 刷掉 | app 的轮询节奏决定 |
98
+
99
+ `expireAt` **不是必填**——它是给「没有轮询通道」的静态标准备的兜底;有轮询的场景压根不用填。
100
+
101
+ ### key 约定
102
+
103
+ | 消费点 | key 取值 |
104
+ | --- | --- |
105
+ | 菜单项(AppSidebar 菜单树 / AppHeader 模块条) | **路由 `path`**,无需另配 |
106
+ | `ActionBtn` | 显式 `badgeKey`(按钮没有 path) |
107
+ | `TabsMain` 的 tab 标题(tab-bar / tile 卡片 / tile 角落 nav 三处同步) | `TabsMainItem.badgeKey`。[MUST NOT] 复用 `item.key`——那是**局部唯一**(v-model 值 + 槽名),跨页会串标 |
108
+
109
+ ### `description`:让这张表可被 agent 读
110
+
111
+ `description` 是 **core 完全不消费**的纯透传字段,供应用层做「本期上了什么功能」这类 AI 对话时,让模型知道某个 key 指向什么、怎么用、什么时候还不该用。
112
+
113
+ 配合已有的 `kind`(= 变更类别,与 [Keep a Changelog](https://keepachangelog.com/) 的 Added / Changed 词汇同构)+ `expireAt`(时效),这张表本身就是一份**机器可读的功能公告**——业界的 Beamer / Headway / LaunchNotes 变更日志、Pendo / Appcues 功能采纳平台,维护的都是同构的 `{类别, 描述, 时效, 受众}` 条目。
114
+
115
+ ```ts
116
+ bridge.badge.set("order.export", {
117
+ kind: "beta",
118
+ description:
119
+ "批量导出订单为 Excel。适合按月对账;单次超 10 万行会超时,内测期不建议用于生产对账。",
120
+ });
121
+ ```
122
+
123
+ **约定:core 内给 agent 读的描述统一挂在 `description` 字段名上;形状按任务定。**
124
+
125
+ | 场景 | 类型 | 为什么 |
126
+ | --- | --- | --- |
127
+ | **选型**(主题 preset,35 套里挑一套) | `CoreConfigDescription` 的 `{suitable, unsuitable}` 正反 facets | 闭集选型;`unsuitable` 可做**无需 LLM 的廉价预筛**,也强迫作者写清排除条件 |
128
+ | **讲解**(徽标,说明某功能是什么) | 自由文本 `string` | 对齐 MCP / OpenAI function calling / JSON Schema 的 tool `description` 范式(均单 string,约定把「何时用 / 何时不该用」写在同一段)。讲解场景不做排除,拆正反会让「新功能的不宜选场景是什么」无从填起 |
129
+
130
+ [MUST NOT] 为「统一」立一个只含 `description` 的空壳基类让两边继承——形状本就不同,继承收益为零;且 agent 侧读描述最终都是拼进 prompt 的文本,形状差异几乎零成本。
131
+
132
+ ⚠️ **边界**:重内容(图文步骤 / 深链 / 视频 / 前置条件)[SHOULD] 走 app 自己的知识库、用**同一 key 关联**,[MUST NOT] 往 `description` 堆——否则 `BadgeEntry` 会从「一个标」膨胀成 CMS 条目,core 被迫维护一个自己不消费的复杂 schema。
133
+
134
+ > 产品提醒:AI 主动推销「本期新功能」极易变成打扰(Clippy 陷阱)。要做 [SHOULD] 守低风险、可逆、ambient(不抢焦点)、用户始终在控制位。
135
+
136
+ ### 已知限制([MUST] 知悉,非缺陷)
137
+
138
+ - **时钟源是客户端**:用户机器时间不准或被改,`expireAt` 判定会偏。徽标是装饰,未做服务端对时。
139
+ - **到期最多滞留 60s**:tick 粒度,非实时。全 app 共享一个定时器,非每标一个。
140
+ - **不做冒泡聚合**:core [MUST NOT] 把子级的标上卷到父级菜单,见下。
141
+
142
+ ### 为什么不做冒泡聚合
143
+
144
+ 聚合要求 core 知道「一个 entry 归属哪个路由」,而真实系统里这个归属是多对多且有例外——弹窗里的按钮、全局工具栏按钮、一个入口同时挂在两个模块下。core 一旦定义归属模型,就是在替业务定信息架构,属越权。
145
+
146
+ 需要「一级模块上有引导点」的效果:**app 自己算完,直接往父级 path 写一条 entry**,同样达到效果且归属规则完全可控。
147
+
52
148
  ## 布局配置里的行为开关
53
149
 
54
150
  布局 8 组配置多为几何数值(宽高 / 间距 / 配色),少数是**行为开关**——需要单独知道语义:
@@ -10,7 +10,7 @@
10
10
  | `AppLayout` | 应用布局总控:bridge/appStore/userStore 三注入 + 5 内容锚 + 内置默认经典布局 shell | `bridge`(必填)`appStore`(必填)`userStore`(必填)`watermarkContent` `sidebarAboveHeader` | [README-AppLayout.md](./docs/README-AppLayout.md) |
11
11
  | `AppPage` | 页面主体容器:撑满视口 + 四向悬浮 fixed 插槽 | `fullViewport` `fullMode` `gap` `topBg`/`bottomBg`/`leftBg`/`rightBg` `nextViewportMaxHeight` | [README-AppPage.md](./docs/README-AppPage.md) |
12
12
  | `AppBody` | 内容主体:内置 RouterView + KeepAlive + 面包屑 affix + #footer 槽 | `menuFlatList` | [README-AppBody.md](./docs/README-AppBody.md) |
13
- | `AppHeader` | 顶栏积木:#left 品牌 + 路由模块菜单 + #right 操作区 | `menus` `menuFlatList`(必填)`show`(未消费) | [README-AppHeader.md](./docs/README-AppHeader.md) |
13
+ | `AppHeader` | 顶栏积木:#left 品牌 + 路由模块菜单(按 path 查 bridge.badge 显徽标)+ #right 操作区 | `menus` `menuFlatList`(必填)`show`(未消费) | [README-AppHeader.md](./docs/README-AppHeader.md) |
14
14
  | `AppSidebar` | 侧边栏积木:折叠把手 + MenuTree 菜单(树形/分组双形态)+ #top/#bottom 高度自撑 | `show` `menus`(必填)`menuFlatList`(必填)`aboveHeader`;菜单形态读 bridge `APP_LAYOUT_SIDEBAR_CONFIG.menuGroupMode`(分组态无折叠把手) | [README-AppSidebar.md](./docs/README-AppSidebar.md) |
15
15
  | `AppAside` | 右侧补充区积木(对称 AppSidebar),无内置内容 | `show` | [README-AppAside.md](./docs/README-AppAside.md) |
16
16
  | `AppBreadcrumb` | 面包屑积木:menuFlatList 匹配 route.matched 出链 | `height`(必填)`menuFlatList`(必填) | [README-AppBreadcrumb.md](./docs/README-AppBreadcrumb.md) |