@ramathibodi/nuxt-commons 0.1.56 → 0.1.58
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/document/TemplateBuilder.vue +133 -122
- package/dist/runtime/components/form/Pad.vue +39 -0
- package/dist/runtime/components/form/TableData.vue +180 -41
- package/dist/runtime/components/label/DateCount.vue +40 -0
- package/dist/runtime/components/label/Object.vue +14 -0
- package/dist/runtime/components/master/Autocomplete.vue +25 -108
- package/dist/runtime/components/master/Combobox.vue +59 -83
- package/dist/runtime/components/model/Autocomplete.vue +23 -234
- package/dist/runtime/components/model/Combobox.vue +70 -0
- package/dist/runtime/composables/document/template.d.ts +4 -1
- package/dist/runtime/composables/document/template.js +22 -15
- package/dist/runtime/composables/document/templateFormHidden.d.ts +1 -1
- package/dist/runtime/composables/document/templateFormHidden.js +5 -2
- package/dist/runtime/composables/document/templateFormTable.d.ts +3 -2
- package/dist/runtime/composables/document/templateFormTable.js +36 -40
- package/dist/runtime/composables/document/templateFormTableData.d.ts +2 -0
- package/dist/runtime/composables/document/templateFormTableData.js +21 -0
- package/dist/runtime/composables/graphqlModel.d.ts +6 -6
- package/dist/runtime/composables/graphqlModelItem.d.ts +4 -4
- package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
- package/dist/runtime/composables/lookupList.d.ts +44 -0
- package/dist/runtime/composables/lookupList.js +194 -0
- package/dist/runtime/composables/lookupListMaster.d.ts +28 -0
- package/dist/runtime/composables/lookupListMaster.js +61 -0
- package/dist/runtime/types/menu.d.ts +7 -4
- package/dist/runtime/utils/formatter.d.ts +1 -0
- package/dist/runtime/utils/formatter.js +19 -0
- package/dist/runtime/utils/object.d.ts +1 -0
- package/dist/runtime/utils/object.js +46 -1
- package/package.json +2 -1
package/dist/module.json
CHANGED
|
@@ -3,14 +3,20 @@ import {computed, ref, watch} from 'vue'
|
|
|
3
3
|
import * as prettier from 'prettier'
|
|
4
4
|
import prettierPluginHtml from 'prettier/plugins/html'
|
|
5
5
|
import {useDocumentTemplate, validationRulesRegex,optionStringToChoiceObject} from '../../composables/document/template'
|
|
6
|
-
import {
|
|
6
|
+
import {autoActionHeader,templateToHeader} from "../../composables/document/templateFormTable";
|
|
7
|
+
import {cloneDeep} from "lodash-es";
|
|
8
|
+
import VueJsonPretty from 'vue-json-pretty';
|
|
9
|
+
import 'vue-json-pretty/lib/styles.css';
|
|
10
|
+
import {safeParseJSONDeep} from "../../utils/object";
|
|
7
11
|
|
|
8
12
|
interface Props {
|
|
9
13
|
title?: string
|
|
14
|
+
disableAdvanceMode?: boolean
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
-
title: "Document Template"
|
|
18
|
+
title: "Document Template",
|
|
19
|
+
disableAdvanceMode: false,
|
|
14
20
|
})
|
|
15
21
|
|
|
16
22
|
const emit = defineEmits(['update:modelValue'])
|
|
@@ -157,9 +163,10 @@ const notRequireLabel = ref(['Separator', 'CustomCode', 'FormFile', 'FormHidden'
|
|
|
157
163
|
const notRequireWidth = ref(['Separator', 'FormHidden','DocumentForm'])
|
|
158
164
|
const notRequireOptions = ref(['Separator','CustomCode', 'FormFile'])
|
|
159
165
|
const notRequireRules = ref(['Separator','Header','CustomCode','FormHidden','DocumentForm'])
|
|
160
|
-
const notRequireInputAttributes = ref(['CustomCode','Header',
|
|
166
|
+
const notRequireInputAttributes = ref(['CustomCode','Header','DocumentForm'])
|
|
161
167
|
const notRequireColumnAttributes = ref(['Separator', 'FormHidden','DocumentForm'])
|
|
162
|
-
const notRequireAdvancedSetting = ref(['Separator','CustomCode'
|
|
168
|
+
const notRequireAdvancedSetting = ref(['Separator','CustomCode'])
|
|
169
|
+
const notRequireClassAndStyle = ref(['FormHidden'])
|
|
163
170
|
|
|
164
171
|
const hasSpecificOption = ref(['FormHidden','FormTable','FormTableData'])
|
|
165
172
|
|
|
@@ -189,6 +196,7 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
189
196
|
color="primary"
|
|
190
197
|
variant="flat"
|
|
191
198
|
@click="convertToAdvanceMode()"
|
|
199
|
+
v-if="!disableAdvanceMode"
|
|
192
200
|
>
|
|
193
201
|
Convert To Advance
|
|
194
202
|
</VBtn>
|
|
@@ -249,6 +257,9 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
249
257
|
<v-col cols="3"><v-text-field v-model="headerData.key" label="Key" :rules="[rules.require()]"></v-text-field></v-col>
|
|
250
258
|
<v-col cols="3"><v-text-field v-model="headerData.width" label="Width"></v-text-field></v-col>
|
|
251
259
|
</v-row>
|
|
260
|
+
<v-row dense>
|
|
261
|
+
<v-col cols="12"><v-textarea v-model="headerData.template" label="Item Template"></v-textarea></v-col>
|
|
262
|
+
</v-row>
|
|
252
263
|
</v-container>
|
|
253
264
|
</template>
|
|
254
265
|
</form-table>
|
|
@@ -263,106 +274,71 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
263
274
|
<template #default="{data: optionData}">
|
|
264
275
|
<v-row dense>
|
|
265
276
|
<v-col cols="12">
|
|
266
|
-
<
|
|
267
|
-
<template #
|
|
268
|
-
<v-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
<v-col cols="3"><v-text-field v-model="headerData.key" label="Key" :rules="[rules.require()]"></v-text-field></v-col>
|
|
272
|
-
<v-col cols="3"><v-text-field v-model="headerData.width" label="Width"></v-text-field></v-col>
|
|
273
|
-
</v-row>
|
|
274
|
-
<v-row dense>
|
|
275
|
-
<v-col cols="12">
|
|
276
|
-
<template-builder title="Template" v-model="headerData.template"></template-builder>
|
|
277
|
-
</v-col>
|
|
278
|
-
</v-row>
|
|
279
|
-
<v-row dense>
|
|
280
|
-
<v-col cols="12">
|
|
281
|
-
<template-builder title="Header Template" v-model="headerData.headerTemplate"></template-builder>
|
|
282
|
-
</v-col>
|
|
283
|
-
</v-row>
|
|
284
|
-
<v-expansion-panels>
|
|
285
|
-
|
|
286
|
-
<v-expansion-panel >
|
|
287
|
-
<v-expansion-panel-title collapse-icon="mdi mdi-minus" expand-icon="mdi mdi-plus" class="font-weight-bold">Advanced settings</v-expansion-panel-title>
|
|
288
|
-
<v-expansion-panel-text>
|
|
289
|
-
<v-container fluid>
|
|
290
|
-
<v-row dense>
|
|
291
|
-
<v-col cols="12" md="6" v-if="!notRequireInputAttributes.includes(data.inputType)">
|
|
292
|
-
<v-text-field
|
|
293
|
-
v-model="headerData.inputAttributes"
|
|
294
|
-
label="Input Attributes"
|
|
295
|
-
/>
|
|
296
|
-
</v-col>
|
|
297
|
-
|
|
298
|
-
<v-col cols="12" md="6">
|
|
299
|
-
<v-text-field
|
|
300
|
-
v-model="headerData.conditionalDisplay"
|
|
301
|
-
label="Conditional Display"
|
|
302
|
-
/>
|
|
303
|
-
</v-col>
|
|
304
|
-
<v-col cols="12" md="6">
|
|
305
|
-
<v-text-field
|
|
306
|
-
v-model="headerData.computedValue"
|
|
307
|
-
label="Computed Value"
|
|
308
|
-
/>
|
|
309
|
-
</v-col>
|
|
310
|
-
<v-col cols="12" md="6">
|
|
311
|
-
<v-text-field
|
|
312
|
-
v-model="headerData.retrievedValue"
|
|
313
|
-
label="Retrieved Value"
|
|
314
|
-
/>
|
|
315
|
-
</v-col>
|
|
316
|
-
</v-row>
|
|
317
|
-
</v-container>
|
|
318
|
-
</v-expansion-panel-text>
|
|
319
|
-
</v-expansion-panel>
|
|
320
|
-
</v-expansion-panels>
|
|
321
|
-
</v-container>
|
|
277
|
+
<tabs-group>
|
|
278
|
+
<template #tabs>
|
|
279
|
+
<v-tab value="itemTemplate">Item Template</v-tab>
|
|
280
|
+
<v-tab value="dataTemplate">Data Template</v-tab>
|
|
281
|
+
<v-tab value="header">Header</v-tab>
|
|
322
282
|
</template>
|
|
323
|
-
|
|
283
|
+
<template #items>
|
|
284
|
+
<v-tabs-window-item value="itemTemplate">
|
|
285
|
+
<document-template-builder v-model="optionData.itemTemplate" title="Item Template" disable-advance-mode></document-template-builder>
|
|
286
|
+
</v-tabs-window-item>
|
|
287
|
+
<v-tabs-window-item value="dataTemplate">
|
|
288
|
+
<document-template-builder v-model="optionData.dataTemplate" title="Data Template" disable-advance-mode></document-template-builder>
|
|
289
|
+
</v-tabs-window-item>
|
|
290
|
+
<v-tabs-window-item value="header">
|
|
291
|
+
<form-table v-model="optionData.headers" :headers="formTableHeaders" title="Headers">
|
|
292
|
+
<template #form="{data: headerData,rules}">
|
|
293
|
+
<v-container fluid>
|
|
294
|
+
<v-row dense>
|
|
295
|
+
<v-col cols="6"><v-text-field v-model="headerData.title" label="Title"></v-text-field></v-col>
|
|
296
|
+
<v-col cols="3"><v-text-field v-model="headerData.key" label="Key" :rules="[rules.require()]"></v-text-field></v-col>
|
|
297
|
+
<v-col cols="3"><v-text-field v-model="headerData.width" label="Width"></v-text-field></v-col>
|
|
298
|
+
</v-row>
|
|
299
|
+
<v-row dense>
|
|
300
|
+
<v-col cols="12"><v-text-field v-model="headerData.value" label="Value"></v-text-field></v-col>
|
|
301
|
+
<v-col cols="12"><v-textarea v-model="headerData.template" label="Item Template"></v-textarea></v-col>
|
|
302
|
+
</v-row>
|
|
303
|
+
</v-container>
|
|
304
|
+
</template>
|
|
305
|
+
</form-table>
|
|
306
|
+
</v-tabs-window-item>
|
|
307
|
+
</template>
|
|
308
|
+
</tabs-group>
|
|
324
309
|
</v-col>
|
|
325
310
|
<v-col cols="12">
|
|
326
|
-
<form-table v-model="optionData.items"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
:exportable="false"
|
|
330
|
-
:searchable="false"
|
|
331
|
-
:headers="optionData.headers" title="Data">
|
|
332
|
-
<template #form="{data: headerData,rules}">
|
|
333
|
-
<v-container fluid>
|
|
334
|
-
<v-row dense>
|
|
335
|
-
<v-col v-for="header of optionData.headers" cols="12">
|
|
336
|
-
<v-text-field v-if="header.key != 'action' && !header.template"
|
|
337
|
-
v-model="headerData[header.title]"
|
|
338
|
-
:label="header.title">
|
|
339
|
-
|
|
340
|
-
</v-text-field></v-col>
|
|
341
|
-
</v-row>
|
|
342
|
-
</v-container>
|
|
343
|
-
</template>
|
|
344
|
-
|
|
345
|
-
<template v-for="header of filter(optionData.headers,(item)=> item.template)" #[`item.${header.key}`]="{item}">
|
|
346
|
-
<form-pad
|
|
347
|
-
:template="header.template"
|
|
348
|
-
v-model="item[header.key]">
|
|
349
|
-
</form-pad>
|
|
350
|
-
|
|
311
|
+
<form-table v-model="optionData.items" :headers="(optionData.headers && optionData.headers.length>0) ? autoActionHeader(optionData.headers) : autoActionHeader(templateToHeader(optionData.itemTemplate))" title="Items">
|
|
312
|
+
<template #form="{data}">
|
|
313
|
+
<form-pad :model-value="data" :template="optionData.itemTemplate"></form-pad>
|
|
351
314
|
</template>
|
|
352
|
-
<template v-for="header of filter(optionData.headers,(item)=> item.headerTemplate)" #[`header.${header.key}`]="{item}">
|
|
353
|
-
<form-pad
|
|
354
|
-
:template="header.headerTemplate"
|
|
355
|
-
>
|
|
356
|
-
</form-pad>
|
|
357
|
-
|
|
358
|
-
</template>
|
|
359
|
-
|
|
360
315
|
</form-table>
|
|
361
|
-
|
|
316
|
+
</v-col>
|
|
317
|
+
<v-col cols="12">
|
|
318
|
+
<v-row dense>
|
|
319
|
+
<v-col cols="4">
|
|
320
|
+
<v-radio-group v-model="optionData.disableApplyToAll" inline>
|
|
321
|
+
<template #prepend>
|
|
322
|
+
<span class="opacity-60"> Disable Apply to All</span>
|
|
323
|
+
</template>
|
|
324
|
+
<v-radio label="No" value="false" hide-details> </v-radio>
|
|
325
|
+
<v-radio label="Yes" value="true" hide-details> </v-radio>
|
|
326
|
+
<v-radio label="Partial" value="partial" hide-details> </v-radio>
|
|
327
|
+
</v-radio-group>
|
|
328
|
+
</v-col>
|
|
329
|
+
<v-col cols="8">
|
|
330
|
+
<VTextField
|
|
331
|
+
variant="underlined"
|
|
332
|
+
v-model="optionData.disableApplyToAllPartial"
|
|
333
|
+
label="Disable Apply To All Column"
|
|
334
|
+
v-if="optionData.disableApplyToAll=='partial'"
|
|
335
|
+
>
|
|
336
|
+
</VTextField>
|
|
337
|
+
</v-col>
|
|
338
|
+
</v-row>
|
|
362
339
|
</v-col>
|
|
363
340
|
</v-row>
|
|
364
341
|
</template>
|
|
365
|
-
|
|
366
342
|
</form-pad>
|
|
367
343
|
<form-pad v-model="data.inputOptions" v-if="data.inputType=='FormHidden'">
|
|
368
344
|
<template #default="{data: optionData}">
|
|
@@ -371,7 +347,6 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
371
347
|
<v-text-field
|
|
372
348
|
v-model="optionData.hook"
|
|
373
349
|
label="Hook"
|
|
374
|
-
:rules="[rules.require()]"
|
|
375
350
|
/>
|
|
376
351
|
</v-col>
|
|
377
352
|
<v-col cols="12">
|
|
@@ -473,37 +448,73 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
473
448
|
</v-container>
|
|
474
449
|
</v-expansion-panel-text>
|
|
475
450
|
</v-expansion-panel>
|
|
451
|
+
<v-expansion-panel v-if="!notRequireClassAndStyle.includes(data.inputType)">
|
|
452
|
+
<v-expansion-panel-title collapse-icon="mdi mdi-minus" expand-icon="mdi mdi-plus" class="font-weight-bold">Class and Styles</v-expansion-panel-title>
|
|
453
|
+
<v-expansion-panel-text>
|
|
454
|
+
<v-container fluid>
|
|
455
|
+
<v-row dense>
|
|
456
|
+
<v-col cols="12">
|
|
457
|
+
<v-text-field
|
|
458
|
+
v-model="data.customClass"
|
|
459
|
+
label="Custom Class"
|
|
460
|
+
/>
|
|
461
|
+
</v-col>
|
|
462
|
+
<v-col cols="12">
|
|
463
|
+
<v-text-field
|
|
464
|
+
v-model="data.customStyle"
|
|
465
|
+
label="Custom Styles"
|
|
466
|
+
/>
|
|
467
|
+
</v-col>
|
|
468
|
+
</v-row>
|
|
469
|
+
</v-container>
|
|
470
|
+
</v-expansion-panel-text>
|
|
471
|
+
</v-expansion-panel>
|
|
476
472
|
</v-expansion-panels>
|
|
477
473
|
</v-col>
|
|
478
474
|
</v-row>
|
|
479
475
|
</v-container>
|
|
480
476
|
</template>
|
|
481
477
|
<template #item.configuration="props">
|
|
482
|
-
<
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
<
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
478
|
+
<v-sheet
|
|
479
|
+
elevation="1"
|
|
480
|
+
max-height="250"
|
|
481
|
+
class="overflow-y-auto my-1 pa-2"
|
|
482
|
+
v-if="props.item.inputType=='CustomCode'
|
|
483
|
+
|| props.item.validationRules
|
|
484
|
+
|| props.item.inputOptions
|
|
485
|
+
|| props.item.inputAttributes
|
|
486
|
+
|| props.item.columnAttributes
|
|
487
|
+
|| props.item.conditionalDisplay
|
|
488
|
+
|| props.item.computedValue
|
|
489
|
+
|| props.item.retrievedValue"
|
|
490
|
+
>
|
|
491
|
+
<template v-if="props.item.inputType=='CustomCode'">
|
|
492
|
+
<b>Custom Code :</b><br>
|
|
493
|
+
<span style="white-space: pre-line">{{ props.item.inputCustomCode }}</span>
|
|
494
|
+
</template>
|
|
495
|
+
<template v-if="props.item.validationRules">
|
|
496
|
+
<b>Validation Rules :</b> {{ props.item.validationRules }}<br>
|
|
497
|
+
</template>
|
|
498
|
+
<template v-if="props.item.inputOptions">
|
|
499
|
+
<b>Input Options :</b>
|
|
500
|
+
<vue-json-pretty :data="safeParseJSONDeep(props.item.inputOptions)" />
|
|
501
|
+
</template>
|
|
502
|
+
<template v-if="props.item.inputAttributes">
|
|
503
|
+
<b>Input Attributes :</b> {{ props.item.inputAttributes }}<br>
|
|
504
|
+
</template>
|
|
505
|
+
<template v-if="props.item.columnAttributes">
|
|
506
|
+
<b>Column Attributes :</b> {{ props.item.columnAttributes }}<br>
|
|
507
|
+
</template>
|
|
508
|
+
<template v-if="props.item.conditionalDisplay">
|
|
509
|
+
<b>Conditional Display :</b> {{ props.item.conditionalDisplay }}<br>
|
|
510
|
+
</template>
|
|
511
|
+
<template v-if="props.item.computedValue">
|
|
512
|
+
<b>Computed Value :</b> {{ props.item.computedValue }}<br>
|
|
513
|
+
</template>
|
|
514
|
+
<template v-if="props.item.retrievedValue">
|
|
515
|
+
<b>Retrieved Value :</b> {{ props.item.retrievedValue }}<br>
|
|
516
|
+
</template>
|
|
517
|
+
</v-sheet>
|
|
507
518
|
</template>
|
|
508
519
|
</FormTable>
|
|
509
520
|
<FormCodeEditor
|
|
@@ -16,6 +16,7 @@ import {isObject} from 'lodash-es'
|
|
|
16
16
|
import {watchDebounced} from '@vueuse/core'
|
|
17
17
|
import {useRules} from '../../composables/utils/validation'
|
|
18
18
|
import {useDocumentTemplate} from '../../composables/document/template'
|
|
19
|
+
import { isArray, isString, isPlainObject } from 'lodash-es'
|
|
19
20
|
import FormPad from './Pad.vue'
|
|
20
21
|
|
|
21
22
|
defineOptions({
|
|
@@ -78,7 +79,44 @@ const formInjected = ref()
|
|
|
78
79
|
|
|
79
80
|
const formData = ref<any>({})
|
|
80
81
|
|
|
82
|
+
function isBlankString(v: unknown): v is string {
|
|
83
|
+
return isString(v) && v.length === 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sanitizeBlankStrings(val: any): boolean {
|
|
87
|
+
let changed = false
|
|
88
|
+
|
|
89
|
+
if (isArray(val)) {
|
|
90
|
+
for (let i = val.length - 1; i >= 0; i--) {
|
|
91
|
+
const item = val[i]
|
|
92
|
+
if (isBlankString(item)) {
|
|
93
|
+
val.splice(i, 1) // remove array element
|
|
94
|
+
changed = true
|
|
95
|
+
} else if (isPlainObject(item) || isArray(item)) {
|
|
96
|
+
if (sanitizeBlankStrings(item)) changed = true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return changed
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isPlainObject(val)) {
|
|
103
|
+
for (const key of Object.keys(val)) {
|
|
104
|
+
const v = val[key]
|
|
105
|
+
if (isBlankString(v)) {
|
|
106
|
+
delete val[key] // remove property directly
|
|
107
|
+
changed = true
|
|
108
|
+
} else if (isPlainObject(v) || isArray(v)) {
|
|
109
|
+
if (sanitizeBlankStrings(v)) changed = true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return changed
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
81
118
|
watch(formData, (newValue) => {
|
|
119
|
+
sanitizeBlankStrings(newValue)
|
|
82
120
|
emit('update:modelValue', newValue)
|
|
83
121
|
}, { deep: true })
|
|
84
122
|
|
|
@@ -109,6 +147,7 @@ function buildFormComponent() {
|
|
|
109
147
|
const formComponentData = ref<any>({})
|
|
110
148
|
const formPadTemplate = ref<any>({})
|
|
111
149
|
watch(formComponentData, (newValue) => {
|
|
150
|
+
sanitizeBlankStrings(newValue)
|
|
112
151
|
ctx.emit('update:modelValue', newValue)
|
|
113
152
|
}, { deep: true })
|
|
114
153
|
watch(() => props.modelValue, (newValue) => {
|
|
@@ -1,30 +1,59 @@
|
|
|
1
|
-
<script
|
|
1
|
+
<script lang="ts" setup>
|
|
2
2
|
import {VDataTable} from 'vuetify/components/VDataTable'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import {VInput} from 'vuetify/components/VInput'
|
|
4
|
+
import {computed, defineOptions,defineExpose, ref, useAttrs, watch, useTemplateRef} from 'vue'
|
|
5
|
+
import {cloneDeep, isEqual, omit, isArray, isString} from 'lodash-es'
|
|
6
|
+
import {templateItemToString} from "../../composables/document/template";
|
|
7
|
+
import {templateToHeader} from "../../composables/document/templateFormTable";
|
|
8
|
+
|
|
9
|
+
defineOptions({
|
|
10
|
+
inheritAttrs: false,
|
|
11
|
+
})
|
|
12
|
+
|
|
5
13
|
interface Props extends /* @vue-ignore */ InstanceType<typeof VDataTable['$props']> {
|
|
6
14
|
title: string
|
|
15
|
+
noDataText?: string
|
|
7
16
|
modelValue?: Record<string, any>[]
|
|
8
17
|
modelKey?: string
|
|
9
18
|
toolbarColor?: string
|
|
10
|
-
|
|
19
|
+
headers : Record<string, any>[]
|
|
20
|
+
itemsInitial : Record<string, any>[]
|
|
21
|
+
dataTemplate?: string | object
|
|
22
|
+
disableApplyToAll?: boolean | string | string[]
|
|
11
23
|
}
|
|
24
|
+
|
|
12
25
|
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
+
noDataText: 'ไม่พบข้อมูล',
|
|
13
27
|
modelKey: 'id',
|
|
28
|
+
toolbarColor: 'primary',
|
|
29
|
+
disableApplyToAll: false,
|
|
14
30
|
})
|
|
15
31
|
|
|
16
32
|
const emit = defineEmits(['update:modelValue'])
|
|
17
33
|
const attrs = useAttrs()
|
|
18
34
|
const plainAttrs = computed(() => {
|
|
19
|
-
return omit(attrs, ['modelValue',
|
|
35
|
+
return omit(attrs, ['modelValue','items','onUpdate:modelValue','itemsInitial','dataTemplate'])
|
|
20
36
|
})
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
37
|
+
|
|
38
|
+
const inputRef = useTemplateRef<VInput>("inputRef")
|
|
39
|
+
|
|
40
|
+
const itemsInternal = ref<Record<string, any>[]>([])
|
|
41
|
+
const itemsApplyAll = ref<Record<string, any>>({})
|
|
42
|
+
|
|
43
|
+
const computedDisableApplyToAll = computed(()=>{
|
|
44
|
+
if (isString(props.disableApplyToAll)) {
|
|
45
|
+
return props.disableApplyToAll.split(',').map(i=>i.trim())
|
|
46
|
+
}
|
|
47
|
+
return props.disableApplyToAll
|
|
48
|
+
})
|
|
49
|
+
const canApplyAll = (variableName: string) => {
|
|
50
|
+
if (isArray(computedDisableApplyToAll.value)) return !computedDisableApplyToAll.value.includes(variableName)
|
|
51
|
+
return !computedDisableApplyToAll.value
|
|
24
52
|
}
|
|
53
|
+
|
|
25
54
|
watch(() => props.modelValue, (newValue) => {
|
|
26
55
|
if (!Array.isArray(newValue) || !newValue.every(item => typeof item === 'object')) {
|
|
27
|
-
|
|
56
|
+
itemsInternal.value = cloneDeep(props.itemsInitial)
|
|
28
57
|
}
|
|
29
58
|
else {
|
|
30
59
|
let maxKey = 0
|
|
@@ -36,47 +65,157 @@ watch(() => props.modelValue, (newValue) => {
|
|
|
36
65
|
}
|
|
37
66
|
})
|
|
38
67
|
|
|
39
|
-
|
|
68
|
+
itemsInternal.value = newValue
|
|
40
69
|
}
|
|
41
70
|
}, { immediate: true })
|
|
42
71
|
|
|
43
|
-
watch(
|
|
44
|
-
|
|
72
|
+
watch(()=>props.itemsInitial, (newValue,oldValue)=>{
|
|
73
|
+
if (!isEqual(newValue,oldValue)) itemsInternal.value = cloneDeep(props.itemsInitial)
|
|
74
|
+
},{immediate:true,deep:true})
|
|
75
|
+
|
|
76
|
+
watch(itemsInternal, () => {
|
|
77
|
+
emit('update:modelValue', itemsInternal.value)
|
|
45
78
|
}, { deep: true })
|
|
46
79
|
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
watch(itemsApplyAll, () => {
|
|
81
|
+
itemsInternal.value = itemsInternal.value?.map((item) => {
|
|
82
|
+
return Object.assign(item, itemsApplyAll.value)
|
|
83
|
+
})
|
|
84
|
+
},{deep:true})
|
|
85
|
+
|
|
86
|
+
const computedHeaders = computed(()=>{
|
|
87
|
+
let dataHeaders = templateToHeader(props.dataTemplate)
|
|
88
|
+
let combinedHeaders = [...props.headers, ...dataHeaders]
|
|
89
|
+
return combinedHeaders.map((header: any) => {
|
|
90
|
+
return {...header,headerProps:
|
|
91
|
+
{
|
|
92
|
+
style: 'font-weight: 800'
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
49
96
|
})
|
|
50
97
|
|
|
51
|
-
|
|
98
|
+
const computedDataTemplate = computed(()=>{
|
|
99
|
+
let template = cloneDeep(props.dataTemplate)
|
|
100
|
+
if (isString(props.dataTemplate)) {
|
|
101
|
+
try {
|
|
102
|
+
template = JSON.parse(props.dataTemplate)
|
|
103
|
+
} catch (e) {
|
|
104
|
+
void e
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (isArray(template)) {
|
|
108
|
+
return template.map((t: any) => {
|
|
109
|
+
const out = { ...t }
|
|
110
|
+
let s = out.inputAttributes?.trim() || ""
|
|
111
|
+
if (!/(^|\s)hide-details(\s|$)/.test(s) && !/(^|\s)hideDetails(\s|$)/.test(s)) {
|
|
112
|
+
s = `${s} hide-details`.trim()
|
|
113
|
+
}
|
|
114
|
+
out.inputAttributes = s
|
|
115
|
+
return out
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return []
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const reset = ()=>{
|
|
122
|
+
inputRef.value?.reset()
|
|
123
|
+
itemsApplyAll.value = {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isValid = computed(()=>{
|
|
127
|
+
return inputRef.value?.isValid
|
|
128
|
+
})
|
|
52
129
|
|
|
130
|
+
const errorMessages = computed(()=>{
|
|
131
|
+
return inputRef.value?.errorMessages
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const operation = ref({reset})
|
|
135
|
+
|
|
136
|
+
defineExpose({
|
|
137
|
+
errorMessages,
|
|
138
|
+
isValid,
|
|
139
|
+
reset,
|
|
140
|
+
resetValidation : ()=>inputRef.value?.resetValidation(),
|
|
141
|
+
validate : ()=>inputRef.value?.validate(),
|
|
142
|
+
operation
|
|
143
|
+
})
|
|
144
|
+
</script>
|
|
53
145
|
|
|
54
146
|
<template>
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
</
|
|
78
|
-
</
|
|
147
|
+
<v-input v-model="itemsInternal" v-bind="plainAttrs" ref="inputRef">
|
|
148
|
+
<template #default="{isReadonly,isDisabled}">
|
|
149
|
+
<v-container fluid class="ma-0 pa-0">
|
|
150
|
+
<v-card>
|
|
151
|
+
<slot
|
|
152
|
+
name="header"
|
|
153
|
+
:items="itemsInternal"
|
|
154
|
+
:operation="operation"
|
|
155
|
+
>
|
|
156
|
+
<VToolbar :color="toolbarColor">
|
|
157
|
+
<v-row
|
|
158
|
+
justify="end"
|
|
159
|
+
class="ma-1"
|
|
160
|
+
dense
|
|
161
|
+
no-gutters
|
|
162
|
+
align="center"
|
|
163
|
+
>
|
|
164
|
+
<v-col>
|
|
165
|
+
<VToolbarTitle class="pl-3">
|
|
166
|
+
<slot name="title">
|
|
167
|
+
{{ title }}
|
|
168
|
+
</slot>
|
|
169
|
+
</VToolbarTitle>
|
|
170
|
+
</v-col>
|
|
171
|
+
</v-row>
|
|
172
|
+
|
|
173
|
+
<VToolbarItems>
|
|
174
|
+
<slot name="toolbarItems" :items="itemsInternal" :operation="operation"/>
|
|
175
|
+
<VBtn
|
|
176
|
+
:color="toolbarColor"
|
|
177
|
+
icon="mdi:mdi-restore"
|
|
178
|
+
variant="flat"
|
|
179
|
+
@click="reset()"
|
|
180
|
+
>
|
|
181
|
+
</VBtn>
|
|
182
|
+
</VToolbarItems>
|
|
183
|
+
</VToolbar>
|
|
184
|
+
</slot>
|
|
185
|
+
<v-data-table
|
|
186
|
+
v-bind="plainAttrs"
|
|
187
|
+
color="primary"
|
|
188
|
+
:items="itemsInternal"
|
|
189
|
+
:headers="computedHeaders"
|
|
190
|
+
disable-sort
|
|
191
|
+
hide-default-footer
|
|
192
|
+
>
|
|
193
|
+
<!-- @ts-ignore -->
|
|
194
|
+
<template
|
|
195
|
+
v-for="(_, name, index) in ($slots as {})"
|
|
196
|
+
:key="index"
|
|
197
|
+
#[name]="slotData"
|
|
198
|
+
>
|
|
199
|
+
<slot
|
|
200
|
+
:name="name"
|
|
201
|
+
v-bind="((slotData || {}) as object)"
|
|
202
|
+
:operation="operation"
|
|
203
|
+
:isReadonly="isReadonly"
|
|
204
|
+
:isDisabled="isDisabled"
|
|
205
|
+
/>
|
|
206
|
+
</template>
|
|
79
207
|
|
|
80
|
-
<
|
|
208
|
+
<template v-for="template in computedDataTemplate" :key="template.variableName" #[`header.${template.variableName}`]="props">
|
|
209
|
+
<form-pad v-model="itemsApplyAll" :template="templateItemToString(template,[])" v-if="canApplyAll(template.variableName)"></form-pad>
|
|
210
|
+
<template v-else>{{props.column.title}}</template>
|
|
211
|
+
</template>
|
|
81
212
|
|
|
82
|
-
|
|
213
|
+
<template v-for="template in computedDataTemplate" :key="template.variableName" #[`item.${template.variableName}`]="{index}">
|
|
214
|
+
<form-pad v-model="itemsInternal[index]" :template="templateItemToString(template,[])"></form-pad>
|
|
215
|
+
</template>
|
|
216
|
+
</v-data-table>
|
|
217
|
+
</v-card>
|
|
218
|
+
</v-container>
|
|
219
|
+
</template>
|
|
220
|
+
</v-input>
|
|
221
|
+
</template>
|