@finema/core 1.4.50 → 1.4.52
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 -0
- package/dist/module.d.ts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/Form/Fields.vue +8 -0
- package/dist/runtime/components/Form/InputSelect/index.vue +0 -1
- package/dist/runtime/components/Form/InputUploadDropzone/index.vue +16 -29
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +241 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +19 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.mjs +0 -0
- package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +3 -3
- package/dist/runtime/components/Form/types.d.ts +4 -2
- package/dist/runtime/components/Form/types.mjs +1 -0
- package/dist/runtime/components/Table/Base.vue +1 -0
- package/dist/runtime/components/Table/index.vue +9 -2
- package/dist/runtime/core.config.d.ts +1 -0
- package/dist/runtime/core.config.mjs +2 -1
- package/dist/runtime/helpers/componentHelper.d.ts +4 -0
- package/dist/runtime/helpers/componentHelper.mjs +39 -0
- package/dist/runtime/ui.config/uploadFileDropzone.d.ts +4 -1
- package/dist/runtime/ui.config/uploadFileDropzone.mjs +7 -4
- package/dist/runtime/utils/StringHelper.mjs +2 -2
- package/package.json +83 -86
package/dist/module.d.mts
CHANGED
package/dist/module.d.ts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -107,6 +107,14 @@
|
|
|
107
107
|
v-bind="getFieldBinding(option)"
|
|
108
108
|
v-on="option.on ?? {}"
|
|
109
109
|
/>
|
|
110
|
+
<FormInputUploadDropzoneAuto
|
|
111
|
+
v-else-if="option.type === INPUT_TYPES.UPLOAD_DROPZONE_AUTO"
|
|
112
|
+
:class="option.class"
|
|
113
|
+
:form="form"
|
|
114
|
+
:request-options="option.props.requestOptions"
|
|
115
|
+
v-bind="getFieldBinding(option)"
|
|
116
|
+
v-on="option.on ?? {}"
|
|
117
|
+
/>
|
|
110
118
|
</template>
|
|
111
119
|
</div>
|
|
112
120
|
</template>
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<Icon v-else :name="ui.default.filePreviewIcon" :class="[ui.preview.icon]" />
|
|
34
34
|
<div :class="[ui.preview.filename]">
|
|
35
|
-
<p>{{ selectedFile.name }}</p>
|
|
36
|
-
</div>
|
|
37
|
-
<div :class="[ui.preview.fileInfo]">
|
|
38
|
-
{{ isSelectedFileUseMb ? `${selectedFileSizeMb} MB` : `${selectedFileSizeKb} KB` }}
|
|
35
|
+
<p class="truncate">{{ selectedFile.name }}</p>
|
|
39
36
|
</div>
|
|
37
|
+
<Badge size="xs" variant="outline" class="mt-1">
|
|
38
|
+
{{ isSelectedFileUseMb ? `${selectedFileSizeMb} Mb` : `${selectedFileSizeKb} Kb` }}
|
|
39
|
+
</Badge>
|
|
40
40
|
</div>
|
|
41
41
|
<div v-if="!selectedFile" :class="[ui.placeholderWrapper]">
|
|
42
42
|
<Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
<script lang="ts" setup>
|
|
57
57
|
import { useDropZone } from '@vueuse/core'
|
|
58
58
|
import { type IUploadDropzoneProps } from './types'
|
|
59
|
+
import { isImage, generateURL, checkMaxSize, checkFileType } from '#core/helpers/componentHelper'
|
|
59
60
|
import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
|
|
60
61
|
import { useFieldHOC } from '#core/composables/useForm'
|
|
61
62
|
import { computed, ref, toRef, useUI, useUiConfig } from '#imports'
|
|
@@ -99,7 +100,7 @@ const onDrop = (files: File[] | null) => {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
const { isOverDropZone } = useDropZone(dropzoneRef, {
|
|
103
|
+
const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
|
|
103
104
|
onDrop,
|
|
104
105
|
})
|
|
105
106
|
|
|
@@ -109,7 +110,7 @@ const handleChange = (e: Event) => {
|
|
|
109
110
|
const file = (e.target as HTMLInputElement).files?.[0]
|
|
110
111
|
const result = handleCheckFileCondition(file)
|
|
111
112
|
|
|
112
|
-
if (result) {
|
|
113
|
+
if (result && file) {
|
|
113
114
|
onChange(file)
|
|
114
115
|
value.value = file
|
|
115
116
|
emit('change', file)
|
|
@@ -129,7 +130,15 @@ const handleDeleteFile = () => {
|
|
|
129
130
|
const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
130
131
|
if (!file) return false
|
|
131
132
|
|
|
132
|
-
const
|
|
133
|
+
const fileType = checkFileType(file, acceptFile.value ?? '')
|
|
134
|
+
|
|
135
|
+
if (!fileType) {
|
|
136
|
+
setErrors(i18next.t('custom:invalid_file_type'))
|
|
137
|
+
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const maxSize = checkMaxSize(file, acceptFileSizeKb.value ?? 0)
|
|
133
142
|
|
|
134
143
|
if (!maxSize) {
|
|
135
144
|
if (isAcceptFileUseMb.value) {
|
|
@@ -145,26 +154,4 @@ const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
|
145
154
|
|
|
146
155
|
return true
|
|
147
156
|
}
|
|
148
|
-
|
|
149
|
-
const isImage = (file: File) => {
|
|
150
|
-
return file.type.startsWith('image/')
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const checkMaxSize = (file: File): boolean => {
|
|
154
|
-
if (acceptFileSizeKb.value) {
|
|
155
|
-
return file.size / 1000 <= acceptFileSizeKb.value
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return true
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const generateURL = (file: File) => {
|
|
162
|
-
const fileSrc = URL.createObjectURL(file)
|
|
163
|
-
|
|
164
|
-
setTimeout(() => {
|
|
165
|
-
URL.revokeObjectURL(fileSrc)
|
|
166
|
-
}, 1000)
|
|
167
|
-
|
|
168
|
-
return fileSrc
|
|
169
|
-
}
|
|
170
157
|
</script>
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FieldWrapper v-bind="wrapperProps">
|
|
3
|
+
<div
|
|
4
|
+
ref="dropzoneRef"
|
|
5
|
+
:class="[
|
|
6
|
+
ui.base,
|
|
7
|
+
{
|
|
8
|
+
[ui.disabled]: isDisabled,
|
|
9
|
+
[ui.background.default]: !isOverDropZone && !isDisabled,
|
|
10
|
+
[ui.background.dragover]: isOverDropZone && !isDisabled,
|
|
11
|
+
},
|
|
12
|
+
]"
|
|
13
|
+
>
|
|
14
|
+
<Icon
|
|
15
|
+
v-if="selectedFile"
|
|
16
|
+
:name="ui.default.closeIcon"
|
|
17
|
+
:class="[ui.button.delete]"
|
|
18
|
+
@click="handleDeleteFile"
|
|
19
|
+
/>
|
|
20
|
+
<input
|
|
21
|
+
ref="fileInputRef"
|
|
22
|
+
type="file"
|
|
23
|
+
class="hidden"
|
|
24
|
+
:accept="acceptFile"
|
|
25
|
+
:disabled="isDisabled"
|
|
26
|
+
@change="handleChange"
|
|
27
|
+
/>
|
|
28
|
+
<div :class="[ui.wrapper]">
|
|
29
|
+
<div v-if="selectedFile" :class="[ui.preview.wrapper]">
|
|
30
|
+
<div
|
|
31
|
+
v-if="isImage(selectedFile) && upload.status.value.isLoaded"
|
|
32
|
+
:class="[ui.preview.image.wrapper]"
|
|
33
|
+
>
|
|
34
|
+
<img
|
|
35
|
+
:src="upload.data.value[responseKey || 'url']"
|
|
36
|
+
:class="[ui.preview.image.img]"
|
|
37
|
+
alt="file"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
<Icon v-else :name="ui.default.filePreviewIcon" :class="[ui.preview.icon]" />
|
|
41
|
+
<div :class="[ui.preview.filename]">
|
|
42
|
+
<p class="truncate">{{ selectedFile.name }}</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="flex items-center space-x-1">
|
|
45
|
+
<Badge size="xs" variant="outline" class="mt-1">
|
|
46
|
+
{{ isSelectedFileUseMb ? `${selectedFileSizeMb} MB` : `${selectedFileSizeKb} KB` }}
|
|
47
|
+
</Badge>
|
|
48
|
+
<div>
|
|
49
|
+
<Icon
|
|
50
|
+
v-if="upload.status.value.isSuccess"
|
|
51
|
+
name="heroicons:check-circle-20-solid"
|
|
52
|
+
class="text-success"
|
|
53
|
+
/>
|
|
54
|
+
<Icon
|
|
55
|
+
v-if="upload.status.value.isError"
|
|
56
|
+
name="heroicons:x-circle-20-solid"
|
|
57
|
+
class="text-danger"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div v-if="upload.status.value.isLoading" :class="[ui.uploadStatus.wrapper]">
|
|
63
|
+
<UProgress :class="[ui.uploadStatus.progressBar]" :value="100" />
|
|
64
|
+
</div>
|
|
65
|
+
<div v-if="!selectedFile" :class="[ui.placeholderWrapper]">
|
|
66
|
+
<Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
|
|
67
|
+
<div :class="[ui.labelWrapper]">
|
|
68
|
+
<p class="text-primary cursor-pointer" @click="handleOpenFile">
|
|
69
|
+
{{ selectFileLabel ?? 'คลิกเพื่อเลือกไฟล์' }}
|
|
70
|
+
</p>
|
|
71
|
+
<p>{{ selectFileSubLabel ?? 'หรือ ลากและวางที่นี่' }}</p>
|
|
72
|
+
</div>
|
|
73
|
+
<p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</FieldWrapper>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<script lang="ts" setup>
|
|
81
|
+
import { useDropZone } from '@vueuse/core'
|
|
82
|
+
import { type IUploadDropzoneAutoProps } from './types'
|
|
83
|
+
import { isImage, checkMaxSize, checkFileType } from '#core/helpers/componentHelper'
|
|
84
|
+
import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
|
|
85
|
+
import { useFieldHOC } from '#core/composables/useForm'
|
|
86
|
+
import {
|
|
87
|
+
type IUploadRequest,
|
|
88
|
+
computed,
|
|
89
|
+
ref,
|
|
90
|
+
toRef,
|
|
91
|
+
useUI,
|
|
92
|
+
useUiConfig,
|
|
93
|
+
useUploadLoader,
|
|
94
|
+
useWatchTrue,
|
|
95
|
+
StringHelper,
|
|
96
|
+
} from '#imports'
|
|
97
|
+
import { uploadFileDropzone } from '#core/ui.config'
|
|
98
|
+
import i18next from 'i18next'
|
|
99
|
+
|
|
100
|
+
const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
|
|
101
|
+
|
|
102
|
+
const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {})
|
|
103
|
+
const emits = defineEmits(['change', 'success', 'delete'])
|
|
104
|
+
|
|
105
|
+
const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<File>(props)
|
|
106
|
+
|
|
107
|
+
const request: IUploadRequest = {
|
|
108
|
+
pathURL: props.uploadPathURL,
|
|
109
|
+
requestOptions: props.requestOptions,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const upload = useUploadLoader(request)
|
|
113
|
+
|
|
114
|
+
const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
|
|
115
|
+
|
|
116
|
+
const fileInputRef = ref<HTMLInputElement>()
|
|
117
|
+
const dropzoneRef = ref<HTMLDivElement>()
|
|
118
|
+
const selectedFile = ref<File | undefined>()
|
|
119
|
+
const percent = ref<number>(0)
|
|
120
|
+
|
|
121
|
+
const acceptFile = computed(() =>
|
|
122
|
+
typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const acceptFileSizeKb = computed(() => props.maxSize)
|
|
126
|
+
const acceptFileSizeMb = computed(() => ((acceptFileSizeKb.value ?? 0) / 1024).toFixed(2))
|
|
127
|
+
const isAcceptFileUseMb = computed(() => acceptFileSizeKb.value && acceptFileSizeKb.value > 1024)
|
|
128
|
+
|
|
129
|
+
const selectedFileSizeKb = computed(() => ((selectedFile.value?.size || 0) / 1000).toFixed(2))
|
|
130
|
+
const selectedFileSizeMb = computed(() =>
|
|
131
|
+
((selectedFile.value?.size || 0) / 1000 / 1000).toFixed(2)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const isSelectedFileUseMb = computed(() => (selectedFile.value?.size || 0) / 1000 > 1024)
|
|
135
|
+
|
|
136
|
+
const onDrop = (files: File[] | null) => {
|
|
137
|
+
if (props.isDisabled || files?.length === 0 || !files) return
|
|
138
|
+
|
|
139
|
+
const file = files[0]
|
|
140
|
+
const result = handleCheckFileCondition(file)
|
|
141
|
+
|
|
142
|
+
if (result && file) {
|
|
143
|
+
selectedFile.value = file
|
|
144
|
+
const formData = new FormData()
|
|
145
|
+
|
|
146
|
+
emits('change', file)
|
|
147
|
+
|
|
148
|
+
formData.append(props.bodyKey || 'file', file)
|
|
149
|
+
upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
|
|
154
|
+
onDrop,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const handleChange = (e: Event) => {
|
|
158
|
+
if (props.isDisabled) return
|
|
159
|
+
|
|
160
|
+
const file = (e.target as HTMLInputElement).files?.[0]
|
|
161
|
+
const result = handleCheckFileCondition(file)
|
|
162
|
+
|
|
163
|
+
if (result && file) {
|
|
164
|
+
selectedFile.value = file
|
|
165
|
+
const formData = new FormData()
|
|
166
|
+
|
|
167
|
+
emits('change', file)
|
|
168
|
+
|
|
169
|
+
formData.append(props.bodyKey || 'file', file)
|
|
170
|
+
upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const handleOpenFile = () => {
|
|
175
|
+
fileInputRef.value?.click()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const handleDeleteFile = () => {
|
|
179
|
+
fileInputRef.value?.value && (fileInputRef.value.value = '')
|
|
180
|
+
selectedFile.value = undefined
|
|
181
|
+
onChange(undefined)
|
|
182
|
+
emits('delete')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
186
|
+
if (!file) return false
|
|
187
|
+
|
|
188
|
+
const fileType = checkFileType(file, acceptFile.value ?? '')
|
|
189
|
+
|
|
190
|
+
if (!fileType) {
|
|
191
|
+
setErrors(i18next.t('custom:invalid_file_type'))
|
|
192
|
+
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const maxSize = checkMaxSize(file, acceptFileSizeKb.value ?? 0)
|
|
197
|
+
|
|
198
|
+
if (!maxSize) {
|
|
199
|
+
if (isAcceptFileUseMb.value) {
|
|
200
|
+
setErrors(i18next.t('custom:invalid_file_size_mb', { size: acceptFileSizeMb.value }))
|
|
201
|
+
} else {
|
|
202
|
+
setErrors(i18next.t('custom:invalid_file_size_kb', { size: acceptFileSizeKb.value }))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setErrors('')
|
|
209
|
+
|
|
210
|
+
return true
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const onUploadProgress = (progressEvent: ProgressEvent) => {
|
|
214
|
+
percent.value = (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const onDownloadProgress = (progressEvent: ProgressEvent) => {
|
|
218
|
+
if (progressEvent.total === 0) {
|
|
219
|
+
percent.value = 100
|
|
220
|
+
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
percent.value = (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
useWatchTrue(
|
|
228
|
+
() => upload.status.value.isSuccess,
|
|
229
|
+
() => {
|
|
230
|
+
value.value = upload.data.value[props.responseKey || 'url']
|
|
231
|
+
emits('success', upload.data.value)
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
useWatchTrue(
|
|
236
|
+
() => upload.status.value.isError,
|
|
237
|
+
() => {
|
|
238
|
+
setErrors(StringHelper.getError(upload.status.value.errorData))
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AxiosRequestConfig } from 'axios';
|
|
2
|
+
import { type IFieldProps, type IFormFieldBase, type INPUT_TYPES } from '../types';
|
|
3
|
+
export interface IUploadDropzoneAutoProps extends IFieldProps {
|
|
4
|
+
requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
|
|
5
|
+
baseURL: string;
|
|
6
|
+
};
|
|
7
|
+
uploadPathURL?: string;
|
|
8
|
+
selectFileLabel?: string;
|
|
9
|
+
selectFileSubLabel?: string;
|
|
10
|
+
accept?: string[] | string;
|
|
11
|
+
bodyKey?: string;
|
|
12
|
+
responseKey?: string;
|
|
13
|
+
maxSize?: number;
|
|
14
|
+
}
|
|
15
|
+
export type IUploadDropzoneAutoField = IFormFieldBase<INPUT_TYPES.UPLOAD_DROPZONE_AUTO, IUploadDropzoneAutoProps, {
|
|
16
|
+
change: (value: File | undefined) => void;
|
|
17
|
+
success: (value: string) => void;
|
|
18
|
+
delete: () => void;
|
|
19
|
+
}>;
|
|
File without changes
|
|
@@ -73,16 +73,16 @@ const fileInput = ref<HTMLInputElement>()
|
|
|
73
73
|
const selectedFile = ref<File | undefined>()
|
|
74
74
|
const percent = ref<number>(0)
|
|
75
75
|
|
|
76
|
-
const acceptFileSizeKb = computed(() => props.maxSize)
|
|
77
|
-
const acceptFileSizeMb = computed(() => ((acceptFileSizeKb.value ?? 0) / 1024).toFixed(2))
|
|
78
76
|
const selectedFileSizeKb = computed(() => ((selectedFile.value?.size || 0) / 1000).toFixed(2))
|
|
79
77
|
const selectedFileSizeMb = computed(() =>
|
|
80
78
|
((selectedFile.value?.size || 0) / 1000 / 1000).toFixed(2)
|
|
81
79
|
)
|
|
82
80
|
|
|
83
81
|
const isSelectedFileUseMb = computed(() => (selectedFile.value?.size || 0) / 1000 > 1024)
|
|
84
|
-
const isAcceptFileUseMb = computed(() => acceptFileSizeKb.value && acceptFileSizeKb.value > 1024)
|
|
85
82
|
|
|
83
|
+
const acceptFileSizeKb = computed(() => props.maxSize)
|
|
84
|
+
const acceptFileSizeMb = computed(() => ((acceptFileSizeKb.value || 0) / 1024).toFixed(2))
|
|
85
|
+
const isAcceptFileUseMb = computed(() => acceptFileSizeKb.value && acceptFileSizeKb.value > 1024)
|
|
86
86
|
const acceptFile = computed(() =>
|
|
87
87
|
typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
|
|
88
88
|
)
|
|
@@ -11,6 +11,7 @@ import { type IDateTimeField } from '#core/components/Form/InputDateTime/date_ti
|
|
|
11
11
|
import { type IUploadFileClassicField } from '#core/components/Form/InputUploadFileClassic/types';
|
|
12
12
|
import { type IUploadFileField } from '#core/components/Form/InputUploadFileClassicAuto/types';
|
|
13
13
|
import { type IUploadDropzoneField } from '#core/components/Form/InputUploadDropzone/types';
|
|
14
|
+
import { type IUploadDropzoneAutoField } from '#core/components/Form/InputUploadDropzoneAuto/types';
|
|
14
15
|
export declare const enum INPUT_TYPES {
|
|
15
16
|
TEXT = "TEXT",
|
|
16
17
|
TEXTAREA = "TEXTAREA",
|
|
@@ -25,7 +26,8 @@ export declare const enum INPUT_TYPES {
|
|
|
25
26
|
DATE = "DATE",
|
|
26
27
|
UPLOAD_FILE_CLASSIC = "UPLOAD_FILE_CLASSIC",
|
|
27
28
|
UPLOAD_FILE_CLASSIC_AUTO = "UPLOAD_FILE_CLASSIC_AUTO",
|
|
28
|
-
UPLOAD_DROPZONE = "UPLOAD_DROPZONE"
|
|
29
|
+
UPLOAD_DROPZONE = "UPLOAD_DROPZONE",
|
|
30
|
+
UPLOAD_DROPZONE_AUTO = "UPLOAD_DROPZONE_AUTO"
|
|
29
31
|
}
|
|
30
32
|
export interface IFieldProps {
|
|
31
33
|
form?: FormContext;
|
|
@@ -54,4 +56,4 @@ export interface IFormFieldBase<I extends INPUT_TYPES, P extends IFieldProps, O>
|
|
|
54
56
|
props: P;
|
|
55
57
|
on?: O;
|
|
56
58
|
}
|
|
57
|
-
export type IFormField = ITextField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IUploadFileClassicField | IUploadFileField | IUploadDropzoneField;
|
|
59
|
+
export type IFormField = ITextField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IUploadFileClassicField | IUploadFileField | IUploadDropzoneField | IUploadDropzoneAutoField;
|
|
@@ -13,5 +13,6 @@ export var INPUT_TYPES = /* @__PURE__ */ ((INPUT_TYPES2) => {
|
|
|
13
13
|
INPUT_TYPES2["UPLOAD_FILE_CLASSIC"] = "UPLOAD_FILE_CLASSIC";
|
|
14
14
|
INPUT_TYPES2["UPLOAD_FILE_CLASSIC_AUTO"] = "UPLOAD_FILE_CLASSIC_AUTO";
|
|
15
15
|
INPUT_TYPES2["UPLOAD_DROPZONE"] = "UPLOAD_DROPZONE";
|
|
16
|
+
INPUT_TYPES2["UPLOAD_DROPZONE_AUTO"] = "UPLOAD_DROPZONE_AUTO";
|
|
16
17
|
return INPUT_TYPES2;
|
|
17
18
|
})(INPUT_TYPES || {});
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
:raw-data="options.rawData"
|
|
14
14
|
:status="options.status"
|
|
15
15
|
:page-options="options.pageOptions"
|
|
16
|
-
:is-simple-pagination="
|
|
16
|
+
:is-simple-pagination="isShowSimplePagination"
|
|
17
17
|
@page-change="onPageChange"
|
|
18
18
|
>
|
|
19
19
|
<template v-for="(_, slot) of $slots" #[slot]="slotProps">
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
<script lang="ts" setup>
|
|
26
26
|
import { type PropType } from 'vue'
|
|
27
27
|
import { type ITableOptions } from '#core/components/Table/types'
|
|
28
|
-
import { _debounce, ref, watch } from '#imports'
|
|
28
|
+
import { _debounce, computed, ref, watch } from '#imports'
|
|
29
|
+
import { useCoreConfig } from '#core/composables/useConfig'
|
|
29
30
|
import Base from '#core/components/Table/Base.vue'
|
|
30
31
|
|
|
31
32
|
const emits = defineEmits<{
|
|
@@ -37,8 +38,14 @@ const props = defineProps({
|
|
|
37
38
|
options: { type: Object as PropType<ITableOptions>, required: true },
|
|
38
39
|
})
|
|
39
40
|
|
|
41
|
+
const coreConfig = useCoreConfig()
|
|
42
|
+
|
|
40
43
|
const q = ref(props.options?.pageOptions.search ?? '')
|
|
41
44
|
|
|
45
|
+
const isShowSimplePagination = computed(
|
|
46
|
+
(): boolean => props.options.isSimplePagination ?? coreConfig.is_simple_pagination
|
|
47
|
+
)
|
|
48
|
+
|
|
42
49
|
watch(
|
|
43
50
|
q,
|
|
44
51
|
_debounce((value) => {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const checkMaxSize: (file: File, acceptFileSize: number) => boolean;
|
|
2
|
+
export declare const checkFileType: (file: File, acceptFileType: string | string[]) => boolean;
|
|
3
|
+
export declare const generateURL: (file: File) => string;
|
|
4
|
+
export declare const isImage: (file: File) => boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const checkMaxSize = (file, acceptFileSize) => {
|
|
2
|
+
if (acceptFileSize) {
|
|
3
|
+
return file.size / 1e3 <= acceptFileSize;
|
|
4
|
+
}
|
|
5
|
+
return true;
|
|
6
|
+
};
|
|
7
|
+
export const checkFileType = (file, acceptFileType) => {
|
|
8
|
+
if (!acceptFileType || Array.isArray(acceptFileType) && acceptFileType.length === 0)
|
|
9
|
+
return true;
|
|
10
|
+
const result = [];
|
|
11
|
+
const acceptedTypes = Array.isArray(acceptFileType) ? acceptFileType : acceptFileType.split(",");
|
|
12
|
+
for (const acceptedType of acceptedTypes) {
|
|
13
|
+
if (acceptedType.startsWith(".")) {
|
|
14
|
+
const fileExtension = `.${file.name.split(".").pop()}`;
|
|
15
|
+
if (fileExtension.toLowerCase() === acceptedType.toLowerCase())
|
|
16
|
+
result.push(true);
|
|
17
|
+
} else {
|
|
18
|
+
const fileType = acceptedType.split("/");
|
|
19
|
+
const fileExtension = file.type.split("/");
|
|
20
|
+
if (fileType.length === 2 && fileExtension.length === 2) {
|
|
21
|
+
if (fileType[1] === "*") {
|
|
22
|
+
result.push(fileType[0].toLowerCase() === fileExtension[0].toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
result.push(acceptedType.toLowerCase() === file.type.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result.includes(true);
|
|
29
|
+
};
|
|
30
|
+
export const generateURL = (file) => {
|
|
31
|
+
const fileSrc = URL.createObjectURL(file);
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
URL.revokeObjectURL(fileSrc);
|
|
34
|
+
}, 1e3);
|
|
35
|
+
return fileSrc;
|
|
36
|
+
};
|
|
37
|
+
export const isImage = (file) => {
|
|
38
|
+
return file.type.startsWith("image/");
|
|
39
|
+
};
|
|
@@ -9,11 +9,14 @@ export const uploadFileDropzone = {
|
|
|
9
9
|
wrapper: "flex flex-col items-center w-full",
|
|
10
10
|
icon: "mb-2 h-10 w-10 text-gray-400",
|
|
11
11
|
image: {
|
|
12
|
-
wrapper: "flex justify-center mb-2 rounded overflow-hidden",
|
|
13
|
-
img: "
|
|
12
|
+
wrapper: "flex justify-center mb-2 rounded overflow-hidden max-w-[300px]",
|
|
13
|
+
img: "w-full object-contain"
|
|
14
14
|
},
|
|
15
|
-
filename: "flex items-center justify-center w-full font-semibold truncate"
|
|
16
|
-
|
|
15
|
+
filename: "flex items-center justify-center w-full font-semibold truncate"
|
|
16
|
+
},
|
|
17
|
+
uploadStatus: {
|
|
18
|
+
wrapper: "w-full md:w-1/2 mt-2",
|
|
19
|
+
progressBar: ""
|
|
17
20
|
},
|
|
18
21
|
background: {
|
|
19
22
|
default: "bg-white border-gray-border",
|
|
@@ -26,9 +26,9 @@ export class StringHelper {
|
|
|
26
26
|
return newStr;
|
|
27
27
|
};
|
|
28
28
|
static getError = (errorData, defaultErrorMessage = "\u0E21\u0E35\u0E1A\u0E32\u0E07\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14") => {
|
|
29
|
-
let msg = errorData?.message;
|
|
29
|
+
let msg = errorData?.message || defaultErrorMessage;
|
|
30
30
|
if (!errorData.code || !msg) {
|
|
31
|
-
return
|
|
31
|
+
return msg;
|
|
32
32
|
}
|
|
33
33
|
if (errorData.code !== "INVALID_PARAMS" && !errorData.fields) {
|
|
34
34
|
return msg;
|
package/package.json
CHANGED
|
@@ -1,86 +1,83 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@finema/core",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"repository": "https://gitlab.finema.co/finema/ui-kit",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "Finema Dev Core Team",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/types.d.ts",
|
|
11
|
-
"import": "./dist/module.mjs",
|
|
12
|
-
"require": "./dist/module.cjs"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"main": "./dist/module.cjs",
|
|
16
|
-
"types": "./dist/types.d.ts",
|
|
17
|
-
"files": [
|
|
18
|
-
"dist"
|
|
19
|
-
],
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=18.0.0"
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"prepack": "nuxt-module-build build",
|
|
25
|
-
"dev": "nuxi dev playground",
|
|
26
|
-
"dev:build": "nuxi build playground",
|
|
27
|
-
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
28
|
-
"lint": "eslint . --cache",
|
|
29
|
-
"lint:fix": "eslint . --fix --cache",
|
|
30
|
-
"test": "vitest run",
|
|
31
|
-
"test:watch": "vitest watch",
|
|
32
|
-
"release": "release-it --ci",
|
|
33
|
-
"prepare": "husky install"
|
|
34
|
-
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"@nuxt/kit": "^3.7.4",
|
|
37
|
-
"@nuxt/ui": "^2.13.0",
|
|
38
|
-
"@pinia/nuxt": "^0.5.1",
|
|
39
|
-
"@vee-validate/nuxt": "^4.12.
|
|
40
|
-
"@vee-validate/zod": "^4.12.
|
|
41
|
-
"@vuepic/vue-datepicker": "^7.4.1",
|
|
42
|
-
"axios": "^1.6.
|
|
43
|
-
"date-fns": "^3.
|
|
44
|
-
"i18next": "^23.
|
|
45
|
-
"lodash-es": "^4.17.21",
|
|
46
|
-
"nuxt-security": "^1.
|
|
47
|
-
"pinia": "^2.1.7",
|
|
48
|
-
"qrcode.vue": "^3.4.1",
|
|
49
|
-
"url-join": "^5.0.0",
|
|
50
|
-
"zod": "^3.22.4",
|
|
51
|
-
"zod-i18n-map": "^2.
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@finema/eslint-config": "^1.2.0",
|
|
55
|
-
"@nuxt/devtools": "latest",
|
|
56
|
-
"@nuxt/eslint-config": "^0.2.0",
|
|
57
|
-
"@nuxt/module-builder": "^0.5.4",
|
|
58
|
-
"@nuxt/schema": "^3.7.4",
|
|
59
|
-
"@nuxt/test-utils": "^3.
|
|
60
|
-
"@release-it/conventional-changelog": "^8.0.1",
|
|
61
|
-
"@types/lodash-es": "^4.17.12",
|
|
62
|
-
"@types/node": "^20.10.8",
|
|
63
|
-
"@vue/test-utils": "^2.4.3",
|
|
64
|
-
"changelogen": "^0.5.5",
|
|
65
|
-
"eslint": "^8.56.0",
|
|
66
|
-
"happy-dom": "^13.0.0",
|
|
67
|
-
"husky": "^8.0.3",
|
|
68
|
-
"lint-staged": "^15.2.0",
|
|
69
|
-
"nuxt": "^3.
|
|
70
|
-
"playwright-core": "^1.40.1",
|
|
71
|
-
"prettier": "^3.1.1",
|
|
72
|
-
"release-it": "^17.0.1",
|
|
73
|
-
"sass": "^1.69.5",
|
|
74
|
-
"stylelint": "^16.1.0",
|
|
75
|
-
"stylelint-config-prettier-scss": "^1.0.0",
|
|
76
|
-
"stylelint-config-standard-scss": "^13.0.0",
|
|
77
|
-
"vitest": "^1.
|
|
78
|
-
},
|
|
79
|
-
"
|
|
80
|
-
"vue": "
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"*.{html,json}": "prettier --write"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@finema/core",
|
|
3
|
+
"version": "1.4.52",
|
|
4
|
+
"repository": "https://gitlab.finema.co/finema/ui-kit",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Finema Dev Core Team",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types.d.ts",
|
|
11
|
+
"import": "./dist/module.mjs",
|
|
12
|
+
"require": "./dist/module.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/module.cjs",
|
|
16
|
+
"types": "./dist/types.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"prepack": "nuxt-module-build build",
|
|
25
|
+
"dev": "nuxi dev playground",
|
|
26
|
+
"dev:build": "nuxi build playground",
|
|
27
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
28
|
+
"lint": "eslint . --cache",
|
|
29
|
+
"lint:fix": "eslint . --fix --cache",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest watch",
|
|
32
|
+
"release": "release-it --ci",
|
|
33
|
+
"prepare": "husky install"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@nuxt/kit": "^3.7.4",
|
|
37
|
+
"@nuxt/ui": "^2.13.0",
|
|
38
|
+
"@pinia/nuxt": "^0.5.1",
|
|
39
|
+
"@vee-validate/nuxt": "^4.12.5",
|
|
40
|
+
"@vee-validate/zod": "^4.12.5",
|
|
41
|
+
"@vuepic/vue-datepicker": "^7.4.1",
|
|
42
|
+
"axios": "^1.6.7",
|
|
43
|
+
"date-fns": "^3.3.1",
|
|
44
|
+
"i18next": "^23.8.2",
|
|
45
|
+
"lodash-es": "^4.17.21",
|
|
46
|
+
"nuxt-security": "^1.1.0",
|
|
47
|
+
"pinia": "^2.1.7",
|
|
48
|
+
"qrcode.vue": "^3.4.1",
|
|
49
|
+
"url-join": "^5.0.0",
|
|
50
|
+
"zod": "^3.22.4",
|
|
51
|
+
"zod-i18n-map": "^2.27.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@finema/eslint-config": "^1.2.0",
|
|
55
|
+
"@nuxt/devtools": "latest",
|
|
56
|
+
"@nuxt/eslint-config": "^0.2.0",
|
|
57
|
+
"@nuxt/module-builder": "^0.5.4",
|
|
58
|
+
"@nuxt/schema": "^3.7.4",
|
|
59
|
+
"@nuxt/test-utils": "^3.11.0",
|
|
60
|
+
"@release-it/conventional-changelog": "^8.0.1",
|
|
61
|
+
"@types/lodash-es": "^4.17.12",
|
|
62
|
+
"@types/node": "^20.10.8",
|
|
63
|
+
"@vue/test-utils": "^2.4.3",
|
|
64
|
+
"changelogen": "^0.5.5",
|
|
65
|
+
"eslint": "^8.56.0",
|
|
66
|
+
"happy-dom": "^13.0.0",
|
|
67
|
+
"husky": "^8.0.3",
|
|
68
|
+
"lint-staged": "^15.2.0",
|
|
69
|
+
"nuxt": "^3.10.1",
|
|
70
|
+
"playwright-core": "^1.40.1",
|
|
71
|
+
"prettier": "^3.1.1",
|
|
72
|
+
"release-it": "^17.0.1",
|
|
73
|
+
"sass": "^1.69.5",
|
|
74
|
+
"stylelint": "^16.1.0",
|
|
75
|
+
"stylelint-config-prettier-scss": "^1.0.0",
|
|
76
|
+
"stylelint-config-standard-scss": "^13.0.0",
|
|
77
|
+
"vitest": "^1.2.2"
|
|
78
|
+
},
|
|
79
|
+
"lint-staged": {
|
|
80
|
+
"*.{ts,vue,tsx,js}": "eslint --fix --cache",
|
|
81
|
+
"*.{html,json}": "prettier --write"
|
|
82
|
+
}
|
|
83
|
+
}
|