@ramathibodi/nuxt-commons 0.1.21 → 0.1.23
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.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +5 -1
- package/dist/runtime/components/ExportCSV.vue +22 -13
- package/dist/runtime/components/FileBtn.vue +4 -2
- package/dist/runtime/components/ImportCSV.vue +24 -14
- package/dist/runtime/components/document/TemplateBuilder.vue +1 -1
- package/dist/runtime/components/form/SignPad.vue +30 -30
- package/dist/runtime/components/form/Table.vue +3 -20
- 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/alert.d.ts +1 -1
- package/dist/runtime/composables/{alert.mjs → alert.js} +1 -2
- package/dist/runtime/composables/api.d.ts +4 -4
- package/dist/runtime/composables/api.js +52 -0
- package/dist/runtime/composables/document/{template.mjs → template.js} +8 -16
- package/dist/runtime/composables/graphql.d.ts +6 -5
- package/dist/runtime/composables/{graphql.mjs → graphql.js} +31 -40
- package/dist/runtime/composables/graphqlModel.d.ts +18 -17
- package/dist/runtime/composables/{graphqlModel.mjs → graphqlModel.js} +26 -36
- package/dist/runtime/composables/graphqlModelItem.d.ts +13 -13
- package/dist/runtime/composables/{graphqlModelItem.mjs → graphqlModelItem.js} +6 -9
- package/dist/runtime/composables/graphqlModelOperation.d.ts +7 -7
- package/dist/runtime/composables/{graphqlModelOperation.mjs → graphqlModelOperation.js} +6 -12
- package/dist/runtime/composables/graphqlOperation.d.ts +2 -2
- package/dist/runtime/composables/{graphqlOperation.mjs → graphqlOperation.js} +15 -28
- package/dist/runtime/composables/menu.d.ts +1 -1
- package/dist/runtime/composables/{menu.mjs → menu.js} +4 -5
- package/dist/runtime/composables/utils/fuzzy.d.ts +1 -1
- package/dist/runtime/composables/utils/validation.d.ts +32 -0
- package/dist/runtime/plugins/permission.d.ts +1 -1
- package/dist/runtime/plugins/{permission.mjs → permission.js} +2 -4
- package/dist/runtime/types/menu.d.ts +3 -0
- package/dist/runtime/utils/datetime.d.ts +214 -16
- package/dist/runtime/utils/{datetime.mjs → datetime.js} +22 -44
- package/dist/runtime/utils/{object.mjs → object.js} +1 -2
- package/dist/types.d.mts +1 -16
- package/dist/types.d.ts +1 -16
- package/package.json +35 -34
- package/dist/runtime/composables/api.mjs +0 -64
- package/dist/runtime/plugins/vueSignaturePad.d.ts +0 -2
- package/dist/runtime/plugins/vueSignaturePad.mjs +0 -5
- /package/dist/runtime/composables/utils/{fuzzy.mjs → fuzzy.js} +0 -0
- /package/dist/runtime/composables/utils/{validation.mjs → validation.js} +0 -0
package/dist/module.d.mts
CHANGED
|
@@ -2,6 +2,6 @@ import * as _nuxt_schema from '@nuxt/schema';
|
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
4
|
}
|
|
5
|
-
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
|
5
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
6
6
|
|
|
7
7
|
export { type ModuleOptions, _default as default };
|
package/dist/module.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import * as _nuxt_schema from '@nuxt/schema';
|
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
4
|
}
|
|
5
|
-
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
|
5
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
6
6
|
|
|
7
7
|
export { type ModuleOptions, _default as default };
|
package/dist/module.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {ref
|
|
2
|
+
import {ref} from 'vue'
|
|
3
3
|
import * as XLSX from 'xlsx'
|
|
4
4
|
import {VBtn} from 'vuetify/components/VBtn'
|
|
5
5
|
import {useAlert} from '../composables/alert'
|
|
@@ -20,7 +20,7 @@ function exportFile() {
|
|
|
20
20
|
if (props.modelValue && Array.isArray(props.modelValue)) {
|
|
21
21
|
loading.value = true
|
|
22
22
|
const workbook = XLSX.utils.book_new()
|
|
23
|
-
const worksheet = XLSX.utils.json_to_sheet(props.modelValue)
|
|
23
|
+
const worksheet = XLSX.utils.json_to_sheet(serializeRoleField(props.modelValue))
|
|
24
24
|
const fileName = `${props.fileName}.xlsx`
|
|
25
25
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
|
|
26
26
|
XLSX.writeFile(workbook, fileName)
|
|
@@ -30,25 +30,34 @@ function exportFile() {
|
|
|
30
30
|
alert?.addAlert({ message: 'Invalid or no data to export', alertType: 'error' })
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
const serializeRoleField = (items: any) => {
|
|
35
|
+
return items.map((item:any) => {
|
|
36
|
+
if (item.properties && typeof item.properties === 'object') {
|
|
37
|
+
item.properties = JSON.stringify(item.properties);
|
|
38
|
+
}
|
|
39
|
+
return item;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
33
42
|
</script>
|
|
34
43
|
|
|
35
44
|
<template>
|
|
36
45
|
<VBtn
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
v-bind="$attrs"
|
|
47
|
+
color="primary"
|
|
48
|
+
:loading="loading"
|
|
49
|
+
:disabled="loading"
|
|
50
|
+
text="Export CSV"
|
|
51
|
+
@click="exportFile"
|
|
43
52
|
>
|
|
44
53
|
<template
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
v-for="(_, name, index) in ($slots as {})"
|
|
55
|
+
:key="index"
|
|
56
|
+
#[name]="slotData"
|
|
48
57
|
>
|
|
49
58
|
<slot
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
:name="name"
|
|
60
|
+
v-bind="((slotData || {}) as object)"
|
|
52
61
|
/>
|
|
53
62
|
</template>
|
|
54
63
|
</VBtn>
|
|
@@ -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"
|
|
@@ -30,34 +30,44 @@ function uploadedFile(files: File[] | File | undefined) {
|
|
|
30
30
|
const reader = new FileReader()
|
|
31
31
|
reader.onload = (e: ProgressEvent<FileReader>) => {
|
|
32
32
|
const workbook = XLSX.read(e.target?.result)
|
|
33
|
-
|
|
33
|
+
const parseData = parsePropertiesField(XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]))
|
|
34
|
+
emit('import', parseData)
|
|
34
35
|
loading.value = false
|
|
35
36
|
fileBtnRef.value.reset()
|
|
36
37
|
}
|
|
37
38
|
loading.value = true
|
|
38
39
|
reader.readAsArrayBuffer(files)
|
|
39
40
|
}
|
|
41
|
+
|
|
42
|
+
const parsePropertiesField = (items:any) => {
|
|
43
|
+
return items.map(item => {
|
|
44
|
+
if (item.properties && typeof item.properties === 'string') {
|
|
45
|
+
item.properties = JSON.parse(item.properties);
|
|
46
|
+
}
|
|
47
|
+
return item;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
40
50
|
</script>
|
|
41
51
|
|
|
42
52
|
<template>
|
|
43
53
|
<FileBtn
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
ref="fileBtnRef"
|
|
55
|
+
v-bind="$attrs"
|
|
56
|
+
color="primary"
|
|
57
|
+
:loading="loading"
|
|
58
|
+
text="Import CSV"
|
|
59
|
+
accept=".csv, .xlsx"
|
|
60
|
+
:multiple="false"
|
|
61
|
+
@update:model-value="uploadedFile"
|
|
52
62
|
>
|
|
53
63
|
<template
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
v-for="(_, name, index) in ($slots as {})"
|
|
65
|
+
:key="index"
|
|
66
|
+
#[name]="slotData"
|
|
57
67
|
>
|
|
58
68
|
<slot
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
:name="name"
|
|
70
|
+
v-bind="((slotData || {}) as object)"
|
|
61
71
|
/>
|
|
62
72
|
</template>
|
|
63
73
|
</FileBtn>
|
|
@@ -11,7 +11,7 @@ const isAdvanceMode = computed(() => {
|
|
|
11
11
|
return modelValue.value && !isValidJsonArrayOfObjects(modelValue.value)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
const templateItems = ref<Record<string, any
|
|
14
|
+
const templateItems = ref<Record<string, any>[]>([])
|
|
15
15
|
|
|
16
16
|
const headers = ref([
|
|
17
17
|
{ title: '',
|
|
@@ -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
|
|
|
@@ -235,26 +235,9 @@ const operation = ref({ openDialog, createItem, updateItem, deleteItem, moveUpIt
|
|
|
235
235
|
v-if="!$slots['item.operation']"
|
|
236
236
|
#item.operation="props"
|
|
237
237
|
>
|
|
238
|
-
<v-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
density="compact"
|
|
242
|
-
icon="mdi:mdi-arrow-up-thick"
|
|
243
|
-
@click="moveUpItem(props.item)"
|
|
244
|
-
/>
|
|
245
|
-
<v-btn
|
|
246
|
-
variant="flat"
|
|
247
|
-
density="compact"
|
|
248
|
-
icon="fa:fas fa-arrow-right-to-bracket"
|
|
249
|
-
@click="moveToItem(props.item)"
|
|
250
|
-
/>
|
|
251
|
-
<v-btn
|
|
252
|
-
:disabled="props.index==items.length-1"
|
|
253
|
-
variant="flat"
|
|
254
|
-
density="compact"
|
|
255
|
-
icon="mdi:mdi-arrow-down-thick"
|
|
256
|
-
@click="moveDownItem(props.item)"
|
|
257
|
-
/>
|
|
238
|
+
<v-icon density="compact" :disabled="props.index==0" @click="moveUpItem(props.item)">mdi:mdi-arrow-up-thick</v-icon>
|
|
239
|
+
<v-icon density="compact" @click="moveToItem(props.item)">fa:fas fa-arrow-right-to-bracket</v-icon>
|
|
240
|
+
<v-icon density="compact" :disabled="props.index==items.length-1" @click="moveDownItem(props.item)">mdi:mdi-arrow-down-thick</v-icon>
|
|
258
241
|
</template>
|
|
259
242
|
<template
|
|
260
243
|
v-if="!$slots['item.action']"
|
|
@@ -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"
|