@ramathibodi/nuxt-commons 0.1.49 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/form/Dialog.vue +4 -0
- package/dist/runtime/components/master/Autocomplete.vue +1 -1
- package/dist/runtime/components/model/Autocomplete.vue +12 -13
- package/dist/runtime/components/model/Table.vue +47 -3
- package/dist/runtime/composables/userPermission.d.ts +1 -0
- package/dist/runtime/composables/userPermission.js +5 -0
- package/dist/runtime/plugins/permission.js +35 -14
- package/dist/runtime/types/permission.d.ts +14 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -27,7 +27,7 @@ const module = defineNuxtModule({
|
|
|
27
27
|
src: resolver.resolve("runtime/plugins/dialogManager"),
|
|
28
28
|
mode: "client"
|
|
29
29
|
});
|
|
30
|
-
const typeFiles = ["modules", "alert", "menu", "graphqlOperation", "formDialog", "dialogManager"];
|
|
30
|
+
const typeFiles = ["modules", "alert", "menu", "graphqlOperation", "formDialog", "dialogManager", "permission"];
|
|
31
31
|
for (const file of typeFiles) {
|
|
32
32
|
addTypeTemplate({
|
|
33
33
|
src: resolver.resolve(`runtime/types/${file}.d.ts`),
|
|
@@ -13,6 +13,7 @@ interface Props {
|
|
|
13
13
|
cancelCaption?: string
|
|
14
14
|
closeCaption?: string
|
|
15
15
|
saveAndStay?: boolean
|
|
16
|
+
readonly?: boolean
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -20,6 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
20
21
|
cancelCaption: 'ยกเลิก',
|
|
21
22
|
closeCaption: 'ปิด',
|
|
22
23
|
saveAndStay: false,
|
|
24
|
+
readonly: false,
|
|
23
25
|
})
|
|
24
26
|
|
|
25
27
|
const isShowing = defineModel<boolean>({ default: false })
|
|
@@ -125,6 +127,7 @@ watch(() => isShowing.value, (newValue) => {
|
|
|
125
127
|
<form-pad
|
|
126
128
|
ref="formPadRef"
|
|
127
129
|
v-model="formData"
|
|
130
|
+
:readonly="readonly"
|
|
128
131
|
isolated
|
|
129
132
|
>
|
|
130
133
|
<template #default="slotData">
|
|
@@ -145,6 +148,7 @@ watch(() => isShowing.value, (newValue) => {
|
|
|
145
148
|
:loading="isSaving"
|
|
146
149
|
:disabled="!isDataChange"
|
|
147
150
|
@click="save"
|
|
151
|
+
v-if="!readonly"
|
|
148
152
|
>
|
|
149
153
|
{{ saveCaption }}
|
|
150
154
|
</VBtn>
|
|
@@ -102,7 +102,7 @@ const computedSortBy = computed(()=>{
|
|
|
102
102
|
>
|
|
103
103
|
<v-list-item
|
|
104
104
|
v-bind="props"
|
|
105
|
-
:title="
|
|
105
|
+
:title="item.title"
|
|
106
106
|
/>
|
|
107
107
|
</template>
|
|
108
108
|
</model-autocomplete>
|
|
@@ -97,11 +97,16 @@ watchDebounced(searchData, fuzzySearch, { debounce: 1000, maxWait: 5000 })
|
|
|
97
97
|
const computedItems = computed(()=>{
|
|
98
98
|
let sortByField = (!props.sortBy || typeof props.sortBy === "string") ? [props.sortBy || ((props.showCode) ? props.itemValue : props.itemTitle)] : props.sortBy
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
const baseItems = props.fuzzy && !isEmpty(searchData.value)
|
|
101
|
+
? items.value
|
|
102
|
+
: sortBy(items.value, sortByField)
|
|
103
|
+
|
|
104
|
+
return baseItems.map(item => ({
|
|
105
|
+
...item,
|
|
106
|
+
[props.itemTitle]: props.showCode
|
|
107
|
+
? `${item[props.itemValue]}-${item[props.itemTitle]}`
|
|
108
|
+
: item[props.itemTitle]
|
|
109
|
+
}))
|
|
105
110
|
})
|
|
106
111
|
</script>
|
|
107
112
|
|
|
@@ -126,14 +131,8 @@ const computedItems = computed(()=>{
|
|
|
126
131
|
v-bind="((slotData || {}) as object)"
|
|
127
132
|
/>
|
|
128
133
|
</template>
|
|
129
|
-
<template
|
|
130
|
-
|
|
131
|
-
#item="{ props, item }"
|
|
132
|
-
>
|
|
133
|
-
<v-list-item
|
|
134
|
-
v-bind="props"
|
|
135
|
-
:title="(showCode ? item.value+'-' : '')+item.title"
|
|
136
|
-
/>
|
|
134
|
+
<template v-if="!$slots.item" #item="{ props, item }">
|
|
135
|
+
<v-list-item v-bind="props" :title="(item as Record<string, any>)[String(props.itemTitle)]" />
|
|
137
136
|
</template>
|
|
138
137
|
<template
|
|
139
138
|
v-if="isErrorLoading"
|
|
@@ -4,8 +4,10 @@ import {VDataTable} from 'vuetify/components/VDataTable'
|
|
|
4
4
|
import {clone} from 'lodash-es'
|
|
5
5
|
import {useGraphqlModel} from '../../composables/graphqlModel'
|
|
6
6
|
import {useDialog} from "../../composables/dialog"
|
|
7
|
+
import {useUserPermission} from "../../composables/userPermission"
|
|
7
8
|
import type {GraphqlModelProps} from '../../composables/graphqlModel'
|
|
8
9
|
import type {FormDialogCallback} from "../../types/formDialog";
|
|
10
|
+
import {type AuthenticationState, useState} from "#imports";
|
|
9
11
|
|
|
10
12
|
defineOptions({
|
|
11
13
|
inheritAttrs: false,
|
|
@@ -24,6 +26,8 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataTable['$props
|
|
|
24
26
|
search?: string
|
|
25
27
|
saveAndStay?: boolean
|
|
26
28
|
stringFields?: Array<string>
|
|
29
|
+
onlyOwnerEdit?: boolean
|
|
30
|
+
onlyOwnerOverridePermission?: string | string[]
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
|
|
@@ -39,6 +43,7 @@ const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
|
|
|
39
43
|
modelBy: undefined,
|
|
40
44
|
fields: () => [],
|
|
41
45
|
stringFields: ()=>[],
|
|
46
|
+
onlyOwnerEdit: false,
|
|
42
47
|
})
|
|
43
48
|
|
|
44
49
|
const attrs = useAttrs()
|
|
@@ -48,8 +53,15 @@ const plainAttrs = computed(() => {
|
|
|
48
53
|
return returnAttrs
|
|
49
54
|
})
|
|
50
55
|
|
|
56
|
+
const authenState = useState<AuthenticationState>("authentication")
|
|
57
|
+
const currentUsername = computed(()=>{
|
|
58
|
+
let userProfile = authenState.value?.userProfile as {username: string}
|
|
59
|
+
return userProfile?.username ?? ""
|
|
60
|
+
})
|
|
61
|
+
|
|
51
62
|
const currentItem = ref<Record<string, any> | undefined>(undefined)
|
|
52
63
|
const isDialogOpen = ref<boolean>(false)
|
|
64
|
+
const isDialogReadonly = ref<boolean>(false)
|
|
53
65
|
|
|
54
66
|
const { items, itemsLength,
|
|
55
67
|
search,setSearch,
|
|
@@ -59,6 +71,15 @@ const { items, itemsLength,
|
|
|
59
71
|
isLoading } = useGraphqlModel(props)
|
|
60
72
|
|
|
61
73
|
function openDialog(item?: object) {
|
|
74
|
+
isDialogReadonly.value = false
|
|
75
|
+
currentItem.value = item
|
|
76
|
+
nextTick(() => {
|
|
77
|
+
isDialogOpen.value = true
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function openDialogReadonly(item?: object) {
|
|
82
|
+
isDialogReadonly.value = true
|
|
62
83
|
currentItem.value = item
|
|
63
84
|
nextTick(() => {
|
|
64
85
|
isDialogOpen.value = true
|
|
@@ -70,7 +91,16 @@ async function confirmDeleteItem(item: Record<string, any>, callback?: FormDialo
|
|
|
70
91
|
if (confirm) deleteItem(item,callback)
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
const
|
|
94
|
+
const canEditRow = function (item: Record<string, any>) {
|
|
95
|
+
if (props.onlyOwnerEdit) {
|
|
96
|
+
if (item?.userstampField?.createdBy && item?.userstampField?.createdBy==currentUsername.value) return true
|
|
97
|
+
if (!!props.onlyOwnerOverridePermission && useUserPermission().check(props.onlyOwnerOverridePermission)) return true
|
|
98
|
+
return !item?.userstampField?.createdBy
|
|
99
|
+
}
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const operation = ref({ openDialog, openDialogReadonly, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete, canEditRow, onlyOwnerEdit: props.onlyOwnerEdit, onlyOwnerOverridePermission: props.onlyOwnerOverridePermission })
|
|
74
104
|
|
|
75
105
|
const computedInitialData = computed(() => {
|
|
76
106
|
return Object.assign({}, props.initialData, props.modelBy)
|
|
@@ -188,14 +218,21 @@ defineExpose({ reload,operation })
|
|
|
188
218
|
#item.action="{ item }"
|
|
189
219
|
>
|
|
190
220
|
<v-btn
|
|
191
|
-
|
|
221
|
+
v-if="!canUpdate || !canEditRow(item)"
|
|
222
|
+
variant="flat"
|
|
223
|
+
density="compact"
|
|
224
|
+
icon="mdi mdi-note-search"
|
|
225
|
+
@click="openDialogReadonly(item)"
|
|
226
|
+
/>
|
|
227
|
+
<v-btn
|
|
228
|
+
v-if="canUpdate && canEditRow(item)"
|
|
192
229
|
variant="flat"
|
|
193
230
|
density="compact"
|
|
194
231
|
icon="mdi mdi-note-edit"
|
|
195
232
|
@click="openDialog(item)"
|
|
196
233
|
/>
|
|
197
234
|
<v-btn
|
|
198
|
-
v-if="canDelete"
|
|
235
|
+
v-if="canDelete && canEditRow(item)"
|
|
199
236
|
variant="flat"
|
|
200
237
|
density="compact"
|
|
201
238
|
icon="mdi mdi-delete"
|
|
@@ -253,22 +290,29 @@ defineExpose({ reload,operation })
|
|
|
253
290
|
@create="createItem"
|
|
254
291
|
@update="updateItem"
|
|
255
292
|
:saveAndStay="saveAndStay"
|
|
293
|
+
:readonly="isDialogReadonly"
|
|
256
294
|
>
|
|
257
295
|
<template #default="slotData">
|
|
258
296
|
<slot
|
|
259
297
|
name="form"
|
|
298
|
+
:tableItems="items"
|
|
299
|
+
:tableOperation="operation"
|
|
260
300
|
v-bind="slotData"
|
|
261
301
|
/>
|
|
262
302
|
</template>
|
|
263
303
|
<template #title="slotData">
|
|
264
304
|
<slot
|
|
265
305
|
name="formTitle"
|
|
306
|
+
:tableItems="items"
|
|
307
|
+
:tableOperation="operation"
|
|
266
308
|
v-bind="slotData"
|
|
267
309
|
/>
|
|
268
310
|
</template>
|
|
269
311
|
<template #action="slotData">
|
|
270
312
|
<slot
|
|
271
313
|
name="formAction"
|
|
314
|
+
:tableItems="items"
|
|
315
|
+
:tableOperation="operation"
|
|
272
316
|
v-bind="slotData"
|
|
273
317
|
/>
|
|
274
318
|
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useUserPermission: () => unknown;
|
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
import { defineNuxtPlugin } from "nuxt/app";
|
|
2
2
|
import { useAuthentication } from "#imports";
|
|
3
|
-
export default defineNuxtPlugin(
|
|
4
|
-
function
|
|
5
|
-
if (!
|
|
6
|
-
|
|
3
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
4
|
+
function normalize(input) {
|
|
5
|
+
if (!input) return [];
|
|
6
|
+
if (Array.isArray(input)) return input;
|
|
7
|
+
return [input];
|
|
8
|
+
}
|
|
9
|
+
function isBlank(input) {
|
|
10
|
+
if (!input) return true;
|
|
11
|
+
const arr = normalize(input);
|
|
12
|
+
return arr.length === 0 || arr.every((v) => v.trim() === "");
|
|
13
|
+
}
|
|
14
|
+
function check(permissionIds) {
|
|
15
|
+
if (isBlank(permissionIds)) return true;
|
|
16
|
+
const auth = useAuthentication();
|
|
17
|
+
const perms = normalize(permissionIds);
|
|
18
|
+
return perms.some((id) => auth?.hasPermission(id));
|
|
19
|
+
}
|
|
20
|
+
function checkAll(permissionIds) {
|
|
21
|
+
if (isBlank(permissionIds)) return true;
|
|
22
|
+
const auth = useAuthentication();
|
|
23
|
+
const perms = normalize(permissionIds);
|
|
24
|
+
return perms.every((id) => auth?.hasPermission(id));
|
|
7
25
|
}
|
|
8
|
-
nuxtApp.vueApp.config.globalProperties.$permission = permission;
|
|
9
26
|
nuxtApp.vueApp.directive("permission", (el, binding, vnode) => {
|
|
10
|
-
if (!
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
vnode.um.forEach((um) => {
|
|
14
|
-
um();
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
if (el.parentElement) el.parentElement.removeChild(el);
|
|
18
|
-
}
|
|
27
|
+
if (!check(binding.value)) {
|
|
28
|
+
vnode?.um?.forEach?.((um) => um());
|
|
29
|
+
el.parentElement?.removeChild(el);
|
|
19
30
|
}
|
|
20
31
|
});
|
|
32
|
+
nuxtApp.vueApp.directive("permission-all", (el, binding, vnode) => {
|
|
33
|
+
if (!checkAll(binding.value)) {
|
|
34
|
+
vnode?.um?.forEach?.((um) => um());
|
|
35
|
+
el.parentElement?.removeChild(el);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
nuxtApp.provide("permission", {
|
|
39
|
+
check,
|
|
40
|
+
checkAll
|
|
41
|
+
});
|
|
21
42
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PermissionPlugin {
|
|
2
|
+
check: (permission: string | string[] | null | undefined) => boolean
|
|
3
|
+
checkAll: (permission: string | string[] | null | undefined) => boolean
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module '#app' {
|
|
7
|
+
interface NuxtApp {
|
|
8
|
+
$permission: PermissionPlugin
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface RuntimeNuxtApp {
|
|
12
|
+
$permission: PermissionPlugin
|
|
13
|
+
}
|
|
14
|
+
}
|