@vc-shell/vc-app-skill 2.0.0-alpha.32 → 2.0.0-alpha.33-pr220.455e322
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/CHANGELOG.md +59 -47
- package/README.md +18 -12
- package/package.json +4 -4
- package/runtime/VERSION +1 -1
- package/runtime/agents/api-analyzer.md +31 -16
- package/runtime/agents/blade-enhancer.md +15 -9
- package/runtime/agents/details-blade-generator.md +47 -31
- package/runtime/agents/list-blade-generator.md +21 -37
- package/runtime/agents/locales-generator.md +3 -0
- package/runtime/agents/migration-agent.md +94 -0
- package/runtime/agents/module-analyzer.md +2 -0
- package/runtime/agents/module-assembler.md +15 -0
- package/runtime/agents/promote-agent.md +15 -4
- package/runtime/agents/type-checker.md +11 -0
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/api/platform.docs.md +30 -30
- package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +41 -41
- package/runtime/knowledge/docs/core/composables/bladeContext/index.docs.md +12 -10
- package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +11 -14
- package/runtime/knowledge/docs/core/composables/useAppBarMobileButtons/useAppBarMobileButtons.docs.md +35 -35
- package/runtime/knowledge/docs/core/composables/useAppBarWidget/useAppBarWidget.docs.md +35 -35
- package/runtime/knowledge/docs/core/composables/useAppInsights/useAppInsights.docs.md +15 -15
- package/runtime/knowledge/docs/core/composables/useAssets/useAssets.docs.md +21 -18
- package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +31 -27
- package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +90 -61
- package/runtime/knowledge/docs/core/composables/useBeforeUnload/useBeforeUnload.docs.md +19 -18
- package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +89 -68
- package/runtime/knowledge/docs/core/composables/useBladeForm/useBladeForm.docs.md +75 -19
- package/runtime/knowledge/docs/core/composables/useBladeRegistry/useBladeRegistry.docs.md +15 -15
- package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +74 -78
- package/runtime/knowledge/docs/core/composables/useBreadcrumbs/useBreadcrumbs.docs.md +11 -11
- package/runtime/knowledge/docs/core/composables/useConnectionStatus/useConnectionStatus.docs.md +27 -15
- package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +30 -30
- package/runtime/knowledge/docs/core/composables/useDynamicProperties/useDynamicProperties.docs.md +34 -36
- package/runtime/knowledge/docs/core/composables/useErrorHandler/useErrorHandler.docs.md +44 -23
- package/runtime/knowledge/docs/core/composables/useFunctions/useFunctions.docs.md +14 -11
- package/runtime/knowledge/docs/core/composables/useKeyboardNavigation/useKeyboardNavigation.docs.md +47 -38
- package/runtime/knowledge/docs/core/composables/useLanguages/useLanguages.docs.md +37 -28
- package/runtime/knowledge/docs/core/composables/useLoading/useLoading.docs.md +23 -17
- package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +10 -10
- package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +42 -42
- package/runtime/knowledge/docs/core/composables/useModificationTracker/useModificationTracker.docs.md +22 -12
- package/runtime/knowledge/docs/core/composables/useNotifications/useNotifications.docs.md +33 -41
- package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +16 -16
- package/runtime/knowledge/docs/core/composables/usePopup/usePopup.docs.md +32 -24
- package/runtime/knowledge/docs/core/composables/useResponsive/useResponsive.docs.md +32 -11
- package/runtime/knowledge/docs/core/composables/useSettings/useSettings.docs.md +24 -15
- package/runtime/knowledge/docs/core/composables/useSettingsMenu/useSettingsMenu.docs.md +7 -7
- package/runtime/knowledge/docs/core/composables/useSidebarState/useSidebarState.docs.md +32 -24
- package/runtime/knowledge/docs/core/composables/useSlowNetworkDetection/useSlowNetworkDetection.docs.md +21 -17
- package/runtime/knowledge/docs/core/composables/useTheme/useTheme.docs.md +24 -24
- package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +28 -31
- package/runtime/knowledge/docs/core/composables/useUser/useUser.docs.md +43 -24
- package/runtime/knowledge/docs/core/composables/useUserManagement/useUserManagement.docs.md +68 -48
- package/runtime/knowledge/docs/core/composables/useWebVitals/useWebVitals.docs.md +19 -19
- package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +42 -47
- package/runtime/knowledge/docs/core/directives/autofocus/autofocus.docs.md +10 -4
- package/runtime/knowledge/docs/core/directives/loading/loading.docs.md +35 -20
- package/runtime/knowledge/docs/core/notifications/notifications.docs.md +36 -35
- package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +38 -38
- package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +107 -91
- package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +10 -10
- package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +21 -23
- package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +98 -90
- package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +10 -16
- package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +9 -9
- package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +65 -22
- package/runtime/knowledge/docs/core/services/services.docs.md +19 -22
- package/runtime/knowledge/docs/core/types/types.docs.md +40 -40
- package/runtime/knowledge/docs/core/utilities/date/date-utilities.docs.md +27 -27
- package/runtime/knowledge/docs/core/utilities/shared-utilities.docs.md +23 -23
- package/runtime/knowledge/docs/core/utilities/thumbnail/thumbnail.docs.md +22 -25
- package/runtime/knowledge/docs/core/utilities/utilities.docs.md +64 -64
- package/runtime/knowledge/docs/injection-keys.docs.md +52 -51
- package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +9 -9
- package/runtime/knowledge/docs/shell/_internal/popup/common/popup-common.docs.md +23 -43
- package/runtime/knowledge/docs/shell/auth/ChangePasswordPage/change-password-page.docs.md +102 -0
- package/runtime/knowledge/docs/shell/auth/ForgotPasswordPage/forgot-password-page.docs.md +5 -5
- package/runtime/knowledge/docs/shell/auth/InvitePage/invite-page.docs.md +8 -7
- package/runtime/knowledge/docs/shell/auth/LoginPage/login-page.docs.md +7 -7
- package/runtime/knowledge/docs/shell/auth/ResetPasswordPage/reset-password-page.docs.md +8 -7
- package/runtime/knowledge/docs/shell/auth/sign-in/sign-in.docs.md +29 -13
- package/runtime/knowledge/docs/shell/components/change-password/change-password.docs.md +13 -16
- package/runtime/knowledge/docs/shell/components/change-password-button/change-password-button.docs.md +1 -7
- package/runtime/knowledge/docs/shell/components/error-interceptor/error-interceptor.docs.md +5 -5
- package/runtime/knowledge/docs/shell/components/language-selector/language-selector.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/notification-template/notification-template.docs.md +17 -9
- package/runtime/knowledge/docs/shell/components/settings-menu/settings-menu.docs.md +12 -18
- package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md +34 -65
- package/runtime/knowledge/docs/shell/components/sidebar/sidebar.docs.md +16 -26
- package/runtime/knowledge/docs/shell/components/theme-selector/theme-selector.docs.md +2 -2
- package/runtime/knowledge/docs/shell/components/user-dropdown-button/user-dropdown-button.docs.md +7 -9
- package/runtime/knowledge/docs/shell/dashboard/dashboard-charts/dashboard-charts.docs.md +30 -40
- package/runtime/knowledge/docs/shell/dashboard/dashboard-widget-card/dashboard-widget-card.docs.md +26 -19
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +15 -12
- package/runtime/knowledge/docs/ui/components/atoms/vc-badge/vc-badge.docs.md +15 -26
- package/runtime/knowledge/docs/ui/components/atoms/vc-banner/vc-banner.docs.md +21 -19
- package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +83 -67
- package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +104 -59
- package/runtime/knowledge/docs/ui/components/atoms/vc-col/vc-col.docs.md +28 -11
- package/runtime/knowledge/docs/ui/components/atoms/vc-container/vc-container.docs.md +20 -17
- package/runtime/knowledge/docs/ui/components/atoms/vc-hint/vc-hint.docs.md +26 -17
- package/runtime/knowledge/docs/ui/components/atoms/vc-icon/vc-icon.docs.md +30 -32
- package/runtime/knowledge/docs/ui/components/atoms/vc-image/vc-image.docs.md +25 -48
- package/runtime/knowledge/docs/ui/components/atoms/vc-label/vc-label.docs.md +29 -24
- package/runtime/knowledge/docs/ui/components/atoms/vc-link/vc-link.docs.md +23 -15
- package/runtime/knowledge/docs/ui/components/atoms/vc-loading/vc-loading.docs.md +22 -13
- package/runtime/knowledge/docs/ui/components/atoms/vc-progress/vc-progress.docs.md +33 -18
- package/runtime/knowledge/docs/ui/components/atoms/vc-row/vc-row.docs.md +56 -15
- package/runtime/knowledge/docs/ui/components/atoms/vc-scrollable-container/vc-scrollable-container.docs.md +28 -15
- package/runtime/knowledge/docs/ui/components/atoms/vc-skeleton/vc-skeleton.docs.md +40 -20
- package/runtime/knowledge/docs/ui/components/atoms/vc-status/vc-status.docs.md +25 -14
- package/runtime/knowledge/docs/ui/components/atoms/vc-status-icon/vc-status-icon.docs.md +40 -14
- package/runtime/knowledge/docs/ui/components/atoms/vc-tooltip/vc-tooltip.docs.md +54 -42
- package/runtime/knowledge/docs/ui/components/atoms/vc-video/vc-video.docs.md +17 -17
- package/runtime/knowledge/docs/ui/components/atoms/vc-widget/vc-widget.docs.md +21 -21
- package/runtime/knowledge/docs/ui/components/molecules/multilanguage-selector/multilanguage-selector.docs.md +23 -10
- package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +59 -44
- package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md +23 -20
- package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox/vc-checkbox.docs.md +96 -64
- package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox-group/vc-checkbox-group.docs.md +26 -35
- package/runtime/knowledge/docs/ui/components/molecules/vc-color-input/vc-color-input.docs.md +69 -22
- package/runtime/knowledge/docs/ui/components/molecules/vc-date-picker/vc-date-picker.docs.md +58 -72
- package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown/vc-dropdown.docs.md +91 -85
- package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown-panel/vc-dropdown-panel.docs.md +38 -42
- package/runtime/knowledge/docs/ui/components/molecules/vc-editor/vc-editor.docs.md +60 -72
- package/runtime/knowledge/docs/ui/components/molecules/vc-field/vc-field.docs.md +65 -26
- package/runtime/knowledge/docs/ui/components/molecules/vc-file-upload/vc-file-upload.docs.md +46 -49
- package/runtime/knowledge/docs/ui/components/molecules/vc-form/vc-form.docs.md +35 -64
- package/runtime/knowledge/docs/ui/components/molecules/vc-image-tile/vc-image-tile.docs.md +38 -41
- package/runtime/knowledge/docs/ui/components/molecules/vc-input/vc-input.docs.md +115 -130
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-currency/vc-input-currency.docs.md +53 -87
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.docs.md +56 -63
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-group/vc-input-group.docs.md +29 -24
- package/runtime/knowledge/docs/ui/components/molecules/vc-menu/vc-menu.docs.md +32 -28
- package/runtime/knowledge/docs/ui/components/molecules/vc-multivalue/vc-multivalue.docs.md +63 -64
- package/runtime/knowledge/docs/ui/components/molecules/vc-pagination/vc-pagination.docs.md +28 -26
- package/runtime/knowledge/docs/ui/components/molecules/vc-radio-button/vc-radio-button.docs.md +59 -19
- package/runtime/knowledge/docs/ui/components/molecules/vc-radio-group/vc-radio-group.docs.md +25 -34
- package/runtime/knowledge/docs/ui/components/molecules/vc-rating/vc-rating.docs.md +42 -32
- package/runtime/knowledge/docs/ui/components/molecules/vc-select/vc-select.docs.md +78 -82
- package/runtime/knowledge/docs/ui/components/molecules/vc-slider/vc-slider.docs.md +25 -15
- package/runtime/knowledge/docs/ui/components/molecules/vc-switch/vc-switch.docs.md +59 -63
- package/runtime/knowledge/docs/ui/components/molecules/vc-textarea/vc-textarea.docs.md +57 -69
- package/runtime/knowledge/docs/ui/components/molecules/vc-toast/vc-toast.docs.md +58 -57
- package/runtime/knowledge/docs/ui/components/organisms/vc-app/vc-app.docs.md +49 -26
- package/runtime/knowledge/docs/ui/components/organisms/vc-auth-layout/vc-auth-layout.docs.md +82 -28
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +120 -75
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/composables/table-composables.docs.md +30 -44
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +536 -365
- package/runtime/knowledge/docs/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.docs.md +35 -52
- package/runtime/knowledge/docs/ui/components/organisms/vc-gallery/vc-gallery.docs.md +33 -62
- package/runtime/knowledge/docs/ui/components/organisms/vc-image-upload/vc-image-upload.docs.md +17 -23
- package/runtime/knowledge/docs/ui/components/organisms/vc-popup/vc-popup.docs.md +109 -68
- package/runtime/knowledge/docs/ui/components/organisms/vc-sidebar/vc-sidebar.docs.md +82 -44
- package/runtime/knowledge/docs/ui/composables/ui-composables.docs.md +8 -8
- package/runtime/knowledge/docs/ui/composables/useDataTablePagination.docs.md +164 -0
- package/runtime/knowledge/docs/ui/composables/useDataTableSort.docs.md +34 -26
- package/runtime/knowledge/docs/ui/composables/useTableSelection.docs.md +48 -40
- package/runtime/knowledge/docs/ui/composables/useTableSort.docs.md +30 -17
- package/runtime/knowledge/docs/ui/types/ui-types.docs.md +40 -29
- package/runtime/knowledge/examples/offers-module.md +15 -13
- package/runtime/knowledge/examples/team-module.md +82 -119
- package/runtime/knowledge/examples/videos-module.md +44 -17
- package/runtime/knowledge/index.md +22 -0
- package/runtime/knowledge/migration-prompts/blade-form-migration.md +255 -0
- package/runtime/knowledge/migration-prompts/blade-props-migration.md +194 -0
- package/runtime/knowledge/migration-prompts/datatable-migration.md +801 -0
- package/runtime/knowledge/migration-prompts/icon-migration.md +97 -0
- package/runtime/knowledge/migration-prompts/manual-migration-audit.md +117 -0
- package/runtime/knowledge/migration-prompts/notifications-migration.md +223 -0
- package/runtime/knowledge/migration-prompts/nswag-migration.md +244 -0
- package/runtime/knowledge/migration-prompts/use-assets-migration.md +164 -0
- package/runtime/knowledge/migration-prompts/use-data-table-pagination-migration.md +176 -0
- package/runtime/knowledge/migration-prompts/widgets-migration.md +178 -0
- package/runtime/knowledge/patterns/assets-management.md +20 -20
- package/runtime/knowledge/patterns/blade-navigation.md +7 -14
- package/runtime/knowledge/patterns/blade-widget.md +19 -17
- package/runtime/knowledge/patterns/child-blade-flow.md +19 -7
- package/runtime/knowledge/patterns/composable-details.md +20 -50
- package/runtime/knowledge/patterns/composable-list.md +43 -31
- package/runtime/knowledge/patterns/dashboard-widget.md +14 -16
- package/runtime/knowledge/patterns/datatable-pattern.md +521 -0
- package/runtime/knowledge/patterns/details-blade-pattern.md +78 -116
- package/runtime/knowledge/patterns/extension-points-usage.md +53 -44
- package/runtime/knowledge/patterns/form-validation.md +28 -64
- package/runtime/knowledge/patterns/list-blade-pattern.md +33 -21
- package/runtime/knowledge/patterns/module-structure.md +7 -1
- package/runtime/knowledge/patterns/multilanguage-fields.md +8 -14
- package/runtime/knowledge/patterns/notification-template.md +21 -14
- package/runtime/knowledge/patterns/signalr-notifications.md +30 -32
- package/runtime/knowledge/patterns/toolbar-pattern.md +18 -20
- package/runtime/vc-app.md +354 -49
- package/runtime/knowledge/docs/core/constants/constants.docs.md +0 -185
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Blade Widget Pattern
|
|
2
2
|
|
|
3
|
-
Blade widgets are sidebar items displayed alongside a details blade. They allow navigation to related child blades (e.g., "
|
|
3
|
+
Blade widgets are sidebar items displayed alongside a details blade. They allow navigation to related child blades (e.g., "Children of this entity", "Associated items"). Each widget shows an icon, a title, and an optional reactive badge count.
|
|
4
4
|
|
|
5
5
|
Blade widgets use `useBladeWidgets()` — the headless widget API. No `.vue` component is needed per widget; the framework renders a standard `<VcWidget>` from the config object.
|
|
6
6
|
|
|
@@ -17,18 +17,18 @@ import { useBladeWidgets, useBlade } from "@vc-shell/framework";
|
|
|
17
17
|
import { computed } from "vue";
|
|
18
18
|
|
|
19
19
|
const { openBlade } = useBlade();
|
|
20
|
-
const
|
|
20
|
+
const childCount = ref(0);
|
|
21
21
|
|
|
22
22
|
useBladeWidgets([
|
|
23
23
|
{
|
|
24
|
-
id: "
|
|
24
|
+
id: "ChildListWidget",
|
|
25
25
|
icon: "lucide-tag",
|
|
26
|
-
title: "
|
|
27
|
-
badge: computed(() =>
|
|
28
|
-
isVisible: computed(() => !!param.value),
|
|
29
|
-
onClick: () => openBlade({ name: "
|
|
26
|
+
title: "MODULE.WIDGETS.CHILD_LIST.TITLE",
|
|
27
|
+
badge: computed(() => childCount.value),
|
|
28
|
+
isVisible: computed(() => !!param.value), // hide on "create new"
|
|
29
|
+
onClick: () => openBlade({ name: "ChildEntityList", options: { entityId: entity.value.id } }),
|
|
30
30
|
onRefresh: async () => {
|
|
31
|
-
|
|
31
|
+
childCount.value = await loadChildCount(entity.value.id);
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
34
|
]);
|
|
@@ -66,10 +66,11 @@ export function useXxxWidgets(options: UseXxxWidgetsOptions): UseBladeWidgetsRet
|
|
|
66
66
|
title: "XXX.WIDGETS.RELATED_ITEMS.TITLE",
|
|
67
67
|
badge: relatedCount,
|
|
68
68
|
isVisible,
|
|
69
|
-
onClick: () =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
onClick: () =>
|
|
70
|
+
openBlade({
|
|
71
|
+
name: "RelatedItemsList",
|
|
72
|
+
options: { parentId: item.value?.id },
|
|
73
|
+
}),
|
|
73
74
|
},
|
|
74
75
|
]);
|
|
75
76
|
}
|
|
@@ -96,9 +97,9 @@ const { refreshAll } = useXxxWidgets({
|
|
|
96
97
|
|
|
97
98
|
```ts
|
|
98
99
|
interface HeadlessWidgetDeclaration {
|
|
99
|
-
id: string;
|
|
100
|
-
icon: string;
|
|
101
|
-
title: string;
|
|
100
|
+
id: string; // unique within the blade
|
|
101
|
+
icon: string; // lucide icon name
|
|
102
|
+
title: string; // i18n key or plain string
|
|
102
103
|
badge?: Ref<number | string> | ComputedRef<number | string>;
|
|
103
104
|
loading?: Ref<boolean> | ComputedRef<boolean>;
|
|
104
105
|
disabled?: Ref<boolean> | ComputedRef<boolean> | boolean;
|
|
@@ -112,8 +113,8 @@ interface HeadlessWidgetDeclaration {
|
|
|
112
113
|
|
|
113
114
|
```ts
|
|
114
115
|
interface UseBladeWidgetsReturn {
|
|
115
|
-
refresh: (widgetId: string) => void;
|
|
116
|
-
refreshAll: () => void;
|
|
116
|
+
refresh: (widgetId: string) => void; // trigger onRefresh for one widget
|
|
117
|
+
refreshAll: () => void; // trigger onRefresh for all widgets
|
|
117
118
|
}
|
|
118
119
|
```
|
|
119
120
|
|
|
@@ -124,6 +125,7 @@ Call `refreshAll()` from `onMounted` or after data loads to populate badge count
|
|
|
124
125
|
## Lifecycle
|
|
125
126
|
|
|
126
127
|
`useBladeWidgets()` automatically:
|
|
128
|
+
|
|
127
129
|
- Registers all widgets with the `WidgetService` on `onMounted`
|
|
128
130
|
- Unregisters all widgets from the `WidgetService` on `onUnmounted`
|
|
129
131
|
|
|
@@ -13,10 +13,14 @@ const { openBlade } = useBlade();
|
|
|
13
13
|
|
|
14
14
|
openBlade({
|
|
15
15
|
name: "ProductDetails",
|
|
16
|
-
param: product.id,
|
|
17
|
-
options: { mode: "edit", origin: "catalog" },
|
|
18
|
-
onOpen() {
|
|
19
|
-
|
|
16
|
+
param: product.id, // string — entity ID, appears in URL
|
|
17
|
+
options: { mode: "edit", origin: "catalog" }, // object — runtime-only, not in URL
|
|
18
|
+
onOpen() {
|
|
19
|
+
selectedItemId.value = product.id;
|
|
20
|
+
},
|
|
21
|
+
onClose() {
|
|
22
|
+
selectedItemId.value = undefined;
|
|
23
|
+
},
|
|
20
24
|
});
|
|
21
25
|
```
|
|
22
26
|
|
|
@@ -93,10 +97,12 @@ onBeforeClose(async () => {
|
|
|
93
97
|
```
|
|
94
98
|
|
|
95
99
|
Return value semantics:
|
|
100
|
+
|
|
96
101
|
- `false` — close is **allowed**
|
|
97
102
|
- `true` — close is **blocked** (user stays on the blade)
|
|
98
103
|
|
|
99
104
|
The `!(await showConfirmation(...))` idiom:
|
|
105
|
+
|
|
100
106
|
- User clicks "Confirm" (discard) -> `true` -> `!true` = `false` -> close allowed
|
|
101
107
|
- User clicks "Cancel" (stay) -> `false` -> `!false` = `true` -> close blocked
|
|
102
108
|
|
|
@@ -187,8 +193,12 @@ function onItemClick(event: { data: { id?: string } }) {
|
|
|
187
193
|
openBlade({
|
|
188
194
|
name: "ProductDetails",
|
|
189
195
|
param: event.data.id,
|
|
190
|
-
onOpen() {
|
|
191
|
-
|
|
196
|
+
onOpen() {
|
|
197
|
+
selectedItemId.value = event.data.id;
|
|
198
|
+
},
|
|
199
|
+
onClose() {
|
|
200
|
+
selectedItemId.value = undefined;
|
|
201
|
+
},
|
|
192
202
|
});
|
|
193
203
|
}
|
|
194
204
|
|
|
@@ -250,7 +260,9 @@ A "middle" blade acts as both child (calls its parent) and parent (exposes metho
|
|
|
250
260
|
const { openBlade, callParent, exposeToChildren, closeSelf } = useBlade();
|
|
251
261
|
|
|
252
262
|
// As a parent — expose reload for child blades
|
|
253
|
-
async function reload() {
|
|
263
|
+
async function reload() {
|
|
264
|
+
await loadOrder(param.value!);
|
|
265
|
+
}
|
|
254
266
|
exposeToChildren({ reload });
|
|
255
267
|
|
|
256
268
|
// As a child — open sub-details blade
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Details Composable Pattern (`use<Entity>`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generic worked example for the canonical `useTeamMember` / `useEntity` composable shape.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -13,19 +13,9 @@ Naming convention: `use<Entity>` (singular) for details, `use<Entity>s` (plural)
|
|
|
13
13
|
## Full Code Skeleton
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
17
|
-
useModificationTracker,
|
|
18
|
-
useApiClient,
|
|
19
|
-
useAsync,
|
|
20
|
-
useLoading,
|
|
21
|
-
} from "@vc-shell/framework";
|
|
16
|
+
import { useModificationTracker, useApiClient, useAsync, useLoading } from "@vc-shell/framework";
|
|
22
17
|
import type { AsyncAction } from "@vc-shell/framework";
|
|
23
|
-
import {
|
|
24
|
-
XxxClient,
|
|
25
|
-
XxxType,
|
|
26
|
-
CreateXxxCommand,
|
|
27
|
-
UpdateXxxCommand,
|
|
28
|
-
} from "../../api_client/xxx-client";
|
|
18
|
+
import { XxxClient, XxxType, CreateXxxCommand, UpdateXxxCommand } from "../../api_client/xxx-client";
|
|
29
19
|
import { computed, ComputedRef, Ref, ref } from "vue";
|
|
30
20
|
|
|
31
21
|
// NOTE: useI18n is NOT imported here. Translations belong in blade components.
|
|
@@ -50,15 +40,11 @@ export default (): IUseXxx => {
|
|
|
50
40
|
// --- Entity ref and modification tracker ---
|
|
51
41
|
// entityRef is internal; currentValue is exposed as `entity`
|
|
52
42
|
const entityRef = ref({} as XxxType) as Ref<XxxType>;
|
|
53
|
-
const { currentValue, pristineValue, resetModificationState, isModified } =
|
|
54
|
-
useModificationTracker(entityRef);
|
|
43
|
+
const { currentValue, pristineValue, resetModificationState, isModified } = useModificationTracker(entityRef);
|
|
55
44
|
|
|
56
45
|
// --- CRUD operations (each has its own loading ref) ---
|
|
57
46
|
|
|
58
|
-
const { action: getXxx, loading: getXxxLoading } = useAsync<
|
|
59
|
-
{ id: string },
|
|
60
|
-
void
|
|
61
|
-
>(async (args) => {
|
|
47
|
+
const { action: getXxx, loading: getXxxLoading } = useAsync<{ id: string }, void>(async (args) => {
|
|
62
48
|
if (!args?.id) {
|
|
63
49
|
throw new Error("Id is required");
|
|
64
50
|
}
|
|
@@ -74,10 +60,7 @@ export default (): IUseXxx => {
|
|
|
74
60
|
resetModificationState();
|
|
75
61
|
});
|
|
76
62
|
|
|
77
|
-
const { action: createXxx, loading: createXxxLoading } = useAsync<
|
|
78
|
-
XxxType,
|
|
79
|
-
void
|
|
80
|
-
>(async (details) => {
|
|
63
|
+
const { action: createXxx, loading: createXxxLoading } = useAsync<XxxType, void>(async (details) => {
|
|
81
64
|
const client = await getApiClient();
|
|
82
65
|
|
|
83
66
|
const command = {
|
|
@@ -93,10 +76,7 @@ export default (): IUseXxx => {
|
|
|
93
76
|
resetModificationState();
|
|
94
77
|
});
|
|
95
78
|
|
|
96
|
-
const { action: updateXxx, loading: updateXxxLoading } = useAsync<
|
|
97
|
-
XxxType,
|
|
98
|
-
void
|
|
99
|
-
>(async (details) => {
|
|
79
|
+
const { action: updateXxx, loading: updateXxxLoading } = useAsync<XxxType, void>(async (details) => {
|
|
100
80
|
if (!details?.id) {
|
|
101
81
|
throw new Error("Id is required");
|
|
102
82
|
}
|
|
@@ -116,10 +96,7 @@ export default (): IUseXxx => {
|
|
|
116
96
|
resetModificationState();
|
|
117
97
|
});
|
|
118
98
|
|
|
119
|
-
const { action: deleteXxx, loading: deleteXxxLoading } = useAsync<
|
|
120
|
-
{ id: string },
|
|
121
|
-
void
|
|
122
|
-
>(async (args) => {
|
|
99
|
+
const { action: deleteXxx, loading: deleteXxxLoading } = useAsync<{ id: string }, void>(async (args) => {
|
|
123
100
|
if (!args?.id) {
|
|
124
101
|
throw new Error("Id is required");
|
|
125
102
|
}
|
|
@@ -138,12 +115,7 @@ export default (): IUseXxx => {
|
|
|
138
115
|
// --- Return ---
|
|
139
116
|
return {
|
|
140
117
|
// Aggregate all async loading refs into a single computed boolean
|
|
141
|
-
loading: useLoading(
|
|
142
|
-
getXxxLoading,
|
|
143
|
-
createXxxLoading,
|
|
144
|
-
updateXxxLoading,
|
|
145
|
-
deleteXxxLoading,
|
|
146
|
-
),
|
|
118
|
+
loading: useLoading(getXxxLoading, createXxxLoading, updateXxxLoading, deleteXxxLoading),
|
|
147
119
|
// currentValue is the reactive entity — alias as `entity` for the blade
|
|
148
120
|
entity: currentValue,
|
|
149
121
|
modified: computed(() => isModified.value),
|
|
@@ -263,9 +235,9 @@ export default (): IUseXxx => {
|
|
|
263
235
|
|
|
264
236
|
---
|
|
265
237
|
|
|
266
|
-
## Route Params (when entity scope requires
|
|
238
|
+
## Route Params (when entity scope requires a parent ownerId / tenantId)
|
|
267
239
|
|
|
268
|
-
Some API calls require a scope parameter (e.g., `
|
|
240
|
+
Some API calls require a scope parameter (e.g., `ownerId`) extracted from the route. Use `useRoute()`:
|
|
269
241
|
|
|
270
242
|
```ts
|
|
271
243
|
import { useRoute } from "vue-router";
|
|
@@ -274,20 +246,18 @@ export default (): IUseXxx => {
|
|
|
274
246
|
const route = useRoute();
|
|
275
247
|
const { getApiClient } = useApiClient(XxxClient);
|
|
276
248
|
|
|
277
|
-
async function
|
|
278
|
-
return route?.params?.
|
|
249
|
+
async function getOwnerId(): Promise<string> {
|
|
250
|
+
return route?.params?.ownerId as string;
|
|
279
251
|
}
|
|
280
252
|
|
|
281
|
-
const { action: getXxx, loading: getXxxLoading } = useAsync<{ id: string }>(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const sellerId = await getSellerId();
|
|
253
|
+
const { action: getXxx, loading: getXxxLoading } = useAsync<{ id: string }>(async (args) => {
|
|
254
|
+
const client = await getApiClient();
|
|
255
|
+
const ownerId = await getOwnerId();
|
|
285
256
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
);
|
|
257
|
+
const result = await client.getXxx(args.id, ownerId);
|
|
258
|
+
currentValue.value = result;
|
|
259
|
+
resetModificationState();
|
|
260
|
+
});
|
|
291
261
|
|
|
292
262
|
// ...
|
|
293
263
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Describes the plural composable that handles search, pagination, and sort for a list blade. Named with the **plural** entity name: `useTeamMembers`, `useCatalogItems`, `useOrders`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Generic worked example for the plural list composable shape.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,12 +10,7 @@ Source: extracted from `apps/vendor-portal/src/modules/team/composables/useTeamM
|
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import { useApiClient, useAsync, useLoading } from "@vc-shell/framework";
|
|
13
|
-
import {
|
|
14
|
-
SearchXxxQuery,
|
|
15
|
-
SearchXxxResult,
|
|
16
|
-
XxxType,
|
|
17
|
-
XxxClient,
|
|
18
|
-
} from "../../../../api_client/<module-client-file>";
|
|
13
|
+
import { SearchXxxQuery, SearchXxxResult, XxxType, XxxClient } from "../../../../api_client/<module-client-file>";
|
|
19
14
|
import type { SearchXxxQuery as ISearchXxxQuery } from "../../../../api_client/<module-client-file>";
|
|
20
15
|
import { computed, Ref, ref } from "vue";
|
|
21
16
|
import { useRoute } from "vue-router";
|
|
@@ -39,7 +34,7 @@ interface IUseXxxsOptions {
|
|
|
39
34
|
|
|
40
35
|
export default (options?: IUseXxxsOptions): IUseXxxs => {
|
|
41
36
|
const { getApiClient } = useApiClient(XxxClient);
|
|
42
|
-
const route = useRoute();
|
|
37
|
+
const route = useRoute(); // for extracting route params (e.g., ownerId, parentId)
|
|
43
38
|
|
|
44
39
|
const pageSize = options?.pageSize || 20;
|
|
45
40
|
|
|
@@ -90,24 +85,19 @@ export default (options?: IUseXxxsOptions): IUseXxxs => {
|
|
|
90
85
|
|
|
91
86
|
```ts
|
|
92
87
|
import { useApiClient, useAsync, useLoading } from "@vc-shell/framework";
|
|
93
|
-
import {
|
|
94
|
-
|
|
95
|
-
SearchSellerUsersResult,
|
|
96
|
-
SellerUser,
|
|
97
|
-
VcmpSellerSecurityClient,
|
|
98
|
-
} from "../../../../api_client/virtocommerce.marketplacevendor";
|
|
99
|
-
import type { SearchSellerUsersQuery as ISearchSellerUsersQuery } from "../../../../api_client/virtocommerce.marketplacevendor";
|
|
88
|
+
import { SearchUsersQuery, SearchUsersResult, User, UserSecurityClient } from "../../../../api_client/virtocommerce.mymodule";
|
|
89
|
+
import type { SearchUsersQuery as ISearchUsersQuery } from "../../../../api_client/virtocommerce.mymodule";
|
|
100
90
|
import { computed, Ref, ref } from "vue";
|
|
101
91
|
import { useRoute } from "vue-router";
|
|
102
92
|
|
|
103
93
|
interface IUseTeamMembers {
|
|
104
94
|
readonly loading: Ref<boolean>;
|
|
105
|
-
readonly membersList: Ref<
|
|
95
|
+
readonly membersList: Ref<User[]>;
|
|
106
96
|
readonly totalCount: Ref<number>;
|
|
107
97
|
readonly pages: Ref<number>;
|
|
108
98
|
currentPage: Ref<number>;
|
|
109
|
-
searchQuery: Ref<
|
|
110
|
-
getTeamMembers: (query?:
|
|
99
|
+
searchQuery: Ref<ISearchUsersQuery>;
|
|
100
|
+
getTeamMembers: (query?: ISearchUsersQuery) => Promise<void>;
|
|
111
101
|
}
|
|
112
102
|
|
|
113
103
|
interface IUseTeamMembersOptions {
|
|
@@ -116,29 +106,29 @@ interface IUseTeamMembersOptions {
|
|
|
116
106
|
}
|
|
117
107
|
|
|
118
108
|
export default (options?: IUseTeamMembersOptions): IUseTeamMembers => {
|
|
119
|
-
const { getApiClient } = useApiClient(
|
|
109
|
+
const { getApiClient } = useApiClient(UserSecurityClient);
|
|
120
110
|
const route = useRoute();
|
|
121
111
|
|
|
122
112
|
const pageSize = options?.pageSize || 20;
|
|
123
|
-
const searchQuery = ref<
|
|
113
|
+
const searchQuery = ref<ISearchUsersQuery>({
|
|
124
114
|
take: pageSize,
|
|
125
115
|
sort: options?.sort,
|
|
126
116
|
});
|
|
127
117
|
|
|
128
|
-
const searchResult = ref<
|
|
118
|
+
const searchResult = ref<SearchUsersResult>();
|
|
129
119
|
|
|
130
|
-
async function
|
|
131
|
-
const result = route?.params?.
|
|
120
|
+
async function GetOwnerId(): Promise<string> {
|
|
121
|
+
const result = route?.params?.ownerId as string;
|
|
132
122
|
return result;
|
|
133
123
|
}
|
|
134
124
|
|
|
135
|
-
const { action: getTeamMembers, loading: getTeamMembersLoading } = useAsync<
|
|
125
|
+
const { action: getTeamMembers, loading: getTeamMembersLoading } = useAsync<ISearchUsersQuery>(async (query) => {
|
|
136
126
|
const client = await getApiClient();
|
|
137
|
-
const
|
|
127
|
+
const ownerId = await GetOwnerId();
|
|
138
128
|
searchQuery.value = { ...searchQuery.value, ...query };
|
|
139
129
|
|
|
140
|
-
const command = { ...searchQuery.value,
|
|
141
|
-
searchResult.value = await client.
|
|
130
|
+
const command = { ...searchQuery.value, ownerId } as SearchUsersQuery;
|
|
131
|
+
searchResult.value = await client.searchUsers(command);
|
|
142
132
|
});
|
|
143
133
|
|
|
144
134
|
return {
|
|
@@ -158,7 +148,9 @@ export default (options?: IUseTeamMembersOptions): IUseTeamMembers => {
|
|
|
158
148
|
## Key Rules
|
|
159
149
|
|
|
160
150
|
### File location
|
|
151
|
+
|
|
161
152
|
Each composable lives in its own subdirectory with an `index.ts`:
|
|
153
|
+
|
|
162
154
|
```
|
|
163
155
|
composables/
|
|
164
156
|
├── useXxxs/
|
|
@@ -167,39 +159,52 @@ composables/
|
|
|
167
159
|
```
|
|
168
160
|
|
|
169
161
|
### Default export (not named)
|
|
162
|
+
|
|
170
163
|
The composable is a **default export** (a factory function). Named exports are the interfaces only. The barrel (`composables/index.ts`) wraps it with a named export:
|
|
164
|
+
|
|
171
165
|
```ts
|
|
172
166
|
export { default as useXxxs } from "./useXxxs";
|
|
173
167
|
```
|
|
174
168
|
|
|
175
169
|
### Type aliasing
|
|
170
|
+
|
|
176
171
|
NSwag generates classes with the same name as their interfaces. Import the class for runtime use, and import the interface type separately with an alias:
|
|
172
|
+
|
|
177
173
|
```ts
|
|
178
|
-
import { SearchXxxQuery, XxxClient } from "...";
|
|
174
|
+
import { SearchXxxQuery, XxxClient } from "..."; // class (runtime)
|
|
179
175
|
import type { SearchXxxQuery as ISearchXxxQuery } from "..."; // interface (type-only)
|
|
180
176
|
```
|
|
181
177
|
|
|
182
178
|
### `useAsync` signature
|
|
179
|
+
|
|
183
180
|
`useAsync<TArg>(fn)` — generic `TArg` is the type of the argument passed to the action:
|
|
181
|
+
|
|
184
182
|
```ts
|
|
185
183
|
const { action: getXxxs, loading: getXxxsLoading } = useAsync<ISearchXxxQuery>(async (query) => {
|
|
186
184
|
// query is typed as ISearchXxxQuery | undefined
|
|
187
185
|
});
|
|
188
186
|
```
|
|
187
|
+
|
|
189
188
|
`action` is the callable — invoke it as `getXxxs(query)`.
|
|
190
189
|
|
|
191
190
|
### `useLoading`
|
|
191
|
+
|
|
192
192
|
Wrap loading refs with `useLoading` for consistent boolean ref behavior:
|
|
193
|
+
|
|
193
194
|
```ts
|
|
194
195
|
loading: useLoading(getXxxsLoading),
|
|
195
196
|
```
|
|
197
|
+
|
|
196
198
|
If there are multiple async operations (e.g., search + delete), pass all loading refs:
|
|
199
|
+
|
|
197
200
|
```ts
|
|
198
201
|
loading: useLoading(getXxxsLoading, deleteXxxLoading),
|
|
199
202
|
```
|
|
200
203
|
|
|
201
204
|
### Route params
|
|
202
|
-
|
|
205
|
+
|
|
206
|
+
Use `useRoute()` to access route params when the API requires a parent entity ID (e.g., `ownerId`, `orderId`). Access inside the async function, not at module scope:
|
|
207
|
+
|
|
203
208
|
```ts
|
|
204
209
|
const route = useRoute();
|
|
205
210
|
// inside useAsync callback:
|
|
@@ -207,15 +212,18 @@ const parentId = route?.params?.parentId as string;
|
|
|
207
212
|
```
|
|
208
213
|
|
|
209
214
|
### No `useI18n` in composables
|
|
215
|
+
|
|
210
216
|
i18n belongs in blade components only. Composables must not import or use `useI18n`.
|
|
211
217
|
|
|
212
218
|
### `searchQuery` is mutable
|
|
219
|
+
|
|
213
220
|
The `searchQuery` ref is returned with `Ref<ISearchXxxQuery>` (not `readonly`) because the blade reads it to construct reload/pagination calls:
|
|
221
|
+
|
|
214
222
|
```ts
|
|
215
223
|
// In blade:
|
|
216
224
|
const reload = async () => {
|
|
217
225
|
await getXxxs({
|
|
218
|
-
...searchQuery.value,
|
|
226
|
+
...searchQuery.value, // preserve existing params
|
|
219
227
|
skip: (currentPage.value - 1) * (searchQuery.value?.take ?? 20),
|
|
220
228
|
sort: sortExpression.value,
|
|
221
229
|
});
|
|
@@ -223,22 +231,26 @@ const reload = async () => {
|
|
|
223
231
|
```
|
|
224
232
|
|
|
225
233
|
### `currentPage` computation
|
|
234
|
+
|
|
226
235
|
```ts
|
|
227
236
|
currentPage: computed(() => Math.floor((searchQuery.value.skip || 0) / pageSize) + 1),
|
|
228
237
|
```
|
|
238
|
+
|
|
229
239
|
Derived from `skip` — no separate state needed.
|
|
230
240
|
|
|
231
241
|
### `pages` computation
|
|
242
|
+
|
|
232
243
|
```ts
|
|
233
244
|
pages: computed(() => Math.ceil((searchResult.value?.totalCount || 0) / pageSize)),
|
|
234
245
|
```
|
|
246
|
+
|
|
235
247
|
Total number of pages for pagination component.
|
|
236
248
|
|
|
237
249
|
---
|
|
238
250
|
|
|
239
251
|
## Composable Without Route Params
|
|
240
252
|
|
|
241
|
-
When the API doesn't need a parent ID (module is not nested under
|
|
253
|
+
When the API doesn't need a parent ID (module is not nested under an owner/store/etc.):
|
|
242
254
|
|
|
243
255
|
```ts
|
|
244
256
|
const { action: getXxxs, loading: getXxxsLoading } = useAsync<ISearchXxxQuery>(async (query) => {
|
|
@@ -34,13 +34,13 @@ export * from "./composables";
|
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
36
|
interface DashboardWidget {
|
|
37
|
-
id: string;
|
|
38
|
-
name: string;
|
|
39
|
-
component: Component;
|
|
40
|
-
size: { width: number; height: number };
|
|
37
|
+
id: string; // unique across all registered widgets
|
|
38
|
+
name: string; // display name
|
|
39
|
+
component: Component; // widget Vue component, wrapped in markRaw()
|
|
40
|
+
size: { width: number; height: number }; // grid units
|
|
41
41
|
position?: { x: number; y: number }; // optional initial grid position
|
|
42
|
-
permissions?: string[];
|
|
43
|
-
props?: Record<string, unknown>;
|
|
42
|
+
permissions?: string[]; // optional: restrict visibility by permission
|
|
43
|
+
props?: Record<string, unknown>; // optional: static props passed to component
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -66,28 +66,25 @@ The widget component is a plain Vue SFC placed in `src/modules/xxx/components/`:
|
|
|
66
66
|
</template>
|
|
67
67
|
|
|
68
68
|
<script lang="ts" setup>
|
|
69
|
-
import {
|
|
70
|
-
DashboardWidgetCard,
|
|
71
|
-
DashboardStatItem,
|
|
72
|
-
} from "@vc-shell/framework";
|
|
69
|
+
import { DashboardWidgetCard, DashboardStatItem } from "@vc-shell/framework";
|
|
73
70
|
import { ref, onMounted } from "vue";
|
|
74
71
|
import useXxxs from "../composables/useXxxs";
|
|
75
72
|
|
|
76
73
|
const { getXxxs, totalCount, loading } = useXxxs();
|
|
77
74
|
|
|
78
75
|
onMounted(async () => {
|
|
79
|
-
await getXxxs({ take: 0 });
|
|
76
|
+
await getXxxs({ take: 0 }); // fetch count only
|
|
80
77
|
});
|
|
81
78
|
</script>
|
|
82
79
|
```
|
|
83
80
|
|
|
84
81
|
### Available dashboard sub-components from `@vc-shell/framework`
|
|
85
82
|
|
|
86
|
-
| Component | Description
|
|
87
|
-
|
|
88
|
-
| `DashboardWidgetCard` | Card wrapper with title slot
|
|
89
|
-
| `DashboardStatItem` | Single stat row: label + value + loading
|
|
90
|
-
| `DashboardBarChart` | Horizontal bar chart for breakdown stats
|
|
83
|
+
| Component | Description |
|
|
84
|
+
| --------------------- | ---------------------------------------- |
|
|
85
|
+
| `DashboardWidgetCard` | Card wrapper with title slot |
|
|
86
|
+
| `DashboardStatItem` | Single stat row: label + value + loading |
|
|
87
|
+
| `DashboardBarChart` | Horizontal bar chart for breakdown stats |
|
|
91
88
|
|
|
92
89
|
---
|
|
93
90
|
|
|
@@ -101,6 +98,7 @@ src/modules/xxx/
|
|
|
101
98
|
```
|
|
102
99
|
|
|
103
100
|
`components/index.ts` barrel:
|
|
101
|
+
|
|
104
102
|
```ts
|
|
105
103
|
export { default as XxxDashboardCard } from "./XxxDashboardCard.vue";
|
|
106
104
|
```
|