@drax/crud-vue 0.51.0 → 1.4.0
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/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "1.4.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"format": "prettier --write src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drax/common-front": "^
|
|
28
|
-
"@drax/crud-front": "^0.
|
|
29
|
-
"@drax/crud-share": "^
|
|
30
|
-
"@drax/media-vue": "^0.
|
|
27
|
+
"@drax/common-front": "^1.4.0",
|
|
28
|
+
"@drax/crud-front": "^1.0.0",
|
|
29
|
+
"@drax/crud-share": "^1.4.0",
|
|
30
|
+
"@drax/media-vue": "^1.0.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"pinia": "^2.2.2",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"vue-tsc": "^2.1.10",
|
|
65
65
|
"vuetify": "^3.8.2"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "b957d2b908e730535cf9fb628f0c57e20c007d0f"
|
|
68
68
|
}
|
package/src/EntityCrud.ts
CHANGED
|
@@ -111,6 +111,8 @@ class EntityCrud implements IEntityCrud {
|
|
|
111
111
|
value = field.default
|
|
112
112
|
} else if (field.type === 'object') {
|
|
113
113
|
value = this.objectFields(field)
|
|
114
|
+
} else if (field.type === 'record') {
|
|
115
|
+
value = {}
|
|
114
116
|
} else if (/array/.test(field.type)) {
|
|
115
117
|
value = []
|
|
116
118
|
} else {
|
|
@@ -3,6 +3,7 @@ import {computed, ref} from "vue";
|
|
|
3
3
|
import type {PropType} from "vue";
|
|
4
4
|
import CrudFormList from "./CrudFormList.vue";
|
|
5
5
|
import CrudAutocomplete from "./CrudAutocomplete.vue";
|
|
6
|
+
import CrudFormRecord from "./CrudFormRecord.vue";
|
|
6
7
|
import {useI18n} from "vue-i18n";
|
|
7
8
|
import {useCrudStore} from "../stores/UseCrudStore";
|
|
8
9
|
import {VDateInput} from 'vuetify/labs/VDateInput'
|
|
@@ -538,6 +539,21 @@ defineEmits(['updateValue'])
|
|
|
538
539
|
@updateValue="$emit('updateValue')"
|
|
539
540
|
/>
|
|
540
541
|
|
|
542
|
+
<crud-form-record
|
|
543
|
+
v-if="field.type === 'record'"
|
|
544
|
+
:entity="entity"
|
|
545
|
+
:field="field"
|
|
546
|
+
v-model="valueModel"
|
|
547
|
+
:readonly="readonly"
|
|
548
|
+
:density="density"
|
|
549
|
+
:variant="variant"
|
|
550
|
+
:clearable="clearable"
|
|
551
|
+
:hide-details="hideDetails"
|
|
552
|
+
:single-line="singleLine"
|
|
553
|
+
:error-messages="inputErrors"
|
|
554
|
+
@updateValue="$emit('updateValue')"
|
|
555
|
+
/>
|
|
556
|
+
|
|
541
557
|
</div>
|
|
542
558
|
</template>
|
|
543
559
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
import type { PropType } from 'vue'
|
|
4
|
+
import type { IEntityCrud, IEntityCrudField } from '@drax/crud-share'
|
|
5
|
+
import { useI18n } from 'vue-i18n'
|
|
6
|
+
|
|
7
|
+
const { t } = useI18n()
|
|
8
|
+
|
|
9
|
+
const valueModel = defineModel<Record<string, any>>({ type: Object, default: () => ({}) })
|
|
10
|
+
|
|
11
|
+
const { field, readonly, density, variant } = defineProps({
|
|
12
|
+
entity: { type: Object as PropType<IEntityCrud>, required: true },
|
|
13
|
+
field: { type: Object as PropType<IEntityCrudField>, required: true },
|
|
14
|
+
readonly: { type: Boolean, default: false },
|
|
15
|
+
density: { type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default' },
|
|
16
|
+
variant: {
|
|
17
|
+
type: String as PropType<'underlined' | 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'plain'>,
|
|
18
|
+
default: 'filled'
|
|
19
|
+
},
|
|
20
|
+
clearable: { type: Boolean, default: false },
|
|
21
|
+
hideDetails: { type: Boolean, default: false },
|
|
22
|
+
singleLine: { type: Boolean, default: false },
|
|
23
|
+
errorMessages: { type: Array as PropType<string[]>, default: () => [] }
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Mantener un array local de entradas con IDs únicos
|
|
27
|
+
const localEntries = ref<Array<{ id: string; key: string; value: string }>>([])
|
|
28
|
+
let nextId = 0
|
|
29
|
+
let isUpdatingFromModel = false
|
|
30
|
+
|
|
31
|
+
// Inicializar desde valueModel
|
|
32
|
+
const initializeEntries = () => {
|
|
33
|
+
const modelEntries = Object.entries(valueModel.value || {})
|
|
34
|
+
if (modelEntries.length > 0) {
|
|
35
|
+
localEntries.value = modelEntries.map(([k, v]) => ({
|
|
36
|
+
id: `entry-${nextId++}`,
|
|
37
|
+
key: k,
|
|
38
|
+
value: v as string
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Inicializar al montar
|
|
44
|
+
initializeEntries()
|
|
45
|
+
|
|
46
|
+
// Sincronizar cuando cambia valueModel externamente (no desde updateModel)
|
|
47
|
+
watch(() => valueModel.value, () => {
|
|
48
|
+
if (!isUpdatingFromModel) {
|
|
49
|
+
initializeEntries()
|
|
50
|
+
}
|
|
51
|
+
isUpdatingFromModel = false
|
|
52
|
+
}, { deep: true })
|
|
53
|
+
|
|
54
|
+
const addEntry = () => {
|
|
55
|
+
localEntries.value.push({
|
|
56
|
+
id: `entry-${nextId++}`,
|
|
57
|
+
key: '',
|
|
58
|
+
value: ''
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const removeEntry = (idToRemove: string) => {
|
|
63
|
+
localEntries.value = localEntries.value.filter(entry => entry.id !== idToRemove)
|
|
64
|
+
updateModel()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const updateKey = (id: string, newKey: string) => {
|
|
68
|
+
const entry = localEntries.value.find(e => e.id === id)
|
|
69
|
+
if (entry) {
|
|
70
|
+
entry.key = newKey
|
|
71
|
+
updateModel()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const updateValue = (id: string, newValue: string) => {
|
|
76
|
+
const entry = localEntries.value.find(e => e.id === id)
|
|
77
|
+
if (entry) {
|
|
78
|
+
entry.value = newValue
|
|
79
|
+
updateModel()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const updateModel = () => {
|
|
84
|
+
isUpdatingFromModel = true
|
|
85
|
+
const filtered = localEntries.value.filter(entry => entry.key.trim() !== '' && entry.value.trim() !== '')
|
|
86
|
+
valueModel.value = Object.fromEntries(filtered.map(entry => [entry.key, entry.value]))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
defineEmits(['updateValue'])
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<div class="record-field">
|
|
94
|
+
<v-card variant="flat" border class="mt-3">
|
|
95
|
+
<v-card-title class="text-h5">{{ field.label }}</v-card-title>
|
|
96
|
+
<v-card-text>
|
|
97
|
+
<div v-if="localEntries.length === 0" class="text-center py-4">
|
|
98
|
+
<p class="text-grey">{{ t('common.noData') || 'No data' }}</p>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div v-for="entry in localEntries" :key="entry.id" class="record-entry mb-3">
|
|
102
|
+
<v-row dense>
|
|
103
|
+
<v-col cols="12" sm="5">
|
|
104
|
+
<v-text-field
|
|
105
|
+
:model-value="entry.key"
|
|
106
|
+
:label="t('common.key') || 'Key'"
|
|
107
|
+
:density="density"
|
|
108
|
+
:variant="variant"
|
|
109
|
+
:readonly="readonly"
|
|
110
|
+
@update:model-value="(v) => updateKey(entry.id, v)"
|
|
111
|
+
@blur="$emit('updateValue')"
|
|
112
|
+
outlined
|
|
113
|
+
/>
|
|
114
|
+
</v-col>
|
|
115
|
+
<v-col cols="12" sm="5">
|
|
116
|
+
<v-text-field
|
|
117
|
+
:model-value="entry.value"
|
|
118
|
+
:label="t('common.value') || 'Value'"
|
|
119
|
+
:density="density"
|
|
120
|
+
:variant="variant"
|
|
121
|
+
:readonly="readonly"
|
|
122
|
+
@update:model-value="(v) => updateValue(entry.id, v)"
|
|
123
|
+
@blur="$emit('updateValue')"
|
|
124
|
+
outlined
|
|
125
|
+
/>
|
|
126
|
+
</v-col>
|
|
127
|
+
<v-col cols="12" sm="2" class="d-flex align-center">
|
|
128
|
+
<v-btn
|
|
129
|
+
v-if="!readonly"
|
|
130
|
+
icon="mdi-delete"
|
|
131
|
+
size="small"
|
|
132
|
+
color="error"
|
|
133
|
+
variant="text"
|
|
134
|
+
@click="removeEntry(entry.id)"
|
|
135
|
+
/>
|
|
136
|
+
</v-col>
|
|
137
|
+
</v-row>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<v-divider v-if="localEntries.length > 0" class="my-3" />
|
|
141
|
+
|
|
142
|
+
<v-btn
|
|
143
|
+
v-if="!readonly"
|
|
144
|
+
prepend-icon="mdi-plus"
|
|
145
|
+
color="primary"
|
|
146
|
+
variant="tonal"
|
|
147
|
+
block
|
|
148
|
+
@click="addEntry"
|
|
149
|
+
>
|
|
150
|
+
{{ t('action.addEntry') || 'Add Entry' }}
|
|
151
|
+
</v-btn>
|
|
152
|
+
</v-card-text>
|
|
153
|
+
</v-card>
|
|
154
|
+
</div>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
<style scoped>
|
|
158
|
+
.record-field {
|
|
159
|
+
width: 100%;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.record-entry {
|
|
163
|
+
border-left: 3px solid rgba(0, 0, 0, 0.12);
|
|
164
|
+
padding-left: 12px;
|
|
165
|
+
}
|
|
166
|
+
</style>
|