@ramathibodi/nuxt-commons 0.1.20 → 0.1.22
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/runtime/components/FileBtn.vue +4 -2
- package/dist/runtime/components/form/SignPad.vue +30 -30
- package/dist/runtime/components/form/images/Field.vue +187 -0
- package/dist/runtime/components/form/images/Pad.vue +43 -0
- package/dist/runtime/components/model/Table.vue +20 -12
- package/dist/runtime/components/model/iterator.vue +24 -14
- package/dist/runtime/composables/graphqlModel.d.ts +1 -0
- package/dist/runtime/composables/graphqlModel.mjs +8 -3
- package/dist/runtime/composables/menu.mjs +2 -1
- package/dist/runtime/types/menu.d.ts +3 -0
- package/package.json +3 -2
- package/templates/.codegen/plugin-schema-object.js +2 -2
- package/dist/runtime/plugins/vueSignaturePad.d.ts +0 -2
- package/dist/runtime/plugins/vueSignaturePad.mjs +0 -5
package/dist/module.json
CHANGED
|
@@ -29,9 +29,10 @@ const reset = () => {
|
|
|
29
29
|
files.value = undefined
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const emitFiles = () => {
|
|
33
33
|
emit('update:modelValue', files.value)
|
|
34
|
-
|
|
34
|
+
files.value = []
|
|
35
|
+
}
|
|
35
36
|
|
|
36
37
|
defineExpose({ reset })
|
|
37
38
|
</script>
|
|
@@ -55,6 +56,7 @@ defineExpose({ reset })
|
|
|
55
56
|
<v-file-input
|
|
56
57
|
ref="fileInput"
|
|
57
58
|
v-model="files"
|
|
59
|
+
@update:modelValue="emitFiles"
|
|
58
60
|
:accept="props.accept"
|
|
59
61
|
:multiple="props.multiple"
|
|
60
62
|
style="display: none"
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { VueSignaturePad } from 'vue-signature-pad'
|
|
3
|
-
import { type Ref, ref, onMounted, onBeforeUnmount
|
|
2
|
+
import { VueSignaturePad } from 'vue-signature-pad';
|
|
3
|
+
import { type Ref, ref, onMounted, onBeforeUnmount } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface SignatureOptions {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
penColor: string
|
|
7
|
+
minWidth?: number
|
|
8
|
+
maxWidth?: number
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface SignatureProps {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
title?: string
|
|
13
|
+
titleConfirm?: string
|
|
14
|
+
modelValue?: string
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const props = withDefaults(defineProps<SignatureProps>(), {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
title: 'Draw Your Signature',
|
|
19
|
+
titleConfirm: 'I Accept My Signature',
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
const signaturePadRef: Ref<any> = ref(null)
|
|
@@ -28,54 +28,54 @@ const signatureOptions: Ref<SignatureOptions> = ref({ penColor: defaultColor, mi
|
|
|
28
28
|
const isDialogOpen: Ref<boolean> = ref(false)
|
|
29
29
|
|
|
30
30
|
const undoSignature = (): void => {
|
|
31
|
-
|
|
31
|
+
signaturePadRef.value.undoSignature()
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const clearSignature = (): void => {
|
|
35
|
-
|
|
35
|
+
signaturePadRef.value.clearSignature()
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const closeDialog = (): void => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
isDialogOpen.value = false
|
|
40
|
+
signaturePadRef.value.clearSignature()
|
|
41
|
+
signaturePadRef.value.clearCacheImages()
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const emit = defineEmits<{
|
|
45
|
-
|
|
45
|
+
(event: 'update:modelValue', value: string): void
|
|
46
46
|
}>()
|
|
47
47
|
|
|
48
48
|
const saveSignature = (): void => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
isDialogOpen.value = false
|
|
50
|
+
const { isEmpty, data } = signaturePadRef.value.saveSignature()
|
|
51
|
+
signatureData.value = isEmpty ? '' : data
|
|
52
|
+
emit('update:modelValue', signatureData.value)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const changePenColor = (color: string): void => {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
selectedColor.value = color
|
|
57
|
+
signatureOptions.value = { penColor: color }
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const openSignatureDialog = (): void => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
selectedColor.value = defaultColor
|
|
62
|
+
signatureOptions.value = { penColor: defaultColor }
|
|
63
|
+
isDialogOpen.value = true
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const signaturePadHeight: Ref<string> = ref('')
|
|
67
67
|
const updateSignaturePadHeight = () => {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const screenHeight = window.innerHeight
|
|
69
|
+
signaturePadHeight.value = `${screenHeight * 0.4}px`
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
onMounted(() => {
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
updateSignaturePadHeight()
|
|
74
|
+
window.addEventListener('resize', updateSignaturePadHeight)
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
onBeforeUnmount(() => {
|
|
78
|
-
|
|
78
|
+
window.removeEventListener('resize', updateSignaturePadHeight)
|
|
79
79
|
})
|
|
80
80
|
</script>
|
|
81
81
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { type Ref, ref, watch} from 'vue'
|
|
3
|
+
import {useAlert} from '../../../composables/alert'
|
|
4
|
+
import { isEqual } from 'lodash-es'
|
|
5
|
+
interface Props {
|
|
6
|
+
modelValue?: any;
|
|
7
|
+
readonly?: boolean;
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = defineProps<Props>();
|
|
12
|
+
const emit = defineEmits<{
|
|
13
|
+
(e: "update:modelValue", value: {}): void;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const alert = useAlert()
|
|
17
|
+
|
|
18
|
+
interface Image {
|
|
19
|
+
title: string;
|
|
20
|
+
data: string;
|
|
21
|
+
props: {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const images = ref<Image[]>([]);
|
|
25
|
+
const dialog: Ref<boolean> = ref(false);
|
|
26
|
+
const dialogUpdate: Ref<boolean> = ref(false);
|
|
27
|
+
const dataUpdate: Ref<Image> = ref({
|
|
28
|
+
title: "",
|
|
29
|
+
data: "",
|
|
30
|
+
props: {},
|
|
31
|
+
});
|
|
32
|
+
const remove = (index: number) => {
|
|
33
|
+
images.value.splice(index, 1);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const setDataUpdate = (data: Image) => {
|
|
37
|
+
dataUpdate.value = data;
|
|
38
|
+
dialogUpdate.value = true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
interface FileBase64 {
|
|
43
|
+
base64string: string;
|
|
44
|
+
mineType: string;
|
|
45
|
+
filename: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
const uploadImages: Ref<any[]> = ref([]);
|
|
50
|
+
const checkDuplicationName = async (currentImageName: string) => {
|
|
51
|
+
for (const {title} of images.value) {
|
|
52
|
+
if(isEqual(title, currentImageName)) return true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const update = async () => {
|
|
57
|
+
const duplicatedFileName = ref("")
|
|
58
|
+
for (const image of uploadImages.value) {
|
|
59
|
+
if(await checkDuplicationName(image.name)){
|
|
60
|
+
duplicatedFileName.value += `${image.name},`
|
|
61
|
+
}else{
|
|
62
|
+
const fileBase64: any = await convertFileToBase64(image);
|
|
63
|
+
if (isImage(fileBase64)) {
|
|
64
|
+
addImage(fileBase64);
|
|
65
|
+
} else {
|
|
66
|
+
alert?.addAlert({message: `ไฟล์ "${fileBase64.filename}" ไม่ใช่ไฟล์นามสกุล .jpg หรือ .jpeg`, alertType: 'error'})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
uploadImages.value = []
|
|
71
|
+
if(duplicatedFileName.value !== ""){
|
|
72
|
+
alert?.addAlert({message: `ไม่สามารถอัพโหลดไฟล์ ${duplicatedFileName.value}`, alertType: 'error'})
|
|
73
|
+
duplicatedFileName.value = ""
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const convertFileToBase64 = async (file: any) => {
|
|
77
|
+
try {
|
|
78
|
+
const readPromise: any = new Promise((resolve, reject) => {
|
|
79
|
+
const reader = new FileReader();
|
|
80
|
+
reader.onload = () => {
|
|
81
|
+
let result: any = reader.result;
|
|
82
|
+
const mineType = result.split(",")[0];
|
|
83
|
+
const base64 = result.split(",")[1];
|
|
84
|
+
resolve({
|
|
85
|
+
base64string: base64,
|
|
86
|
+
mineType: mineType,
|
|
87
|
+
filename: file.name,
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
reader.onerror = (error) => {
|
|
91
|
+
reject(error);
|
|
92
|
+
};
|
|
93
|
+
reader.readAsDataURL(file);
|
|
94
|
+
});
|
|
95
|
+
return await readPromise;
|
|
96
|
+
} catch (error: any) {
|
|
97
|
+
alert?.addAlert({message: error, alertType: 'error'})
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const isImage = (fileBase64: any) => {
|
|
101
|
+
const typeFile: string = fileBase64.mineType.substring(5, 10);
|
|
102
|
+
return typeFile === "image" ? true : false;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const addImage = (data: FileBase64) => {
|
|
106
|
+
images.value.push({
|
|
107
|
+
title: data.filename,
|
|
108
|
+
data: `${data.mineType},${data.base64string}`,
|
|
109
|
+
props: {},
|
|
110
|
+
});
|
|
111
|
+
dialog.value = false;
|
|
112
|
+
};
|
|
113
|
+
watch(images, () => {
|
|
114
|
+
emit("update:modelValue", images);
|
|
115
|
+
}, { deep: true });
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<VCard>
|
|
120
|
+
<VToolbar density="compact">
|
|
121
|
+
<VToolbarTitle>{{ label }}</VToolbarTitle>
|
|
122
|
+
<v-spacer></v-spacer>
|
|
123
|
+
<VToolbarItems v-if="!readonly">
|
|
124
|
+
<FileBtn
|
|
125
|
+
ref="fileBtn"
|
|
126
|
+
v-model="uploadImages"
|
|
127
|
+
accept=".jpg, .jpeg"
|
|
128
|
+
color="primary"
|
|
129
|
+
icon="mdi:mdi-image-plus"
|
|
130
|
+
iconOnly
|
|
131
|
+
multiple
|
|
132
|
+
variant="text"
|
|
133
|
+
@update:model-value="update"
|
|
134
|
+
/>
|
|
135
|
+
<v-btn color="primary" icon @click="dialog = true">
|
|
136
|
+
<v-icon>mdi mdi-camera-plus</v-icon>
|
|
137
|
+
</v-btn>
|
|
138
|
+
</VToolbarItems>
|
|
139
|
+
</VToolbar>
|
|
140
|
+
<VCardText>
|
|
141
|
+
<VRow dense justify="center">
|
|
142
|
+
<VCol v-for="(image, index) in images" :key="index" cols="4">
|
|
143
|
+
<VCard max-height="250">
|
|
144
|
+
<VToolbar density="compact">
|
|
145
|
+
<VToolbarTitle>
|
|
146
|
+
{{ image.title }}
|
|
147
|
+
</VToolbarTitle>
|
|
148
|
+
<VSpacer></VSpacer>
|
|
149
|
+
<VToolbarItems v-if="!readonly">
|
|
150
|
+
<v-btn icon @click="remove(index)">
|
|
151
|
+
<v-icon>mdi mdi-delete-outline</v-icon>
|
|
152
|
+
</v-btn>
|
|
153
|
+
<v-btn
|
|
154
|
+
color="primary"
|
|
155
|
+
icon
|
|
156
|
+
@click="setDataUpdate(image)"
|
|
157
|
+
>
|
|
158
|
+
<v-icon>mdi mdi-image-edit-outline</v-icon>
|
|
159
|
+
</v-btn>
|
|
160
|
+
</VToolbarItems>
|
|
161
|
+
</VToolbar>
|
|
162
|
+
<v-img
|
|
163
|
+
:src="image.data"
|
|
164
|
+
@click="setDataUpdate(image)"
|
|
165
|
+
></v-img>
|
|
166
|
+
</VCard>
|
|
167
|
+
</VCol>
|
|
168
|
+
</VRow>
|
|
169
|
+
</VCardText>
|
|
170
|
+
</VCard>
|
|
171
|
+
<VDialog
|
|
172
|
+
v-model="dialogUpdate"
|
|
173
|
+
fullscreen
|
|
174
|
+
transition="dialog-bottom-transition"
|
|
175
|
+
>
|
|
176
|
+
<FormImagesPad
|
|
177
|
+
v-model="dataUpdate.data"
|
|
178
|
+
@closedDialog="dialogUpdate = false"
|
|
179
|
+
></FormImagesPad>
|
|
180
|
+
</VDialog>
|
|
181
|
+
<VDialog v-model="dialog">
|
|
182
|
+
<FormImagesCapture
|
|
183
|
+
|
|
184
|
+
@close-dialog="dialog = false"
|
|
185
|
+
></FormImagesCapture>
|
|
186
|
+
</VDialog>
|
|
187
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, nextTick } from 'vue';
|
|
3
|
+
import Painterro from 'painterro';
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
modelValue: String
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
const emit = defineEmits(['update:modelValue', 'closedDialog'])
|
|
10
|
+
|
|
11
|
+
const setting = {
|
|
12
|
+
id: "painterroContainer",
|
|
13
|
+
defaultTool: 'brush',
|
|
14
|
+
toolbarPosition: 'top',
|
|
15
|
+
toolbarHeightPx: '50',
|
|
16
|
+
pixelizePixelSize: '5%',
|
|
17
|
+
hiddenTools: [
|
|
18
|
+
'resize',
|
|
19
|
+
'redo',
|
|
20
|
+
'bucket',
|
|
21
|
+
'clear',
|
|
22
|
+
'settings',
|
|
23
|
+
],
|
|
24
|
+
saveHandler: function (image:any, done:any) {
|
|
25
|
+
emit('update:modelValue', image.asDataURL())
|
|
26
|
+
emit('closedDialog', false)
|
|
27
|
+
done(true)
|
|
28
|
+
},
|
|
29
|
+
onClose: function () {
|
|
30
|
+
emit('closedDialog', false)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMounted(async () => {
|
|
35
|
+
await nextTick();
|
|
36
|
+
props.modelValue != "" ? Painterro(setting).show(props.modelValue) : Painterro(setting).show()
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div id="painterroContainer" style="width: 100%; height: 100dvh;"></div>
|
|
43
|
+
</template>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {computed, nextTick, ref, useAttrs} from 'vue'
|
|
3
|
-
import {VDataTable} from 'vuetify/components/VDataTable'
|
|
4
|
-
import {omit} from 'lodash-es'
|
|
5
|
-
import type {GraphqlModelProps} from '../../composables/graphqlModel'
|
|
6
|
-
import {useGraphqlModel} from '../../composables/graphqlModel'
|
|
2
|
+
import { computed,watch, nextTick, ref, useAttrs } from 'vue'
|
|
3
|
+
import { VDataTable } from 'vuetify/components/VDataTable'
|
|
4
|
+
import { omit } from 'lodash-es'
|
|
5
|
+
import type { GraphqlModelProps } from '../../composables/graphqlModel'
|
|
6
|
+
import { useGraphqlModel } from '../../composables/graphqlModel'
|
|
7
7
|
|
|
8
8
|
defineOptions({
|
|
9
9
|
inheritAttrs: false,
|
|
@@ -17,6 +17,9 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataTable['$props
|
|
|
17
17
|
toolbarColor?: string
|
|
18
18
|
importable?: boolean
|
|
19
19
|
exportable?: boolean
|
|
20
|
+
insertable?: boolean
|
|
21
|
+
searchable?: boolean
|
|
22
|
+
search?: string
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
|
|
@@ -25,6 +28,8 @@ const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
|
|
|
25
28
|
toolbarColor: 'primary',
|
|
26
29
|
importable: true,
|
|
27
30
|
exportable: true,
|
|
31
|
+
insertable: true,
|
|
32
|
+
searchable: true,
|
|
28
33
|
modelKey: 'id',
|
|
29
34
|
modelBy: undefined,
|
|
30
35
|
fields: () => [],
|
|
@@ -40,7 +45,7 @@ const currentItem = ref<Record<string, any> | undefined>(undefined)
|
|
|
40
45
|
const isDialogOpen = ref<boolean>(false)
|
|
41
46
|
|
|
42
47
|
const { items, itemsLength,
|
|
43
|
-
search,
|
|
48
|
+
search,setSearch,
|
|
44
49
|
canServerPageable, canServerSearch, canCreate, canUpdate, canDelete,
|
|
45
50
|
createItem, importItems, updateItem, deleteItem,
|
|
46
51
|
loadItems, reload,
|
|
@@ -53,12 +58,16 @@ function openDialog(item?: object) {
|
|
|
53
58
|
})
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
|
|
61
|
+
const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
|
|
57
62
|
|
|
58
63
|
const computedInitialData = computed(() => {
|
|
59
64
|
return Object.assign({}, props.initialData, props.modelBy)
|
|
60
65
|
})
|
|
61
66
|
|
|
67
|
+
watch(()=>props.search,()=>{
|
|
68
|
+
search.value = props.search
|
|
69
|
+
},{immediate:true})
|
|
70
|
+
|
|
62
71
|
defineExpose({ reload })
|
|
63
72
|
</script>
|
|
64
73
|
|
|
@@ -67,7 +76,6 @@ defineExpose({ reload })
|
|
|
67
76
|
<slot
|
|
68
77
|
name="header"
|
|
69
78
|
:items="items"
|
|
70
|
-
:search="search"
|
|
71
79
|
:operation="operation"
|
|
72
80
|
>
|
|
73
81
|
<VToolbar :color="toolbarColor">
|
|
@@ -94,7 +102,7 @@ defineExpose({ reload })
|
|
|
94
102
|
</slot>
|
|
95
103
|
</VToolbarTitle>
|
|
96
104
|
</v-col>
|
|
97
|
-
<v-col cols="5">
|
|
105
|
+
<v-col cols="5" v-if="props.searchable">
|
|
98
106
|
<slot name="search">
|
|
99
107
|
<VTextField
|
|
100
108
|
v-model="search"
|
|
@@ -112,11 +120,11 @@ defineExpose({ reload })
|
|
|
112
120
|
<VToolbarItems>
|
|
113
121
|
<slot name="toolbarItems" />
|
|
114
122
|
<ImportCSV
|
|
115
|
-
v-if="props.importable"
|
|
123
|
+
v-if="props.importable && canCreate && props.insertable"
|
|
116
124
|
icon="mdi mdi-file-upload"
|
|
117
125
|
variant="flat"
|
|
118
|
-
@import="importItems"
|
|
119
126
|
:color="toolbarColor"
|
|
127
|
+
@import="importItems"
|
|
120
128
|
/>
|
|
121
129
|
<ExportCSV
|
|
122
130
|
v-if="props.exportable && items.length"
|
|
@@ -127,7 +135,7 @@ defineExpose({ reload })
|
|
|
127
135
|
:color="toolbarColor"
|
|
128
136
|
/>
|
|
129
137
|
<VBtn
|
|
130
|
-
v-if="canCreate"
|
|
138
|
+
v-if="canCreate && props.insertable"
|
|
131
139
|
:color="toolbarColor"
|
|
132
140
|
prepend-icon="mdi mdi-plus"
|
|
133
141
|
variant="flat"
|
|
@@ -17,6 +17,9 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataIterator['$pr
|
|
|
17
17
|
toolbarColor?: string
|
|
18
18
|
importable?: boolean
|
|
19
19
|
exportable?: boolean
|
|
20
|
+
insertable?: boolean
|
|
21
|
+
searchable?: boolean
|
|
22
|
+
search?: string
|
|
20
23
|
cols?: string | number | boolean
|
|
21
24
|
xxl?: string | number | boolean
|
|
22
25
|
xl?: string | number | boolean
|
|
@@ -32,6 +35,8 @@ const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
|
|
|
32
35
|
toolbarColor: 'primary',
|
|
33
36
|
importable: false,
|
|
34
37
|
exportable: false,
|
|
38
|
+
insertable: true,
|
|
39
|
+
searchable: true,
|
|
35
40
|
modelKey: 'id',
|
|
36
41
|
modelBy: undefined,
|
|
37
42
|
fields: () => [],
|
|
@@ -54,7 +59,7 @@ const currentItem = ref<Record<string, any> | undefined>(undefined)
|
|
|
54
59
|
const isDialogOpen = ref<boolean>(false)
|
|
55
60
|
|
|
56
61
|
const { items, itemsLength,
|
|
57
|
-
search, currentOptions,
|
|
62
|
+
search, setSearch, currentOptions,
|
|
58
63
|
canServerPageable, canServerSearch, canCreate, canUpdate, canDelete,
|
|
59
64
|
createItem, importItems, updateItem, deleteItem,
|
|
60
65
|
loadItems, reload,
|
|
@@ -92,7 +97,7 @@ watch([currentPage, itemsPerPageInternal, sortBy], () => {
|
|
|
92
97
|
}
|
|
93
98
|
}, { immediate: true })
|
|
94
99
|
|
|
95
|
-
const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
|
|
100
|
+
const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
|
|
96
101
|
|
|
97
102
|
const computedInitialData = computed(() => {
|
|
98
103
|
return Object.assign({}, props.initialData, props.modelBy)
|
|
@@ -103,6 +108,10 @@ const computedSkeletonPerPage = computed(() => {
|
|
|
103
108
|
else return Number(itemsPerPageInternal.value)
|
|
104
109
|
})
|
|
105
110
|
|
|
111
|
+
watch(()=>props.search,()=>{
|
|
112
|
+
search.value = props.search
|
|
113
|
+
},{immediate:true})
|
|
114
|
+
|
|
106
115
|
defineExpose({ reload })
|
|
107
116
|
</script>
|
|
108
117
|
|
|
@@ -176,7 +185,6 @@ defineExpose({ reload })
|
|
|
176
185
|
name="header"
|
|
177
186
|
v-bind="headerProps"
|
|
178
187
|
:items="items"
|
|
179
|
-
:search="search"
|
|
180
188
|
:operation="operation"
|
|
181
189
|
>
|
|
182
190
|
<VToolbar :color="toolbarColor">
|
|
@@ -203,7 +211,7 @@ defineExpose({ reload })
|
|
|
203
211
|
</slot>
|
|
204
212
|
</VToolbarTitle>
|
|
205
213
|
</v-col>
|
|
206
|
-
<v-col cols="5">
|
|
214
|
+
<v-col cols="5" v-if="props.searchable">
|
|
207
215
|
<slot name="search">
|
|
208
216
|
<VTextField
|
|
209
217
|
v-model="search"
|
|
@@ -221,20 +229,22 @@ defineExpose({ reload })
|
|
|
221
229
|
<VToolbarItems>
|
|
222
230
|
<slot name="toolbarItems" />
|
|
223
231
|
<ImportCSV
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
232
|
+
v-if="props.importable && canCreate && props.insertable"
|
|
233
|
+
icon="mdi mdi-file-upload"
|
|
234
|
+
variant="flat"
|
|
235
|
+
:color="toolbarColor"
|
|
236
|
+
@import="importItems"
|
|
228
237
|
/>
|
|
229
238
|
<ExportCSV
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
v-if="props.exportable && items.length"
|
|
240
|
+
icon="mdi mdi-file-download"
|
|
241
|
+
variant="flat"
|
|
242
|
+
:file-name="title"
|
|
243
|
+
:model-value="items"
|
|
244
|
+
:color="toolbarColor"
|
|
235
245
|
/>
|
|
236
246
|
<VBtn
|
|
237
|
-
v-if="canCreate"
|
|
247
|
+
v-if="canCreate && props.insertable"
|
|
238
248
|
:color="toolbarColor"
|
|
239
249
|
prepend-icon="mdi mdi-plus"
|
|
240
250
|
variant="flat"
|
|
@@ -11,6 +11,7 @@ export declare function useGraphqlModel<T extends GraphqlModelProps>(props: T):
|
|
|
11
11
|
items: import("vue").Ref<Record<string, any>[]>;
|
|
12
12
|
itemsLength: import("vue").Ref<number>;
|
|
13
13
|
search: import("vue").Ref<string | undefined>;
|
|
14
|
+
setSearch: (keyword: string) => void;
|
|
14
15
|
currentOptions: import("vue").Ref<any>;
|
|
15
16
|
operationCreate: import("vue").ComputedRef<any>;
|
|
16
17
|
operationUpdate: import("vue").ComputedRef<any>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { computed, onMounted, ref
|
|
1
|
+
import { computed, onMounted, ref } from "vue";
|
|
2
|
+
import { watchDebounced } from "@vueuse/core";
|
|
2
3
|
import { useAlert } from "./alert.mjs";
|
|
3
4
|
import { buildRequiredInputFields } from "./graphqlOperation.mjs";
|
|
4
5
|
import { useGraphqlModelOperation } from "./graphqlModelOperation.mjs";
|
|
@@ -8,6 +9,9 @@ export function useGraphqlModel(props) {
|
|
|
8
9
|
const itemsLength = ref(0);
|
|
9
10
|
const search = ref();
|
|
10
11
|
const currentOptions = ref();
|
|
12
|
+
function setSearch(keyword) {
|
|
13
|
+
search.value = keyword;
|
|
14
|
+
}
|
|
11
15
|
const isLoading = ref(false);
|
|
12
16
|
const { operationCreate, operationUpdate, operationDelete, operationRead, operationReadPageable, operationSearch } = useGraphqlModelOperation(props);
|
|
13
17
|
function keyToField(key) {
|
|
@@ -150,9 +154,9 @@ export function useGraphqlModel(props) {
|
|
|
150
154
|
});
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
|
-
|
|
157
|
+
watchDebounced([() => props.modelName, () => props.modelBy, () => props.modelKey, canServerPageable], () => {
|
|
154
158
|
reload();
|
|
155
|
-
}, { deep: true });
|
|
159
|
+
}, { deep: true, debounce: 500, maxWait: 500 });
|
|
156
160
|
onMounted(() => {
|
|
157
161
|
if (!canServerPageable.value)
|
|
158
162
|
reload();
|
|
@@ -161,6 +165,7 @@ export function useGraphqlModel(props) {
|
|
|
161
165
|
items,
|
|
162
166
|
itemsLength,
|
|
163
167
|
search,
|
|
168
|
+
setSearch,
|
|
164
169
|
currentOptions,
|
|
165
170
|
operationCreate,
|
|
166
171
|
operationUpdate,
|
|
@@ -11,11 +11,12 @@ export function routeToMenuItem(route) {
|
|
|
11
11
|
icon: menuMeta.icon,
|
|
12
12
|
role: menuMeta.role,
|
|
13
13
|
name: route.name,
|
|
14
|
+
image: menuMeta.image,
|
|
14
15
|
menuItems: [],
|
|
15
16
|
link: {
|
|
16
17
|
name: route.name
|
|
17
18
|
},
|
|
18
|
-
active: true
|
|
19
|
+
active: menuMeta.active != void 0 ? menuMeta.active : true
|
|
19
20
|
};
|
|
20
21
|
if (route.children) {
|
|
21
22
|
const menuItems = new Array();
|
|
@@ -9,6 +9,8 @@ declare global {
|
|
|
9
9
|
title: string
|
|
10
10
|
icon: string
|
|
11
11
|
role: string
|
|
12
|
+
image? : string
|
|
13
|
+
active? : boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
interface MenuItem {
|
|
@@ -19,6 +21,7 @@ declare global {
|
|
|
19
21
|
menuItems: Array<MenuItem>
|
|
20
22
|
active: boolean
|
|
21
23
|
name: string
|
|
24
|
+
image? : string
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramathibodi/nuxt-commons",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Ramathibodi Nuxt modules for common components",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"lodash": "^4.17.21",
|
|
87
87
|
"luxon": "^3.4.4",
|
|
88
88
|
"maska": "^2.1.11",
|
|
89
|
+
"painterro": "^1.2.87",
|
|
89
90
|
"pdf-vue3": "^1.0.12",
|
|
90
91
|
"prettier": "3.3.2",
|
|
91
92
|
"print-js": "^1.6.0",
|
|
@@ -116,5 +117,5 @@
|
|
|
116
117
|
"vitest": "^1.5.1",
|
|
117
118
|
"vue-tsc": "^1.8.27"
|
|
118
119
|
},
|
|
119
|
-
"packageManager": "pnpm@9.
|
|
120
|
+
"packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1"
|
|
120
121
|
}
|
|
@@ -110,7 +110,7 @@ module.exports = {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
function createGraphQLOperation${'<' + 'T,U>'}(operationType: "Query" | "Mutation",name: string,variables?: graphqlVariable[],fields?: graphqlTypeObject): graphqlOperationObject${'<' + 'T,U>'} {
|
|
113
|
-
let returnGraphqlOperation = {
|
|
113
|
+
let returnGraphqlOperation : ${'Record' + '<string,any>'} = {
|
|
114
114
|
operationType,
|
|
115
115
|
name,
|
|
116
116
|
variables,
|
|
@@ -125,7 +125,7 @@ module.exports = {
|
|
|
125
125
|
return operationCall(operationType,name, fields, variables,cache);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
return returnGraphqlOperation
|
|
128
|
+
return returnGraphqlOperation as graphqlOperationObject${'<' + 'T,U>'}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
interface graphqlTypeObjects extends ${'Record<string,' + 'graphqlTypeObject>'} {
|