tantee-nuxt-commons 0.0.169 → 0.0.171
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 +6 -0
- package/dist/runtime/components/Alert.vue +0 -1
- package/dist/runtime/components/form/ActionPad.vue +1 -1
- package/dist/runtime/components/form/Dialog.vue +1 -1
- package/dist/runtime/components/form/EditPad.vue +1 -1
- package/dist/runtime/components/form/File.vue +189 -126
- package/dist/runtime/components/form/Iterator.vue +2 -0
- package/dist/runtime/components/form/Pad.vue +3 -72
- package/dist/runtime/components/form/SignPad.vue +126 -141
- package/dist/runtime/components/form/images/Field.vue +269 -219
- package/dist/runtime/components/label/DateCount.vue +13 -117
- package/dist/runtime/components/pdf/View.vue +32 -42
- package/dist/runtime/composables/assetFile.d.ts +31 -0
- package/dist/runtime/composables/assetFile.js +134 -0
- package/dist/runtime/composables/document/templateFormTable.js +1 -4
- package/package.json +1 -1
|
@@ -1,263 +1,313 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type
|
|
6
|
-
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
import { isEqual } from 'lodash-es'
|
|
4
|
+
import { useAlert } from '../../../composables/alert'
|
|
5
|
+
import { useAssetFile, type Base64Image, type Base64Asset, type Base64File} from '../../../composables/assetFile'
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
|
+
import { VInput } from 'vuetify/components/VInput'
|
|
7
8
|
|
|
8
9
|
const emit = defineEmits<{
|
|
9
|
-
(e:
|
|
10
|
-
}>()
|
|
10
|
+
(e: 'update:modelValue', value: Base64Image[]): void
|
|
11
|
+
}>()
|
|
11
12
|
|
|
12
13
|
const alert = useAlert()
|
|
13
|
-
|
|
14
|
-
interface Image {
|
|
15
|
-
title: string;
|
|
16
|
-
data: string;
|
|
17
|
-
props: {};
|
|
18
|
-
}
|
|
14
|
+
const { fileToBase64, hydrateAssetFile } = useAssetFile()
|
|
19
15
|
|
|
20
16
|
interface Props {
|
|
21
|
-
modelValue?:
|
|
22
|
-
readonly?: boolean
|
|
23
|
-
label?: string
|
|
17
|
+
modelValue?: Base64Image[]
|
|
18
|
+
readonly?: boolean
|
|
19
|
+
label?: string
|
|
20
|
+
accept?: string
|
|
21
|
+
autoHydrate?: boolean
|
|
22
|
+
maxFileSize?: number
|
|
24
23
|
}
|
|
25
24
|
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
-
modelValue: () => [] as
|
|
25
|
+
modelValue: () => [] as Base64Image[],
|
|
26
|
+
accept: '.jpg,.jpeg,.png,.webp,.gif,.bmp,.tiff,.tif',
|
|
27
|
+
autoHydrate: false,
|
|
28
|
+
maxFileSize: 10,
|
|
27
29
|
})
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const uploadImages
|
|
32
|
-
// dialog = dialog for capturing image
|
|
33
|
-
const dialog: Ref<boolean> = ref(false);
|
|
34
|
-
// dialogUpdate, dataUpdate = dialog for editing image
|
|
35
|
-
const dialogUpdate: Ref<boolean> = ref(false);
|
|
36
|
-
const dataUpdate: Ref<Image> = ref({
|
|
37
|
-
title: "",
|
|
38
|
-
data: "",
|
|
39
|
-
props: {},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// remove selected image
|
|
44
|
-
const remove = (index: number) => {
|
|
45
|
-
images.value.splice(index, 1);
|
|
46
|
-
};
|
|
31
|
+
/** Internal state (always Base64Image[]) */
|
|
32
|
+
const images = ref<Base64Image[]>([])
|
|
33
|
+
const uploadImages = ref<File[]>([])
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
35
|
+
/** Dialogs */
|
|
36
|
+
const dialog = ref(false) // capture dialog
|
|
37
|
+
const dialogUpdate = ref(false) // edit dialog
|
|
38
|
+
const dataUpdate = ref<Base64Image>({ imageData: {}, imageTitle: '', imageProps: {} })
|
|
53
39
|
|
|
40
|
+
/** Fullscreen preview */
|
|
41
|
+
const dialogImageFullScreen = ref(false)
|
|
42
|
+
const imageFullScreen = ref<{title: string,image: string|undefined}>({ title: '', image: '' })
|
|
54
43
|
|
|
44
|
+
/** ---------- Stable keys + guards ---------- */
|
|
45
|
+
let internalSync = false
|
|
46
|
+
let lastEmittedSig = '' // signature of last emitted state
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
function imageKey(im: Base64Image): string {
|
|
49
|
+
const id = im.imageData?.id
|
|
50
|
+
if (id != null) return `id:${id}`
|
|
51
|
+
const title = im.imageTitle ?? ''
|
|
52
|
+
const len = im.imageData?.base64String?.length ?? 0
|
|
53
|
+
return `t:${title}|l:${len}`
|
|
61
54
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
55
|
+
|
|
56
|
+
function signature(arr: Base64Image[]): string {
|
|
57
|
+
return arr.map(imageKey).join('|')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Emit helper (guarded + updates last signature) */
|
|
61
|
+
function emitNow(next: Base64Image[]) {
|
|
62
|
+
const sig = signature(next)
|
|
63
|
+
if (sig === lastEmittedSig) return
|
|
64
|
+
internalSync = true
|
|
70
65
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const mineType = result.split(",")[0];
|
|
76
|
-
const base64 = result.split(",")[1];
|
|
77
|
-
resolve({
|
|
78
|
-
base64string: base64,
|
|
79
|
-
mineType: mineType,
|
|
80
|
-
filename: file.name,
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
reader.onerror = (error) => {
|
|
84
|
-
reject(error);
|
|
85
|
-
};
|
|
86
|
-
reader.readAsDataURL(file);
|
|
87
|
-
});
|
|
88
|
-
return await readPromise;
|
|
89
|
-
} catch (error: any) {
|
|
90
|
-
alert?.addAlert({message: error, alertType: 'error'})
|
|
66
|
+
emit('update:modelValue', next)
|
|
67
|
+
lastEmittedSig = sig
|
|
68
|
+
} finally {
|
|
69
|
+
queueMicrotask(() => { internalSync = false })
|
|
91
70
|
}
|
|
92
|
-
}
|
|
71
|
+
}
|
|
93
72
|
|
|
94
|
-
|
|
95
|
-
|
|
73
|
+
/** ---------- Helpers ---------- */
|
|
74
|
+
|
|
75
|
+
const addImage = (img: Base64Image) => {
|
|
96
76
|
images.value.push({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
})
|
|
101
|
-
dialog.value = false
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
77
|
+
imageData: img.imageData ?? {},
|
|
78
|
+
imageTitle: img.imageTitle ?? '',
|
|
79
|
+
imageProps: img.imageProps ?? {},
|
|
80
|
+
})
|
|
81
|
+
dialog.value = false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const remove = (index: number) => {
|
|
85
|
+
images.value.splice(index, 1)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const setDataUpdate = (img: Base64Image) => {
|
|
89
|
+
dataUpdate.value = {
|
|
90
|
+
imageData: { ...(img.imageData ?? {}) },
|
|
91
|
+
imageTitle: img.imageTitle ?? '',
|
|
92
|
+
imageProps: { ...(img.imageProps ?? {}) },
|
|
93
|
+
}
|
|
94
|
+
dialogUpdate.value = true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const checkDuplicationName = (name: string) => images.value.some(({ imageTitle }) => isEqual(imageTitle, name))
|
|
98
|
+
|
|
99
|
+
const isImageDataUrl = (dataUrl: string) => /^data:image\//i.test(dataUrl)
|
|
100
|
+
|
|
101
|
+
const imageSrcFromImageData = (imageData: Base64Image) => {
|
|
102
|
+
let assetUrl = useRuntimeConfig().public.ASSET_URL as string
|
|
103
|
+
if (imageData?.imageData?.base64String) return useAssetFile().ensureDataUrl(imageData?.imageData?.base64String.trim(),(imageData?.imageData as Base64File).fileType || "image/png")
|
|
104
|
+
if (imageData?.imageData?.id) return (assetUrl || '/asset').replace(/\/+$/, '') +"/"+imageData?.imageData?.id
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** File → Base64Image using composable */
|
|
109
|
+
const fileToBase64Image = async (file: File): Promise<Base64Image | null> => {
|
|
110
|
+
try {
|
|
111
|
+
const base64 = await fileToBase64(file,props.maxFileSize)
|
|
112
|
+
const dataUrl = base64.base64String || ''
|
|
113
|
+
if (!isImageDataUrl(dataUrl)) {
|
|
114
|
+
alert?.addAlert({ message: `File "${base64.fileName}" is not supported image type.`, alertType: 'error' })
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
imageData: { base64String: dataUrl } as Base64Asset,
|
|
119
|
+
imageTitle: base64.fileName,
|
|
120
|
+
imageProps: {},
|
|
123
121
|
}
|
|
122
|
+
} catch (e: any) {
|
|
123
|
+
alert?.addAlert({ message: String(e), alertType: 'error' })
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Handle upload button update */
|
|
129
|
+
const uploadImageFile = async () => {
|
|
130
|
+
const duplicated: string[] = []
|
|
131
|
+
for (const file of uploadImages.value) {
|
|
132
|
+
if (checkDuplicationName(file.name)) {
|
|
133
|
+
duplicated.push(file.name)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const base64Image = await fileToBase64Image(file)
|
|
137
|
+
if (base64Image) addImage(base64Image)
|
|
124
138
|
}
|
|
125
139
|
uploadImages.value = []
|
|
126
|
-
if (
|
|
127
|
-
alert?.addAlert({message:
|
|
128
|
-
duplicatedFileName.value = ""
|
|
140
|
+
if (duplicated.length) {
|
|
141
|
+
alert?.addAlert({ message: `File(s) are duplicated. ${duplicated.join(', ')}`, alertType: 'error' })
|
|
129
142
|
}
|
|
130
|
-
}
|
|
143
|
+
}
|
|
131
144
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
145
|
+
/** Capture flow (FormDialog) */
|
|
146
|
+
type FormDialogCallback = { done: () => void }
|
|
147
|
+
const modelData = ref()
|
|
148
|
+
|
|
149
|
+
const captureImage = (payload: any, cb: FormDialogCallback) => {
|
|
150
|
+
const dataUrl: string = payload?.imageCapture ?? ''
|
|
151
|
+
if (!dataUrl || !isImageDataUrl(dataUrl)) {
|
|
152
|
+
alert?.addAlert({ message: 'Invalid image.', alertType: 'error' })
|
|
153
|
+
return
|
|
154
|
+
}
|
|
135
155
|
addImage({
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
imageData: { base64String: dataUrl },
|
|
157
|
+
imageTitle: Math.random().toString(36).slice(2, 11),
|
|
158
|
+
imageProps: {},
|
|
139
159
|
})
|
|
140
|
-
|
|
160
|
+
cb?.done?.()
|
|
141
161
|
}
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const imageFullScreen = ref({
|
|
146
|
-
title: "",
|
|
147
|
-
image: ""
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
const openImageFullScreen = (image: { [key: string]: string }) => {
|
|
163
|
+
/** Fullscreen preview */
|
|
164
|
+
const openImageFullScreen = (img: Base64Image) => {
|
|
151
165
|
dialogImageFullScreen.value = true
|
|
152
|
-
imageFullScreen.value.title =
|
|
153
|
-
imageFullScreen.value.image =
|
|
166
|
+
imageFullScreen.value.title = img.imageTitle ?? ''
|
|
167
|
+
imageFullScreen.value.image = imageSrcFromImageData(img)
|
|
154
168
|
}
|
|
155
169
|
|
|
170
|
+
/** ---------- Watchers (signature-based) ---------- */
|
|
156
171
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
/* Parent → Internal */
|
|
173
|
+
watch(
|
|
174
|
+
() => props.modelValue,
|
|
175
|
+
async (val) => {
|
|
176
|
+
if (internalSync) return
|
|
160
177
|
|
|
178
|
+
const next = Array.isArray(val) ? [...val] : []
|
|
179
|
+
const nextSig = signature(next)
|
|
161
180
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}, {deep: true});
|
|
181
|
+
// Only reassign when truly different
|
|
182
|
+
if (nextSig !== signature(images.value)) {
|
|
183
|
+
images.value = next
|
|
166
184
|
|
|
185
|
+
// optional hydration
|
|
186
|
+
if (props.autoHydrate && images.value.length) {
|
|
187
|
+
const targets = images.value.filter(
|
|
188
|
+
(im) => im.imageData?.id != null && !im.imageData?.base64String
|
|
189
|
+
)
|
|
190
|
+
if (targets.length) {
|
|
191
|
+
await Promise.allSettled(targets.map((im) => hydrateAssetFile(im.imageData!)))
|
|
192
|
+
// After hydration, emit once (guarded) to update parent
|
|
193
|
+
emitNow(images.value)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
167
196
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
197
|
+
// sync lastEmittedSig to current internal state so next local change emits
|
|
198
|
+
lastEmittedSig = signature(images.value)
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{ deep: true, immediate: true }
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
/* Internal → Parent: watch the signature instead of deep structure */
|
|
205
|
+
watch(
|
|
206
|
+
() => signature(images.value),
|
|
207
|
+
() => {
|
|
208
|
+
if (internalSync) return
|
|
209
|
+
// Only emit when signature actually changes
|
|
210
|
+
emitNow(images.value)
|
|
211
|
+
},
|
|
212
|
+
{ immediate: false }
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
// validation passthrough
|
|
216
|
+
const inputRef = ref<InstanceType<typeof VInput> | null>(null)
|
|
217
|
+
|
|
218
|
+
const isValid = computed(() => inputRef.value?.isValid)
|
|
219
|
+
const errorMessages = computed(() => inputRef.value?.errorMessages)
|
|
220
|
+
|
|
221
|
+
defineExpose({
|
|
222
|
+
errorMessages,
|
|
223
|
+
isValid,
|
|
224
|
+
reset: () => inputRef.value?.reset(),
|
|
225
|
+
resetValidation: () => inputRef.value?.resetValidation(),
|
|
226
|
+
validate: () => inputRef.value?.validate(),
|
|
177
227
|
})
|
|
178
228
|
</script>
|
|
179
229
|
|
|
180
230
|
<template>
|
|
181
|
-
<
|
|
182
|
-
<
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<v-icon>mdi mdi-camera-plus</v-icon>
|
|
199
|
-
</v-btn>
|
|
200
|
-
</VToolbarItems>
|
|
201
|
-
</VToolbar>
|
|
202
|
-
<VCardText>
|
|
203
|
-
<VRow dense justify="center">
|
|
204
|
-
<VCol v-for="(image, index) in images" :key="index" cols="4">
|
|
205
|
-
<VCard>
|
|
206
|
-
<VToolbar density="compact">
|
|
207
|
-
<VToolbarTitle>
|
|
208
|
-
{{ image.title }}
|
|
209
|
-
</VToolbarTitle>
|
|
210
|
-
<VSpacer></VSpacer>
|
|
211
|
-
<VToolbarItems v-if="!readonly">
|
|
212
|
-
<v-btn icon @click="remove(index)">
|
|
213
|
-
<v-icon>mdi mdi-delete-outline</v-icon>
|
|
214
|
-
</v-btn>
|
|
215
|
-
<v-btn
|
|
216
|
-
color="primary"
|
|
217
|
-
icon
|
|
218
|
-
@click="setDataUpdate(image)"
|
|
219
|
-
>
|
|
220
|
-
<v-icon>mdi mdi-image-edit-outline</v-icon>
|
|
221
|
-
</v-btn>
|
|
222
|
-
</VToolbarItems>
|
|
223
|
-
</VToolbar>
|
|
224
|
-
<v-img
|
|
225
|
-
:src="image.data"
|
|
226
|
-
@click="() => { (props.readonly) ? openImageFullScreen(image) : setDataUpdate(image)}"
|
|
227
|
-
height="250"
|
|
231
|
+
<v-input v-model="images" v-bind="$attrs" ref="inputRef">
|
|
232
|
+
<template #default="{ isReadonly, isDisabled }">
|
|
233
|
+
<VCard>
|
|
234
|
+
<VToolbar density="compact">
|
|
235
|
+
<VToolbarTitle>{{ label }}</VToolbarTitle>
|
|
236
|
+
<v-spacer />
|
|
237
|
+
<VToolbarItems v-if="!readonly">
|
|
238
|
+
<FileBtn
|
|
239
|
+
v-model="uploadImages"
|
|
240
|
+
:accept="accept"
|
|
241
|
+
color="primary"
|
|
242
|
+
icon="mdi:mdi-image-plus"
|
|
243
|
+
icon-only
|
|
244
|
+
multiple
|
|
245
|
+
variant="text"
|
|
246
|
+
@update:model-value="uploadImageFile"
|
|
247
|
+
:disabled="isDisabled?.value" :readonly="isReadonly?.value"
|
|
228
248
|
/>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
<v-btn color="primary" icon @click="dialog = true" :disabled="isDisabled?.value" :readonly="isReadonly?.value">
|
|
250
|
+
<v-icon>mdi mdi-camera-plus</v-icon>
|
|
251
|
+
</v-btn>
|
|
252
|
+
</VToolbarItems>
|
|
253
|
+
</VToolbar>
|
|
254
|
+
|
|
255
|
+
<VCardText>
|
|
256
|
+
<VRow dense justify="center">
|
|
257
|
+
<VCol v-for="(image, index) in images" :key="`${imageKey(image)}-${index}`" cols="4">
|
|
258
|
+
<VCard>
|
|
259
|
+
<VToolbar density="compact">
|
|
260
|
+
<VToolbarTitle>
|
|
261
|
+
{{ image.imageTitle }}
|
|
262
|
+
</VToolbarTitle>
|
|
263
|
+
<VSpacer />
|
|
264
|
+
<VToolbarItems v-if="!readonly">
|
|
265
|
+
<v-btn icon @click="remove(index)" :disabled="isDisabled?.value" :readonly="isReadonly?.value">
|
|
266
|
+
<v-icon>mdi mdi-delete-outline</v-icon>
|
|
267
|
+
</v-btn>
|
|
268
|
+
<v-btn color="primary" icon @click="setDataUpdate(image)" v-if="!image.imageData?.id" :disabled="isDisabled?.value" :readonly="isReadonly?.value">
|
|
269
|
+
<v-icon>mdi mdi-image-edit-outline</v-icon>
|
|
270
|
+
</v-btn>
|
|
271
|
+
</VToolbarItems>
|
|
272
|
+
</VToolbar>
|
|
273
|
+
|
|
274
|
+
<v-img
|
|
275
|
+
:src="imageSrcFromImageData(image)"
|
|
276
|
+
height="250"
|
|
277
|
+
@click="() => { (props.readonly || image.imageData?.id || isReadonly?.value) ? openImageFullScreen(image) : setDataUpdate(image) }"
|
|
278
|
+
:disabled="isDisabled?.value"
|
|
279
|
+
/>
|
|
280
|
+
</VCard>
|
|
281
|
+
</VCol>
|
|
282
|
+
</VRow>
|
|
283
|
+
</VCardText>
|
|
284
|
+
</VCard>
|
|
285
|
+
|
|
286
|
+
<!-- Edit dialog -->
|
|
287
|
+
<VDialog v-model="dialogUpdate" fullscreen transition="dialog-bottom-transition">
|
|
288
|
+
<FormImagesPad
|
|
289
|
+
v-model="dataUpdate.imageData.base64String"
|
|
290
|
+
@closedDialog="dialogUpdate = false"
|
|
291
|
+
/>
|
|
292
|
+
</VDialog>
|
|
293
|
+
|
|
294
|
+
<!-- Capture dialog -->
|
|
295
|
+
<FormDialog v-model="dialog" :form-data="modelData" @create="captureImage">
|
|
296
|
+
<template #default="{ data }">
|
|
297
|
+
<FormImagesCapture v-model="data.imageCapture" />
|
|
298
|
+
</template>
|
|
299
|
+
</FormDialog>
|
|
300
|
+
|
|
301
|
+
<!-- Fullscreen preview -->
|
|
302
|
+
<v-dialog v-model="dialogImageFullScreen">
|
|
303
|
+
<v-toolbar :title="imageFullScreen.title">
|
|
304
|
+
<v-spacer />
|
|
305
|
+
<v-btn icon="mdi mdi-close" @click="dialogImageFullScreen = false" />
|
|
306
|
+
</v-toolbar>
|
|
307
|
+
<v-card height="80vh">
|
|
308
|
+
<v-img :src="imageFullScreen.image" />
|
|
309
|
+
</v-card>
|
|
310
|
+
</v-dialog>
|
|
250
311
|
</template>
|
|
251
|
-
</
|
|
252
|
-
|
|
253
|
-
<v-dialog v-model="dialogImageFullScreen">
|
|
254
|
-
<v-toolbar :title="imageFullScreen.title">
|
|
255
|
-
<v-spacer/>
|
|
256
|
-
<v-btn icon="mdi mdi-close" @click="dialogImageFullScreen = false"/>
|
|
257
|
-
</v-toolbar>
|
|
258
|
-
<v-card height="80vh">
|
|
259
|
-
<v-img :src="imageFullScreen.image"/>
|
|
260
|
-
</v-card>
|
|
261
|
-
|
|
262
|
-
</v-dialog>
|
|
312
|
+
</v-input>
|
|
263
313
|
</template>
|