@vc-shell/create-vc-app 1.1.88 → 1.1.90
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 +8 -0
- package/dist/index.js +1 -1
- package/dist/templates/base/_package.json +5 -5
- package/dist/templates/modules/classic-module/composables/use{{ModuleNamePascalCase}}Details/index.ts +24 -19
- package/dist/templates/modules/classic-module/composables/use{{ModuleNamePascalCase}}List/index.ts +8 -1
- package/dist/templates/modules/classic-module/locales/en.json +37 -28
- package/dist/templates/modules/classic-module/pages/details.vue +21 -2
- package/dist/templates/modules/classic-module/pages/list.vue +102 -7
- package/dist/templates/sample/classic-module/locales/en.json +67 -59
- package/dist/templates/sample/classic-module/pages/details.vue +6 -12
- package/dist/templates/sample/classic-module/pages/list.vue +60 -11
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [1.1.90](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.89...v1.1.90) (2025-10-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## [1.1.89](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.88...v1.1.89) (2025-10-08)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
1
9
|
## [1.1.88](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.87...v1.1.88) (2025-10-07)
|
|
2
10
|
|
|
3
11
|
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import c from "node:path";
|
|
|
6
6
|
import s from "node:fs";
|
|
7
7
|
import { fileURLToPath as Oe } from "node:url";
|
|
8
8
|
import { cwd as Me, exit as Ee, argv as Te } from "node:process";
|
|
9
|
-
const Ue = "1.1.
|
|
9
|
+
const Ue = "1.1.90", I = {
|
|
10
10
|
version: Ue
|
|
11
11
|
};
|
|
12
12
|
var Pe = typeof global == "object" && global && global.Object === Object && global, Le = typeof self == "object" && self && self.Object === Object && self, _e = Pe || Le || Function("return this")(), b = _e.Symbol, K = Object.prototype, ze = K.hasOwnProperty, De = K.toString, $ = b ? b.toStringTag : void 0;
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"@types/node": "^20.10.5",
|
|
24
24
|
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
|
25
25
|
"@typescript-eslint/parser": "^7.4.0",
|
|
26
|
-
"@vc-shell/api-client-generator": "^1.1.
|
|
27
|
-
"@vc-shell/release-config": "^1.1.
|
|
28
|
-
"@vc-shell/ts-config": "^1.1.
|
|
26
|
+
"@vc-shell/api-client-generator": "^1.1.90",
|
|
27
|
+
"@vc-shell/release-config": "^1.1.89",
|
|
28
|
+
"@vc-shell/ts-config": "^1.1.89",
|
|
29
29
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
30
30
|
"@vue/eslint-config-prettier": "^9.0.0",
|
|
31
31
|
"@vue/eslint-config-typescript": "^13.0.0",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"vue-tsc": "^2.2.10"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@vc-shell/config-generator": "^1.1.
|
|
57
|
-
"@vc-shell/framework": "^1.1.
|
|
56
|
+
"@vc-shell/config-generator": "^1.1.89",
|
|
57
|
+
"@vc-shell/framework": "^1.1.90",
|
|
58
58
|
"@vueuse/core": "^10.7.1",
|
|
59
59
|
"@vueuse/integrations": "^10.7.1",
|
|
60
60
|
"cross-spawn": "^7.0.3",
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import { computed, ref } from "vue";
|
|
2
|
-
import { useAsync, useLoading } from "@vc-shell/framework";
|
|
3
|
-
|
|
4
|
-
export default () => {
|
|
5
|
-
const item = ref({});
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import { useAsync, useLoading, useModificationTracker } from "@vc-shell/framework";
|
|
3
|
+
|
|
4
|
+
export default () => {
|
|
5
|
+
const item = ref({});
|
|
6
|
+
|
|
7
|
+
const { isModified, currentValue, resetModificationState } = useModificationTracker(item);
|
|
8
|
+
|
|
9
|
+
// Implement your own load function
|
|
10
|
+
const { loading: itemLoading, action: getItem } = useAsync<{ id: string }>(async (payload) => {
|
|
11
|
+
item.value = {};
|
|
12
|
+
|
|
13
|
+
resetModificationState();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const loading = useLoading(itemLoading);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
item: currentValue,
|
|
20
|
+
loading: computed(() => loading.value),
|
|
21
|
+
getItem,
|
|
22
|
+
isModified,
|
|
23
|
+
};
|
|
24
|
+
};
|
package/dist/templates/modules/classic-module/composables/use{{ModuleNamePascalCase}}List/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ interface SearchQuery {
|
|
|
6
6
|
take?: number;
|
|
7
7
|
skip?: number;
|
|
8
8
|
sort?: string;
|
|
9
|
+
keyword?: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export default (options?: { pageSize?: number, sort?: string }) => {
|
|
@@ -26,7 +27,12 @@ export default (options?: { pageSize?: number, sort?: string }) => {
|
|
|
26
27
|
};
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// Implement your own remove function
|
|
31
|
+
const { loading: removeLoading, action: removeItems } = useAsync<{ ids: string[] }>(async (payload) => {
|
|
32
|
+
return;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const loading = useLoading(itemLoading, removeLoading);
|
|
30
36
|
|
|
31
37
|
return {
|
|
32
38
|
data: computed(() => searchResult.value?.items),
|
|
@@ -35,6 +41,7 @@ export default (options?: { pageSize?: number, sort?: string }) => {
|
|
|
35
41
|
pages: computed(() => Math.ceil((searchResult.value?.totalCount || 1) / pageSize)),
|
|
36
42
|
currentPage: computed(() => Math.ceil((searchQuery.value?.skip || 0) / Math.max(1, pageSize) + 1)),
|
|
37
43
|
getItems,
|
|
44
|
+
removeItems,
|
|
38
45
|
searchQuery,
|
|
39
46
|
};
|
|
40
47
|
};
|
|
@@ -1,28 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"{{ModuleNameUppercaseSnakeCase}}": {
|
|
3
|
-
"MENU": {
|
|
4
|
-
"TITLE": "{{ModuleNameSentenceCase}}"
|
|
5
|
-
},
|
|
6
|
-
"PAGES": {
|
|
7
|
-
"LIST": {
|
|
8
|
-
"TITLE": "{{ModuleNameSentenceCase}} list",
|
|
9
|
-
"TOOLBAR": {
|
|
10
|
-
"REFRESH": "Refresh"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"{{ModuleNameUppercaseSnakeCase}}": {
|
|
3
|
+
"MENU": {
|
|
4
|
+
"TITLE": "{{ModuleNameSentenceCase}}"
|
|
5
|
+
},
|
|
6
|
+
"PAGES": {
|
|
7
|
+
"LIST": {
|
|
8
|
+
"TITLE": "{{ModuleNameSentenceCase}} list",
|
|
9
|
+
"TOOLBAR": {
|
|
10
|
+
"REFRESH": "Refresh",
|
|
11
|
+
"ADD": "Add"
|
|
12
|
+
},
|
|
13
|
+
"SEARCH": {
|
|
14
|
+
"PLACEHOLDER": "Search keywords"
|
|
15
|
+
},
|
|
16
|
+
"TABLE": {
|
|
17
|
+
"TOTALS": "Count:",
|
|
18
|
+
"HEADER": {}
|
|
19
|
+
},
|
|
20
|
+
"EMPTY": {
|
|
21
|
+
"NO_ITEMS": "No items found",
|
|
22
|
+
"ADD": "Add items"
|
|
23
|
+
},
|
|
24
|
+
"NOT_FOUND": {
|
|
25
|
+
"EMPTY": "No items found",
|
|
26
|
+
"RESET": "Reset"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"DETAILS": {
|
|
30
|
+
"TITLE": "{{ModuleNameSentenceCase}} details"
|
|
31
|
+
},
|
|
32
|
+
"ALERTS": {
|
|
33
|
+
"CLOSE_CONFIRMATION": "You have unsaved changes. Close anyway?"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
</template>
|
|
20
20
|
|
|
21
21
|
<script lang="ts" setup>
|
|
22
|
-
import { IBladeToolbar, IParentCallArgs } from "@vc-shell/framework";
|
|
22
|
+
import { IBladeToolbar, IParentCallArgs, useBladeNavigation, usePopup, useBeforeUnload } from "@vc-shell/framework";
|
|
23
23
|
import { use{{ModuleNamePascalCase}}Details } from "./../composables";
|
|
24
24
|
import { computed, onMounted, ref } from "vue";
|
|
25
25
|
import { useI18n } from "vue-i18n";
|
|
26
|
+
import { useForm } from "vee-validate";
|
|
26
27
|
|
|
27
28
|
export interface Props {
|
|
28
29
|
expanded?: boolean;
|
|
@@ -50,9 +51,19 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
50
51
|
|
|
51
52
|
defineEmits<Emits>();
|
|
52
53
|
|
|
53
|
-
const { loading, getItem } = use{{ModuleNamePascalCase}}Details();
|
|
54
|
+
const { loading, getItem, isModified } = use{{ModuleNamePascalCase}}Details();
|
|
55
|
+
const { onBeforeClose } = useBladeNavigation();
|
|
56
|
+
const { showConfirmation } = usePopup();
|
|
54
57
|
const { t } = useI18n({ useScope: "global" });
|
|
55
58
|
|
|
59
|
+
const { meta } = useForm({
|
|
60
|
+
validateOnMount: false,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const isDisabled = computed(() => {
|
|
64
|
+
return !meta.value.dirty || !meta.value.valid;
|
|
65
|
+
});
|
|
66
|
+
|
|
56
67
|
const bladeToolbar = ref<IBladeToolbar[]>([]);
|
|
57
68
|
const title = computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.DETAILS.TITLE"));
|
|
58
69
|
|
|
@@ -62,6 +73,14 @@ onMounted(async () => {
|
|
|
62
73
|
}
|
|
63
74
|
});
|
|
64
75
|
|
|
76
|
+
onBeforeClose(async () => {
|
|
77
|
+
if (!isDisabled.value && isModified.value) {
|
|
78
|
+
return await showConfirmation(t("SAMPLE_APP.PAGES.ALERTS.CLOSE_CONFIRMATION"));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
useBeforeUnload(computed(() => !isDisabled.value && isModified.value));
|
|
83
|
+
|
|
65
84
|
defineExpose({
|
|
66
85
|
title,
|
|
67
86
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<VcBlade
|
|
3
|
-
:title="
|
|
3
|
+
:title="title"
|
|
4
4
|
width="50%"
|
|
5
5
|
:expanded="expanded"
|
|
6
6
|
:closable="closable"
|
|
@@ -18,17 +18,24 @@
|
|
|
18
18
|
:loading="loading"
|
|
19
19
|
:columns="columns"
|
|
20
20
|
:sort="sortExpression"
|
|
21
|
+
:current-page="currentPage"
|
|
22
|
+
:search-value="searchValue"
|
|
23
|
+
enable-item-actions
|
|
24
|
+
:item-action-builder="actionBuilder"
|
|
21
25
|
:pages="pages"
|
|
26
|
+
:empty="empty"
|
|
27
|
+
:notfound="notfound"
|
|
22
28
|
:total-count="totalCount"
|
|
23
|
-
:
|
|
24
|
-
:current-page="currentPage"
|
|
29
|
+
:selected-item-id="selectedItemId"
|
|
25
30
|
:search-placeholder="$t('{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.SEARCH.PLACEHOLDER')"
|
|
26
31
|
:total-label="$t('{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TABLE.TOTALS')"
|
|
27
|
-
:selected-item-id="selectedItemId"
|
|
28
32
|
state-key="{{ModuleNameUppercaseSnakeCase}}"
|
|
29
33
|
:items="data"
|
|
30
34
|
@item-click="onItemClick"
|
|
31
35
|
@header-click="onHeaderClick"
|
|
36
|
+
@search:change="onSearchList"
|
|
37
|
+
@pagination-click="onPaginationClick"
|
|
38
|
+
@selection-changed="onSelectionChanged"
|
|
32
39
|
>
|
|
33
40
|
</VcTable>
|
|
34
41
|
</VcBlade>
|
|
@@ -36,7 +43,7 @@
|
|
|
36
43
|
|
|
37
44
|
<script lang="ts" setup>
|
|
38
45
|
import { computed, ref, markRaw, onMounted, watch } from "vue";
|
|
39
|
-
import { IBladeToolbar, IParentCallArgs, ITableColumns, useBladeNavigation, useTableSort } from "@vc-shell/framework";
|
|
46
|
+
import { IBladeToolbar, IParentCallArgs, ITableColumns, useBladeNavigation, useTableSort, IActionBuilderResult, useFunctions, usePopup } from "@vc-shell/framework";
|
|
40
47
|
import { useI18n } from "vue-i18n";
|
|
41
48
|
import { use{{ModuleNamePascalCase}}List } from "./../composables";
|
|
42
49
|
import Details from "./details.vue";
|
|
@@ -75,19 +82,43 @@ defineEmits<Emits>();
|
|
|
75
82
|
|
|
76
83
|
const { t } = useI18n({ useScope: "global" });
|
|
77
84
|
const { openBlade } = useBladeNavigation();
|
|
78
|
-
|
|
85
|
+
const { debounce } = useFunctions();
|
|
86
|
+
const { showConfirmation } = usePopup();
|
|
79
87
|
const { sortExpression, handleSortChange: tableSortHandler } = useTableSort({
|
|
80
88
|
initialDirection: "DESC",
|
|
81
89
|
initialProperty: "createdDate",
|
|
82
90
|
});
|
|
83
91
|
|
|
84
|
-
const { getItems, data, loading, totalCount, pages, currentPage, searchQuery } = use{{ModuleNamePascalCase}}List({
|
|
92
|
+
const { getItems, data, loading, totalCount, pages, currentPage, searchQuery, removeItems } = use{{ModuleNamePascalCase}}List({
|
|
85
93
|
sort: sortExpression.value,
|
|
86
94
|
pageSize: 20,
|
|
87
95
|
});
|
|
88
96
|
|
|
89
97
|
const searchValue = ref();
|
|
90
98
|
const selectedItemId = ref<string>();
|
|
99
|
+
const selectedItemsIds = ref<string[]>([]);
|
|
100
|
+
|
|
101
|
+
const empty = {
|
|
102
|
+
icon: "lucide-file",
|
|
103
|
+
text: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.EMPTY.NO_ITEMS")),
|
|
104
|
+
action: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.EMPTY.ADD")),
|
|
105
|
+
clickHandler: () => {
|
|
106
|
+
addItem();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const notfound = {
|
|
111
|
+
icon: "lucide-file",
|
|
112
|
+
text: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.NOT_FOUND.EMPTY")),
|
|
113
|
+
action: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.NOT_FOUND.RESET")),
|
|
114
|
+
clickHandler: async () => {
|
|
115
|
+
searchValue.value = "";
|
|
116
|
+
await getItems({
|
|
117
|
+
...searchQuery.value,
|
|
118
|
+
keyword: "",
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
91
122
|
|
|
92
123
|
watch(
|
|
93
124
|
() => props.param,
|
|
@@ -113,6 +144,14 @@ const bladeToolbar = ref<IBladeToolbar[]>([
|
|
|
113
144
|
await reload();
|
|
114
145
|
},
|
|
115
146
|
},
|
|
147
|
+
{
|
|
148
|
+
id: "add",
|
|
149
|
+
icon: "material-add",
|
|
150
|
+
title: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TOOLBAR.ADD")),
|
|
151
|
+
clickHandler: () => {
|
|
152
|
+
addItem();
|
|
153
|
+
},
|
|
154
|
+
},
|
|
116
155
|
]);
|
|
117
156
|
|
|
118
157
|
const columns = ref<ITableColumns[]>([
|
|
@@ -121,6 +160,14 @@ const columns = ref<ITableColumns[]>([
|
|
|
121
160
|
|
|
122
161
|
const title = computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TITLE"));
|
|
123
162
|
|
|
163
|
+
const onSearchList = debounce(async (keyword: string) => {
|
|
164
|
+
searchValue.value = keyword;
|
|
165
|
+
await getItems({
|
|
166
|
+
...searchQuery.value,
|
|
167
|
+
keyword,
|
|
168
|
+
});
|
|
169
|
+
}, 1000);
|
|
170
|
+
|
|
124
171
|
const reload = async () => {
|
|
125
172
|
await getItems({
|
|
126
173
|
...searchQuery.value,
|
|
@@ -129,6 +176,12 @@ const reload = async () => {
|
|
|
129
176
|
});
|
|
130
177
|
};
|
|
131
178
|
|
|
179
|
+
const addItem = () => {
|
|
180
|
+
openBlade({
|
|
181
|
+
blade: markRaw(Details),
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
132
185
|
const onItemClick = (item: { id: string }) => {
|
|
133
186
|
openBlade({
|
|
134
187
|
blade: markRaw(Details),
|
|
@@ -146,6 +199,48 @@ const onHeaderClick = (item: ITableColumns) => {
|
|
|
146
199
|
tableSortHandler(item.id);
|
|
147
200
|
};
|
|
148
201
|
|
|
202
|
+
const actionBuilder = (): IActionBuilderResult[] => {
|
|
203
|
+
const result: IActionBuilderResult[] = [];
|
|
204
|
+
result.push({
|
|
205
|
+
icon: "material-delete",
|
|
206
|
+
title: "Delete",
|
|
207
|
+
type: "danger",
|
|
208
|
+
async clickHandler(_item: { id: string }) {
|
|
209
|
+
if (_item.id && !selectedItemsIds.value.includes(_item.id)) {
|
|
210
|
+
selectedItemsIds.value.push(_item.id);
|
|
211
|
+
}
|
|
212
|
+
await remove(selectedItemsIds.value);
|
|
213
|
+
selectedItemsIds.value = [];
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const onPaginationClick = async (page: number) => {
|
|
221
|
+
await getItems({
|
|
222
|
+
...searchQuery.value,
|
|
223
|
+
skip: (page - 1) * (searchQuery.value.take ?? 20),
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const onSelectionChanged = (items: Record<string, any>[]) => {
|
|
228
|
+
selectedItemsIds.value = items.flatMap((item) => (item.id ? [item.id] : []));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
async function remove(ids: string[]) {
|
|
232
|
+
if (
|
|
233
|
+
await showConfirmation(
|
|
234
|
+
t(`{{ModuleNameUppercaseSnakeCase}}.PAGES.ALERTS.DELETE_SELECTED_CONFIRMATION.MESSAGE`, {
|
|
235
|
+
count: selectedItemsIds.value.length,
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
await removeItems({ ids });
|
|
240
|
+
await reload();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
149
244
|
watch(
|
|
150
245
|
() => sortExpression.value,
|
|
151
246
|
async (newVal) => {
|
|
@@ -1,59 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"SAMPLE_APP": {
|
|
3
|
-
"MENU": {
|
|
4
|
-
"TITLE": "Sample"
|
|
5
|
-
},
|
|
6
|
-
"PAGES": {
|
|
7
|
-
"LIST": {
|
|
8
|
-
"TITLE": "Sample list",
|
|
9
|
-
"TOOLBAR": {
|
|
10
|
-
"REFRESH": "Refresh",
|
|
11
|
-
"REMOVE": "Remove"
|
|
12
|
-
},
|
|
13
|
-
"SEARCH": {
|
|
14
|
-
"PLACEHOLDER": "Search keywords"
|
|
15
|
-
},
|
|
16
|
-
"TABLE": {
|
|
17
|
-
"TOTALS": "Count:",
|
|
18
|
-
"HEADER": {
|
|
19
|
-
"IMAGE": "Img",
|
|
20
|
-
"PRODUCT_NAME": "Product name",
|
|
21
|
-
"DESCRIPTION": "Description",
|
|
22
|
-
"PRICE": "Price",
|
|
23
|
-
"SALE_PRICE": "Sale price",
|
|
24
|
-
"CURRENCY": "Currency"
|
|
25
|
-
},
|
|
26
|
-
"ACTIONS": {
|
|
27
|
-
"DELETE": "Delete"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
{
|
|
2
|
+
"SAMPLE_APP": {
|
|
3
|
+
"MENU": {
|
|
4
|
+
"TITLE": "Sample"
|
|
5
|
+
},
|
|
6
|
+
"PAGES": {
|
|
7
|
+
"LIST": {
|
|
8
|
+
"TITLE": "Sample list",
|
|
9
|
+
"TOOLBAR": {
|
|
10
|
+
"REFRESH": "Refresh",
|
|
11
|
+
"REMOVE": "Remove"
|
|
12
|
+
},
|
|
13
|
+
"SEARCH": {
|
|
14
|
+
"PLACEHOLDER": "Search keywords"
|
|
15
|
+
},
|
|
16
|
+
"TABLE": {
|
|
17
|
+
"TOTALS": "Count:",
|
|
18
|
+
"HEADER": {
|
|
19
|
+
"IMAGE": "Img",
|
|
20
|
+
"PRODUCT_NAME": "Product name",
|
|
21
|
+
"DESCRIPTION": "Description",
|
|
22
|
+
"PRICE": "Price",
|
|
23
|
+
"SALE_PRICE": "Sale price",
|
|
24
|
+
"CURRENCY": "Currency"
|
|
25
|
+
},
|
|
26
|
+
"ACTIONS": {
|
|
27
|
+
"DELETE": "Delete"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"EMPTY": {
|
|
31
|
+
"NO_ITEMS": "No items found",
|
|
32
|
+
"ADD": "Add item"
|
|
33
|
+
},
|
|
34
|
+
"NOT_FOUND": {
|
|
35
|
+
"EMPTY": "No items found",
|
|
36
|
+
"RESET": "Reset"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"DETAILS": {
|
|
40
|
+
"TITLE": {
|
|
41
|
+
"DETAILS": " details",
|
|
42
|
+
"LOADING": "Loading..."
|
|
43
|
+
},
|
|
44
|
+
"TOOLBAR": {
|
|
45
|
+
"SAVE": "Save",
|
|
46
|
+
"DELETE": "Delete"
|
|
47
|
+
},
|
|
48
|
+
"FIELDS": {
|
|
49
|
+
"NAME": "Name",
|
|
50
|
+
"CONTENT": "Content",
|
|
51
|
+
"GUID": "GUID",
|
|
52
|
+
"DESCRIPTION": "Description",
|
|
53
|
+
"PRICE": "Price",
|
|
54
|
+
"SALE_PRICE": "Sale price"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"ALERTS": {
|
|
58
|
+
"CLOSE_CONFIRMATION": "You have unsaved changes. Close anyway?",
|
|
59
|
+
"DELETE": "Are you sure you want to delete this item?",
|
|
60
|
+
"DELETE_SELECTED_CONFIRMATION": {
|
|
61
|
+
"MESSAGE": "Are you sure you want to delete {count} selected items?",
|
|
62
|
+
"ALL": "all {totalCount}"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
@collapse="$emit('collapse:blade')"
|
|
12
12
|
>
|
|
13
13
|
<VcContainer class="tw-p-2">
|
|
14
|
-
<VcForm>
|
|
14
|
+
<VcForm class="tw-space-y-4">
|
|
15
15
|
<Field
|
|
16
16
|
v-slot="{ errorMessage, handleChange, errors }"
|
|
17
17
|
name="name"
|
|
@@ -25,15 +25,11 @@
|
|
|
25
25
|
required
|
|
26
26
|
:error="!!errors.length"
|
|
27
27
|
:error-message="errorMessage"
|
|
28
|
-
class="tw-mb-4"
|
|
29
28
|
@update:model-value="handleChange"
|
|
30
29
|
></VcInput>
|
|
31
30
|
</Field>
|
|
32
|
-
<VcCard
|
|
33
|
-
|
|
34
|
-
class="tw-mb-4"
|
|
35
|
-
>
|
|
36
|
-
<div class="tw-p-4">
|
|
31
|
+
<VcCard header="Content">
|
|
32
|
+
<div class="tw-p-4 tw-space-y-4">
|
|
37
33
|
<Field
|
|
38
34
|
v-slot="{ errorMessage, handleChange, errors }"
|
|
39
35
|
name="guid"
|
|
@@ -50,7 +46,6 @@
|
|
|
50
46
|
required
|
|
51
47
|
:error="!!errors.length"
|
|
52
48
|
:error-message="errorMessage"
|
|
53
|
-
class="tw-mb-4"
|
|
54
49
|
@update:model-value="handleChange"
|
|
55
50
|
></VcInput>
|
|
56
51
|
</Field>
|
|
@@ -137,11 +132,10 @@
|
|
|
137
132
|
<script lang="ts" setup>
|
|
138
133
|
import { IBladeToolbar, IParentCallArgs, useBeforeUnload, useBladeNavigation, usePopup } from "@vc-shell/framework";
|
|
139
134
|
import { useDetails } from "./../composables";
|
|
140
|
-
import { computed, onMounted, ref
|
|
141
|
-
import { Field, useForm
|
|
135
|
+
import { computed, onMounted, ref } from "vue";
|
|
136
|
+
import { Field, useForm } from "vee-validate";
|
|
142
137
|
import { useI18n } from "vue-i18n";
|
|
143
138
|
import * as _ from "lodash-es";
|
|
144
|
-
import { MockedItem } from "../sample-data";
|
|
145
139
|
|
|
146
140
|
export interface Props {
|
|
147
141
|
expanded?: boolean;
|
|
@@ -210,7 +204,7 @@ const bladeToolbar = ref<IBladeToolbar[]>([
|
|
|
210
204
|
icon: "material-delete",
|
|
211
205
|
title: "Delete",
|
|
212
206
|
async clickHandler() {
|
|
213
|
-
if (await showConfirmation(
|
|
207
|
+
if (await showConfirmation(t(`SAMPLE_APP.PAGES.ALERTS.DELETE`))) {
|
|
214
208
|
if (props.param) {
|
|
215
209
|
await removeItem({ id: props.param });
|
|
216
210
|
emit("parent:call", {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<VcBlade
|
|
3
|
-
:title="
|
|
3
|
+
:title="title"
|
|
4
4
|
width="50%"
|
|
5
5
|
:expanded="expanded"
|
|
6
6
|
:closable="closable"
|
|
@@ -22,16 +22,20 @@
|
|
|
22
22
|
:total-count="totalCount"
|
|
23
23
|
:search-value="searchValue"
|
|
24
24
|
:current-page="currentPage"
|
|
25
|
+
enable-item-actions
|
|
26
|
+
:item-action-builder="actionBuilder"
|
|
27
|
+
:empty="empty"
|
|
28
|
+
:notfound="notfound"
|
|
25
29
|
:search-placeholder="$t('SAMPLE_APP.PAGES.LIST.SEARCH.PLACEHOLDER')"
|
|
26
30
|
:total-label="$t('SAMPLE_APP.PAGES.LIST.TABLE.TOTALS')"
|
|
27
31
|
:selected-item-id="selectedItemId"
|
|
28
32
|
state-key="SAMPLE_APP"
|
|
29
|
-
:items="data"
|
|
30
|
-
:item-action-builder="actionBuilder"
|
|
31
|
-
enable-item-actions
|
|
33
|
+
:items="data ?? []"
|
|
32
34
|
@item-click="onItemClick"
|
|
33
35
|
@header-click="onHeaderClick"
|
|
34
36
|
@selection-changed="onSelectionChanged"
|
|
37
|
+
@search:change="onSearchList"
|
|
38
|
+
@pagination-click="onPaginationClick"
|
|
35
39
|
>
|
|
36
40
|
</VcTable>
|
|
37
41
|
</VcBlade>
|
|
@@ -47,6 +51,7 @@ import {
|
|
|
47
51
|
useBladeNavigation,
|
|
48
52
|
usePopup,
|
|
49
53
|
useTableSort,
|
|
54
|
+
useFunctions,
|
|
50
55
|
} from "@vc-shell/framework";
|
|
51
56
|
import { useI18n } from "vue-i18n";
|
|
52
57
|
import { useList } from "./../composables";
|
|
@@ -88,6 +93,7 @@ defineEmits<Emits>();
|
|
|
88
93
|
const { t } = useI18n({ useScope: "global" });
|
|
89
94
|
const { openBlade } = useBladeNavigation();
|
|
90
95
|
const { showConfirmation } = usePopup();
|
|
96
|
+
const { debounce } = useFunctions();
|
|
91
97
|
|
|
92
98
|
const { sortExpression, handleSortChange: tableSortHandler } = useTableSort({
|
|
93
99
|
initialDirection: "DESC",
|
|
@@ -103,10 +109,32 @@ const searchValue = ref();
|
|
|
103
109
|
const selectedItemId = ref<string>();
|
|
104
110
|
const selectedIds = ref<string[]>([]);
|
|
105
111
|
|
|
112
|
+
const empty = {
|
|
113
|
+
icon: "lucide-file",
|
|
114
|
+
text: computed(() => t("SAMPLE_APP.PAGES.LIST.EMPTY.NO_ITEMS")),
|
|
115
|
+
action: computed(() => t("SAMPLE_APP.PAGES.LIST.EMPTY.ADD")),
|
|
116
|
+
clickHandler: () => {
|
|
117
|
+
addItem();
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const notfound = {
|
|
122
|
+
icon: "lucide-file",
|
|
123
|
+
text: computed(() => t("SAMPLE_APP.PAGES.LIST.NOT_FOUND.EMPTY")),
|
|
124
|
+
action: computed(() => t("SAMPLE_APP.PAGES.LIST.NOT_FOUND.RESET")),
|
|
125
|
+
clickHandler: async () => {
|
|
126
|
+
searchValue.value = "";
|
|
127
|
+
await getItems({
|
|
128
|
+
...searchQuery.value,
|
|
129
|
+
keyword: "",
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
106
134
|
watch(
|
|
107
135
|
() => props.param,
|
|
108
136
|
(newVal) => {
|
|
109
|
-
|
|
137
|
+
selectedItemId.value = newVal;
|
|
110
138
|
},
|
|
111
139
|
{ immediate: true },
|
|
112
140
|
);
|
|
@@ -118,6 +146,27 @@ onMounted(async () => {
|
|
|
118
146
|
});
|
|
119
147
|
});
|
|
120
148
|
|
|
149
|
+
const onSearchList = debounce(async (keyword: string) => {
|
|
150
|
+
searchValue.value = keyword;
|
|
151
|
+
await getItems({
|
|
152
|
+
...searchQuery.value,
|
|
153
|
+
keyword,
|
|
154
|
+
});
|
|
155
|
+
}, 1000);
|
|
156
|
+
|
|
157
|
+
const addItem = () => {
|
|
158
|
+
openBlade({
|
|
159
|
+
blade: markRaw(Details),
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const onPaginationClick = async (page: number) => {
|
|
164
|
+
await getItems({
|
|
165
|
+
...searchQuery.value,
|
|
166
|
+
skip: (page - 1) * (searchQuery.value.take ?? 20),
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
121
170
|
const bladeToolbar = ref<IBladeToolbar[]>([
|
|
122
171
|
{
|
|
123
172
|
id: "refresh",
|
|
@@ -171,7 +220,7 @@ const columns = ref<ITableColumns[]>([
|
|
|
171
220
|
},
|
|
172
221
|
]);
|
|
173
222
|
|
|
174
|
-
const title = computed(() => t("SAMPLE_APP.PAGES.LIST.
|
|
223
|
+
const title = computed(() => t("SAMPLE_APP.PAGES.LIST.TITLE"));
|
|
175
224
|
|
|
176
225
|
const reload = async () => {
|
|
177
226
|
await getItems({
|
|
@@ -208,12 +257,12 @@ const actionBuilder = (): IActionBuilderResult[] => {
|
|
|
208
257
|
icon: "material-delete",
|
|
209
258
|
title: computed(() => t("SAMPLE_APP.PAGES.LIST.TABLE.ACTIONS.DELETE")),
|
|
210
259
|
type: "danger",
|
|
211
|
-
clickHandler(
|
|
212
|
-
if (
|
|
213
|
-
if (!selectedIds.value.includes(
|
|
214
|
-
selectedIds.value.push(
|
|
260
|
+
async clickHandler(_item: MockedItem) {
|
|
261
|
+
if (_item.id) {
|
|
262
|
+
if (!selectedIds.value.includes(_item.id)) {
|
|
263
|
+
selectedIds.value.push(_item.id);
|
|
215
264
|
}
|
|
216
|
-
remove(selectedIds.value);
|
|
265
|
+
await remove(selectedIds.value);
|
|
217
266
|
selectedIds.value = [];
|
|
218
267
|
}
|
|
219
268
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/create-vc-app",
|
|
3
3
|
"description": "Application scaffolding",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.90",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./dist/index.js",
|
|
7
7
|
"files": [
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "vite build && yarn postbuild",
|
|
12
|
+
"type-check": "vue-tsc --noEmit",
|
|
12
13
|
"postbuild": "shx cp -R src/templates dist/"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
15
16
|
"@types/prompts": "^2.4.4",
|
|
16
|
-
"@vc-shell/ts-config": "^1.1.
|
|
17
|
+
"@vc-shell/ts-config": "^1.1.90",
|
|
17
18
|
"copyfiles": "^2.4.1",
|
|
18
19
|
"cross-env": "^7.0.3",
|
|
19
20
|
"shx": "^0.3.4",
|