@finema/core 2.12.2 → 2.13.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/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/FlexDeck/Base.vue +1 -1
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue +35 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue.d.ts +12 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue +54 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue.d.ts +14 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/LoadingState.vue +54 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/LoadingState.vue.d.ts +8 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/PreviewModal.vue +35 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/PreviewModal.vue.d.ts +10 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/SuccessState.vue +88 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/SuccessState.vue.d.ts +17 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +91 -76
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue.d.ts +10 -6
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +7 -5
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/useUploadState.d.ts +25 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/useUploadState.js +187 -0
- package/dist/runtime/components/Image.vue +1 -1
- package/dist/runtime/components/Table/Base.vue +77 -77
- package/dist/runtime/components/Table/Base.vue.d.ts +5 -5
- package/dist/runtime/components/Table/Simple.vue +21 -21
- package/dist/runtime/components/Table/index.vue +4 -3
- package/dist/runtime/components/Table/index.vue.d.ts +1 -1
- package/dist/runtime/components/Table/types.d.ts +1 -2
- package/dist/runtime/composables/useUpload.js +2 -4
- package/dist/runtime/helpers/componentHelper.d.ts +3 -2
- package/dist/runtime/helpers/componentHelper.js +6 -2
- package/dist/runtime/theme/uploadFileDropzone.d.ts +32 -0
- package/dist/runtime/theme/uploadFileDropzone.js +41 -5
- package/package.json +1 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useDropZone, useFileDialog } from "@vueuse/core";
|
|
2
|
+
import PreviewModal from "./PreviewModal.vue";
|
|
3
|
+
import { computed, ref, useOverlay, watch } from "#imports";
|
|
4
|
+
import { useUploadLoader } from "#core/composables/useUpload";
|
|
5
|
+
import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
|
|
6
|
+
import { StringHelper } from "#core/utils/StringHelper";
|
|
7
|
+
import { _get } from "#core/utils/lodash";
|
|
8
|
+
const overlay = useOverlay();
|
|
9
|
+
const previewModal = overlay.create(PreviewModal);
|
|
10
|
+
export var UploadState = /* @__PURE__ */ ((UploadState2) => {
|
|
11
|
+
UploadState2["EMPTY"] = "empty";
|
|
12
|
+
UploadState2["UPLOADING"] = "uploading";
|
|
13
|
+
UploadState2["SUCCESS"] = "success";
|
|
14
|
+
UploadState2["ERROR"] = "error";
|
|
15
|
+
return UploadState2;
|
|
16
|
+
})(UploadState || {});
|
|
17
|
+
export const useUploadState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
|
|
18
|
+
const selectedFile = ref();
|
|
19
|
+
const fileAllocate = useFileAllocate(selectedFile, props);
|
|
20
|
+
const {
|
|
21
|
+
percent,
|
|
22
|
+
onDownloadProgress,
|
|
23
|
+
onUploadProgress
|
|
24
|
+
} = useFileProgress();
|
|
25
|
+
const request = {
|
|
26
|
+
requestOptions: {
|
|
27
|
+
...props.requestOptions,
|
|
28
|
+
onDownloadProgress,
|
|
29
|
+
onUploadProgress
|
|
30
|
+
},
|
|
31
|
+
pathURL: props.uploadPathURL
|
|
32
|
+
};
|
|
33
|
+
const upload = useUploadLoader(request);
|
|
34
|
+
const validateFile = (file) => {
|
|
35
|
+
const acceptedTypes = fileAllocate.acceptFile.value;
|
|
36
|
+
if (acceptedTypes) {
|
|
37
|
+
const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
|
|
38
|
+
const fileExtension = file.name.toLowerCase().split(".").pop();
|
|
39
|
+
const isValidFileType = acceptedTypesList.some((acceptedType) => {
|
|
40
|
+
if (acceptedType.startsWith(".")) {
|
|
41
|
+
const extension = acceptedType.slice(1).toLowerCase();
|
|
42
|
+
return fileExtension === extension;
|
|
43
|
+
} else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
|
|
44
|
+
return fileExtension === acceptedType.toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
if (acceptedType.endsWith("/*")) {
|
|
47
|
+
const baseType = acceptedType.slice(0, -2);
|
|
48
|
+
return file.type.startsWith(baseType + "/");
|
|
49
|
+
}
|
|
50
|
+
return file.type === acceptedType;
|
|
51
|
+
});
|
|
52
|
+
if (!isValidFileType) {
|
|
53
|
+
setErrors("\u0E1B\u0E23\u0E30\u0E40\u0E20\u0E17\u0E44\u0E1F\u0E25\u0E4C\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 (\u0E23\u0E2D\u0E07\u0E23\u0E31\u0E1A\u0E40\u0E09\u0E1E\u0E32\u0E30 " + acceptedTypesList.join(", ") + ")");
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
|
|
58
|
+
if (file.size > maxSizeBytes) {
|
|
59
|
+
if (fileAllocate.isAcceptFileUseMb.value) {
|
|
60
|
+
setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
|
|
61
|
+
} else {
|
|
62
|
+
setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
setErrors("");
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
const processFile = (file) => {
|
|
70
|
+
if (!validateFile(file)) return;
|
|
71
|
+
selectedFile.value = file;
|
|
72
|
+
emits("change", file);
|
|
73
|
+
const formData = new FormData();
|
|
74
|
+
formData.append(props.bodyKey || "file", file);
|
|
75
|
+
upload.run({
|
|
76
|
+
data: formData
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const handleFileDrop = (files) => {
|
|
80
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
|
|
81
|
+
const file = files[0];
|
|
82
|
+
if (file) {
|
|
83
|
+
processFile(file);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const fileDialog = useFileDialog({
|
|
87
|
+
accept: acceptedFileTypes.value || "",
|
|
88
|
+
directory: false,
|
|
89
|
+
multiple: false
|
|
90
|
+
});
|
|
91
|
+
const dropzone = useDropZone(dropzoneRef, {
|
|
92
|
+
onDrop: handleFileDrop,
|
|
93
|
+
// dataTypes: typeof props.accept === 'string' ? [props.accept] : props.accept,
|
|
94
|
+
multiple: false,
|
|
95
|
+
preventDefaultForUnhandled: false
|
|
96
|
+
});
|
|
97
|
+
const currentState = computed(() => {
|
|
98
|
+
if (value.value) return "success" /* SUCCESS */;
|
|
99
|
+
if (selectedFile.value && upload.status.value.isLoading) return "uploading" /* UPLOADING */;
|
|
100
|
+
if (selectedFile.value && upload.status.value.isError) return "error" /* ERROR */;
|
|
101
|
+
return "empty" /* EMPTY */;
|
|
102
|
+
});
|
|
103
|
+
const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
|
|
104
|
+
const isUploading = computed(() => currentState.value === "uploading" /* UPLOADING */);
|
|
105
|
+
const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
|
|
106
|
+
const isError = computed(() => currentState.value === "error" /* ERROR */);
|
|
107
|
+
const handleInputChange = (event) => {
|
|
108
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
|
|
109
|
+
const file = event.target.files?.[0];
|
|
110
|
+
if (file) {
|
|
111
|
+
processFile(file);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const handleOpenFile = () => {
|
|
115
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
|
|
116
|
+
fileDialog.open();
|
|
117
|
+
};
|
|
118
|
+
const handleDeleteFile = () => {
|
|
119
|
+
fileDialog.reset();
|
|
120
|
+
upload.clear();
|
|
121
|
+
selectedFile.value = void 0;
|
|
122
|
+
onChange(void 0);
|
|
123
|
+
emits("delete");
|
|
124
|
+
};
|
|
125
|
+
const handleRetryUpload = () => {
|
|
126
|
+
if (selectedFile.value) {
|
|
127
|
+
const formData = new FormData();
|
|
128
|
+
formData.append(props.bodyKey || "file", selectedFile.value);
|
|
129
|
+
upload.run(formData);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handlePreview = () => {
|
|
133
|
+
previewModal.open({
|
|
134
|
+
value: value.value
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
watch(
|
|
138
|
+
() => fileDialog.files.value,
|
|
139
|
+
(files) => {
|
|
140
|
+
if (files?.length) {
|
|
141
|
+
processFile(files[0]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
watch(
|
|
146
|
+
() => upload.status.value.isSuccess,
|
|
147
|
+
(isSuccess2) => {
|
|
148
|
+
if (isSuccess2 && upload.data.value) {
|
|
149
|
+
const fileValue = {
|
|
150
|
+
url: _get(upload.data.value, props.responseURL),
|
|
151
|
+
path: _get(upload.data.value, props.responsePath),
|
|
152
|
+
name: _get(upload.data.value, props.responseName) || selectedFile.value?.name,
|
|
153
|
+
size: Number(_get(upload.data.value, props.responseSize) || selectedFile.value?.size)
|
|
154
|
+
};
|
|
155
|
+
value.value = fileValue;
|
|
156
|
+
emits("success", fileValue);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
watch(
|
|
161
|
+
() => upload.status.value.isError,
|
|
162
|
+
(isError2) => {
|
|
163
|
+
if (isError2) {
|
|
164
|
+
setErrors(StringHelper.getError(upload.status.value.errorData));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
return {
|
|
169
|
+
// State
|
|
170
|
+
currentState,
|
|
171
|
+
isEmpty,
|
|
172
|
+
isUploading,
|
|
173
|
+
isSuccess,
|
|
174
|
+
isError,
|
|
175
|
+
selectedFile,
|
|
176
|
+
// Upload utilities
|
|
177
|
+
upload,
|
|
178
|
+
dropzone,
|
|
179
|
+
percent,
|
|
180
|
+
// Handlers
|
|
181
|
+
handleInputChange,
|
|
182
|
+
handleOpenFile,
|
|
183
|
+
handleDeleteFile,
|
|
184
|
+
handleRetryUpload,
|
|
185
|
+
handlePreview
|
|
186
|
+
};
|
|
187
|
+
};
|
|
@@ -1,80 +1,80 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
v-if="!isHideCaption || !isHideBottomPagination"
|
|
4
|
-
class="mb-4 text-gray-500"
|
|
5
|
-
>
|
|
6
|
-
<span class="font-bold">ผลลัพธ์ทั้งหมด:</span>
|
|
7
|
-
จำนวน
|
|
8
|
-
<span class="font-bold">{{ pageOptions?.totalCount || 0 }}</span>
|
|
9
|
-
รายการ
|
|
10
|
-
</div>
|
|
11
|
-
<UTable
|
|
12
|
-
:loading="status.isLoading"
|
|
13
|
-
:columns="uTableCompatibleColumns"
|
|
14
|
-
:rows="rawData"
|
|
15
|
-
v-bind="$attrs"
|
|
16
|
-
>
|
|
17
|
-
<template #loading-state>
|
|
18
|
-
<div class="flex h-60 items-center justify-center">
|
|
19
|
-
<Icon
|
|
20
|
-
name="i-svg-spinners:180-ring-with-bg"
|
|
21
|
-
class="size-8
|
|
22
|
-
/>
|
|
23
|
-
</div>
|
|
24
|
-
</template>
|
|
25
|
-
<template #empty-state>
|
|
26
|
-
<Empty />
|
|
27
|
-
</template>
|
|
28
|
-
<template
|
|
29
|
-
v-for="column in columns"
|
|
30
|
-
#[`${column.accessorKey}-data`]="{ row }"
|
|
31
|
-
:key="column.accessorKey"
|
|
32
|
-
>
|
|
33
|
-
<component
|
|
34
|
-
:is="column.type === COLUMN_TYPES.COMPONENT ? column.component : column.type ? columnTypeComponents[column.type] : void 0"
|
|
35
|
-
v-if="column.type === COLUMN_TYPES.COMPONENT || column.type && columnTypeComponents[column.type]"
|
|
36
|
-
:value="transformValue(column, row)"
|
|
37
|
-
:column="column"
|
|
38
|
-
:row="row"
|
|
39
|
-
/>
|
|
40
|
-
<ColumnText
|
|
41
|
-
v-else
|
|
42
|
-
:value="transformValue(column, row)"
|
|
43
|
-
:column="column"
|
|
44
|
-
:row="row"
|
|
45
|
-
/>
|
|
46
|
-
</template>
|
|
47
|
-
|
|
48
|
-
<template
|
|
49
|
-
v-for="(_, slotName) in $slots"
|
|
50
|
-
#[slotName]="scope"
|
|
51
|
-
>
|
|
52
|
-
<slot
|
|
53
|
-
:name="slotName"
|
|
54
|
-
v-bind="scope"
|
|
55
|
-
/>
|
|
56
|
-
</template>
|
|
57
|
-
</UTable>
|
|
58
|
-
<div
|
|
59
|
-
v-if="pageOptions"
|
|
60
|
-
class="mt-4 flex justify-between px-3"
|
|
61
|
-
>
|
|
62
|
-
<p class="text-sm text-gray-500">
|
|
63
|
-
ผลลัพธ์ {{ pageBetween }} ของ {{ totalCountWithComma }} รายการ
|
|
64
|
-
</p>
|
|
65
|
-
<Pagination
|
|
66
|
-
v-if="pageOptions.totalPage > 1 && !isSimplePagination && !isHideBottomPagination"
|
|
67
|
-
v-model="page"
|
|
68
|
-
:page-count="pageOptions.limit"
|
|
69
|
-
:total="pageOptions.totalCount"
|
|
70
|
-
/>
|
|
71
|
-
<SimplePagination
|
|
72
|
-
v-if="pageOptions.totalPage > 1 && isSimplePagination"
|
|
73
|
-
v-model="page"
|
|
74
|
-
:page-count="pageOptions.limit"
|
|
75
|
-
:total="pageOptions.totalCount"
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
2
|
+
<div
|
|
3
|
+
v-if="!isHideCaption || !isHideBottomPagination"
|
|
4
|
+
class="mb-4 text-gray-500"
|
|
5
|
+
>
|
|
6
|
+
<span class="font-bold">ผลลัพธ์ทั้งหมด:</span>
|
|
7
|
+
จำนวน
|
|
8
|
+
<span class="font-bold">{{ pageOptions?.totalCount || 0 }}</span>
|
|
9
|
+
รายการ
|
|
10
|
+
</div>
|
|
11
|
+
<UTable
|
|
12
|
+
:loading="status.isLoading"
|
|
13
|
+
:columns="uTableCompatibleColumns"
|
|
14
|
+
:rows="rawData"
|
|
15
|
+
v-bind="$attrs"
|
|
16
|
+
>
|
|
17
|
+
<template #loading-state>
|
|
18
|
+
<div class="flex h-60 items-center justify-center">
|
|
19
|
+
<Icon
|
|
20
|
+
name="i-svg-spinners:180-ring-with-bg"
|
|
21
|
+
class="text-primary size-8"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
<template #empty-state>
|
|
26
|
+
<Empty />
|
|
27
|
+
</template>
|
|
28
|
+
<template
|
|
29
|
+
v-for="column in columns"
|
|
30
|
+
#[`${column.accessorKey}-data`]="{ row }"
|
|
31
|
+
:key="column.accessorKey"
|
|
32
|
+
>
|
|
33
|
+
<component
|
|
34
|
+
:is="column.type === COLUMN_TYPES.COMPONENT ? column.component : column.type ? columnTypeComponents[column.type] : void 0"
|
|
35
|
+
v-if="column.type === COLUMN_TYPES.COMPONENT || column.type && columnTypeComponents[column.type]"
|
|
36
|
+
:value="transformValue(column, row)"
|
|
37
|
+
:column="column"
|
|
38
|
+
:row="row"
|
|
39
|
+
/>
|
|
40
|
+
<ColumnText
|
|
41
|
+
v-else
|
|
42
|
+
:value="transformValue(column, row)"
|
|
43
|
+
:column="column"
|
|
44
|
+
:row="row"
|
|
45
|
+
/>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<template
|
|
49
|
+
v-for="(_, slotName) in $slots"
|
|
50
|
+
#[slotName]="scope"
|
|
51
|
+
>
|
|
52
|
+
<slot
|
|
53
|
+
:name="slotName"
|
|
54
|
+
v-bind="scope"
|
|
55
|
+
/>
|
|
56
|
+
</template>
|
|
57
|
+
</UTable>
|
|
58
|
+
<div
|
|
59
|
+
v-if="pageOptions"
|
|
60
|
+
class="mt-4 flex justify-between px-3"
|
|
61
|
+
>
|
|
62
|
+
<p class="text-sm text-gray-500">
|
|
63
|
+
ผลลัพธ์ {{ pageBetween }} ของ {{ totalCountWithComma }} รายการ
|
|
64
|
+
</p>
|
|
65
|
+
<Pagination
|
|
66
|
+
v-if="pageOptions.totalPage > 1 && !isSimplePagination && !isHideBottomPagination"
|
|
67
|
+
v-model="page"
|
|
68
|
+
:page-count="pageOptions.limit"
|
|
69
|
+
:total="pageOptions.totalCount"
|
|
70
|
+
/>
|
|
71
|
+
<SimplePagination
|
|
72
|
+
v-if="pageOptions.totalPage > 1 && isSimplePagination"
|
|
73
|
+
v-model="page"
|
|
74
|
+
:page-count="pageOptions.limit"
|
|
75
|
+
:total="pageOptions.totalCount"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
78
|
</template>
|
|
79
79
|
|
|
80
80
|
<script setup>
|
|
@@ -113,7 +113,7 @@ const props = defineProps({
|
|
|
113
113
|
type: Boolean,
|
|
114
114
|
default: false
|
|
115
115
|
},
|
|
116
|
-
|
|
116
|
+
isHidePagination: {
|
|
117
117
|
type: Boolean,
|
|
118
118
|
default: false
|
|
119
119
|
},
|
|
@@ -25,8 +25,8 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
|
|
|
25
25
|
type: PropType<ITableOptions["isSimplePagination"]>;
|
|
26
26
|
default: boolean;
|
|
27
27
|
};
|
|
28
|
-
|
|
29
|
-
type: PropType<ITableOptions["
|
|
28
|
+
isHidePagination: {
|
|
29
|
+
type: PropType<ITableOptions["isHidePagination"]>;
|
|
30
30
|
default: boolean;
|
|
31
31
|
};
|
|
32
32
|
isHideCaption: {
|
|
@@ -56,8 +56,8 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
|
|
|
56
56
|
type: PropType<ITableOptions["isSimplePagination"]>;
|
|
57
57
|
default: boolean;
|
|
58
58
|
};
|
|
59
|
-
|
|
60
|
-
type: PropType<ITableOptions["
|
|
59
|
+
isHidePagination: {
|
|
60
|
+
type: PropType<ITableOptions["isHidePagination"]>;
|
|
61
61
|
default: boolean;
|
|
62
62
|
};
|
|
63
63
|
isHideCaption: {
|
|
@@ -68,8 +68,8 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
|
|
|
68
68
|
onPageChange?: ((...args: any[]) => any) | undefined;
|
|
69
69
|
}>, {
|
|
70
70
|
isHideCaption: boolean | undefined;
|
|
71
|
-
isHideBottomPagination: boolean | undefined;
|
|
72
71
|
isSimplePagination: boolean | undefined;
|
|
72
|
+
isHidePagination: boolean | undefined;
|
|
73
73
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
74
74
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
75
75
|
export default _default;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Base
|
|
3
|
-
v-bind="$attrs"
|
|
4
|
-
:columns="options.columns"
|
|
5
|
-
:raw-data="itemsByPage"
|
|
6
|
-
:status="options.status"
|
|
7
|
-
:page-options="pageOptions"
|
|
8
|
-
:is-simple-pagination="isShowSimplePagination"
|
|
9
|
-
:is-hide-
|
|
10
|
-
:is-hide-caption="options.isHideCaption"
|
|
11
|
-
@page-change="onPageChange"
|
|
12
|
-
>
|
|
13
|
-
<template
|
|
14
|
-
v-for="(_, slot) of $slots"
|
|
15
|
-
#[slot]="slotProps"
|
|
16
|
-
>
|
|
17
|
-
<slot
|
|
18
|
-
:name="slot"
|
|
19
|
-
v-bind="slotProps || {}"
|
|
20
|
-
/>
|
|
21
|
-
</template>
|
|
22
|
-
</Base>
|
|
2
|
+
<Base
|
|
3
|
+
v-bind="$attrs"
|
|
4
|
+
:columns="options.columns"
|
|
5
|
+
:raw-data="itemsByPage"
|
|
6
|
+
:status="options.status"
|
|
7
|
+
:page-options="pageOptions"
|
|
8
|
+
:is-simple-pagination="isShowSimplePagination"
|
|
9
|
+
:is-hide-pagination="options.isHidePagination"
|
|
10
|
+
:is-hide-caption="options.isHideCaption"
|
|
11
|
+
@page-change="onPageChange"
|
|
12
|
+
>
|
|
13
|
+
<template
|
|
14
|
+
v-for="(_, slot) of $slots"
|
|
15
|
+
#[slot]="slotProps"
|
|
16
|
+
>
|
|
17
|
+
<slot
|
|
18
|
+
:name="slot"
|
|
19
|
+
v-bind="slotProps || {}"
|
|
20
|
+
/>
|
|
21
|
+
</template>
|
|
22
|
+
</Base>
|
|
23
23
|
</template>
|
|
24
24
|
|
|
25
25
|
<script setup>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
/>
|
|
14
14
|
</div>
|
|
15
15
|
<div
|
|
16
|
-
v-if="!options.isHideCaption || !options.
|
|
16
|
+
v-if="!options.isHideCaption || !options.isHidePagination"
|
|
17
17
|
:class="theme.captionContainer({
|
|
18
18
|
class: [ui?.captionContainer]
|
|
19
19
|
})"
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
name="error"
|
|
54
54
|
>
|
|
55
55
|
<div
|
|
56
|
-
class="flex h-[200px] items-center justify-center text-2xl
|
|
56
|
+
class="text-error-400 flex h-[200px] items-center justify-center text-2xl"
|
|
57
57
|
>
|
|
58
58
|
{{ StringHelper.getError(options.status.errorData) }}
|
|
59
59
|
</div>
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
</UTable>
|
|
92
92
|
|
|
93
93
|
<div
|
|
94
|
+
v-if="!options.isHidePagination"
|
|
94
95
|
:class="theme.paginationContainer({
|
|
95
96
|
class: [ui?.paginationContainer]
|
|
96
97
|
})"
|
|
@@ -100,7 +101,7 @@
|
|
|
100
101
|
class: [ui?.paginationInfo]
|
|
101
102
|
})"
|
|
102
103
|
>
|
|
103
|
-
|
|
104
|
+
{{ pageBetween }} รายการ จากทั้งหมด {{ totalCountWithComma }} รายการ
|
|
104
105
|
</p>
|
|
105
106
|
<Pagination
|
|
106
107
|
v-model:page="page"
|
|
@@ -6,7 +6,7 @@ type Slot = TableSlots<any> & {
|
|
|
6
6
|
};
|
|
7
7
|
type __VLS_Slots = Slot;
|
|
8
8
|
type __VLS_Props = {
|
|
9
|
-
options: ITableOptions
|
|
9
|
+
options: ITableOptions<any>;
|
|
10
10
|
ui?: typeof tableTheme['slots'];
|
|
11
11
|
};
|
|
12
12
|
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
@@ -19,9 +19,8 @@ export interface IBaseTableOptions<T extends Record<string, any> = Record<string
|
|
|
19
19
|
rawData: T[];
|
|
20
20
|
status: IStatus;
|
|
21
21
|
columns: TableColumn<T>[];
|
|
22
|
-
|
|
22
|
+
isHidePagination?: boolean;
|
|
23
23
|
isHideCaption?: boolean;
|
|
24
|
-
isHideTopPagination?: boolean;
|
|
25
24
|
isSimplePagination?: boolean;
|
|
26
25
|
}
|
|
27
26
|
export interface ITableOptions<T extends Record<string, any> = Record<string, any>> extends IBaseTableOptions<T> {
|
|
@@ -3,14 +3,12 @@ export const useUploadLoader = (request) => {
|
|
|
3
3
|
return useObjectLoader({
|
|
4
4
|
method: "post",
|
|
5
5
|
url: request.pathURL || "",
|
|
6
|
-
getRequestOptions: (
|
|
6
|
+
getRequestOptions: () => ({
|
|
7
7
|
...request.requestOptions,
|
|
8
8
|
headers: {
|
|
9
9
|
...request.requestOptions.headers,
|
|
10
10
|
"Content-Type": "multipart/form-data"
|
|
11
|
-
}
|
|
12
|
-
onDownloadProgress: opts.data?.onDownloadProgress,
|
|
13
|
-
onUploadProgress: opts.data?.onUploadProgress
|
|
11
|
+
}
|
|
14
12
|
})
|
|
15
13
|
});
|
|
16
14
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Ref } from 'vue';
|
|
2
|
+
import type { AxiosProgressEvent } from 'axios';
|
|
2
3
|
export declare const checkMaxSize: (file: File, acceptFileSize?: number) => boolean;
|
|
3
4
|
export declare const checkFileType: (file: File, acceptFileType: string | string[]) => boolean;
|
|
4
5
|
export declare const generateURL: (file: File) => string;
|
|
@@ -25,7 +26,7 @@ export declare const useFileSize: (size?: number) => {
|
|
|
25
26
|
};
|
|
26
27
|
export declare const useFileProgress: () => {
|
|
27
28
|
percent: Ref<number, number>;
|
|
28
|
-
onUploadProgress: (progressEvent:
|
|
29
|
-
onDownloadProgress: (progressEvent:
|
|
29
|
+
onUploadProgress: (progressEvent: AxiosProgressEvent) => void;
|
|
30
|
+
onDownloadProgress: (progressEvent: AxiosProgressEvent) => void;
|
|
30
31
|
};
|
|
31
32
|
export declare const downloadFileFromURL: (url: string, filename?: string) => Promise<void>;
|
|
@@ -86,6 +86,10 @@ export const useFileSize = (size = 0) => {
|
|
|
86
86
|
export const useFileProgress = () => {
|
|
87
87
|
const percent = ref(0);
|
|
88
88
|
const onUploadProgress = (progressEvent) => {
|
|
89
|
+
if (!progressEvent.total || progressEvent.total === 0) {
|
|
90
|
+
percent.value = 100;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
89
93
|
percent.value = Number.parseFloat(
|
|
90
94
|
StringHelper.withFixed(
|
|
91
95
|
(Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.8
|
|
@@ -93,13 +97,13 @@ export const useFileProgress = () => {
|
|
|
93
97
|
);
|
|
94
98
|
};
|
|
95
99
|
const onDownloadProgress = (progressEvent) => {
|
|
96
|
-
if (progressEvent.total === 0) {
|
|
100
|
+
if (!progressEvent.total || progressEvent.total === 0) {
|
|
97
101
|
percent.value = 100;
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
100
104
|
percent.value = Number.parseFloat(
|
|
101
105
|
StringHelper.withFixed(
|
|
102
|
-
|
|
106
|
+
Math.floor(progressEvent.loaded * 100 / progressEvent.total) * 0.2 + 80
|
|
103
107
|
)
|
|
104
108
|
);
|
|
105
109
|
};
|
|
@@ -13,6 +13,28 @@ export declare const uploadFileDropzoneTheme: {
|
|
|
13
13
|
failedImgIcon: string;
|
|
14
14
|
loadingIcon: string;
|
|
15
15
|
labelIcon: string;
|
|
16
|
+
onLoadingWrapper: string;
|
|
17
|
+
onLoadingPlaceholderWrapper: string;
|
|
18
|
+
onLoadingPlaceholderIconClass: string;
|
|
19
|
+
onLoadingTextWrapper: string;
|
|
20
|
+
onLoadingLoadingIconClass: string;
|
|
21
|
+
onPreviewWrapper: string;
|
|
22
|
+
onPreviewPreviewImgWrapper: string;
|
|
23
|
+
onPreviewPreviewImgClass: string;
|
|
24
|
+
onPreviewPreviewFileClass: string;
|
|
25
|
+
onPreviewTextWrapper: string;
|
|
26
|
+
onFailedWrapper: string;
|
|
27
|
+
onFailedFailedImgWrapper: string;
|
|
28
|
+
onFailedFailedIconClass: string;
|
|
29
|
+
onFailedTextWrapper: string;
|
|
30
|
+
actionWrapper: string;
|
|
31
|
+
actionIconClass: string;
|
|
32
|
+
actionDeleteIconClass: string;
|
|
33
|
+
actionPreviewIcon: string;
|
|
34
|
+
actionDownloadIcon: string;
|
|
35
|
+
actionDeleteIcon: string;
|
|
36
|
+
actionRetryIcon: string;
|
|
37
|
+
actionRetryBtnClass: string;
|
|
16
38
|
};
|
|
17
39
|
variants: {
|
|
18
40
|
dragover: {
|
|
@@ -20,5 +42,15 @@ export declare const uploadFileDropzoneTheme: {
|
|
|
20
42
|
base: string;
|
|
21
43
|
};
|
|
22
44
|
};
|
|
45
|
+
disabled: {
|
|
46
|
+
true: {
|
|
47
|
+
base: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
failed: {
|
|
51
|
+
true: {
|
|
52
|
+
base: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
23
55
|
};
|
|
24
56
|
};
|
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
export const uploadFileDropzoneTheme = {
|
|
2
2
|
slots: {
|
|
3
|
-
base: "relative w-full p-4 transition rounded-lg flex items-center justify-center ring-1 bg-white ring-gray-300",
|
|
3
|
+
base: "relative w-full text-base p-4 transition rounded-lg flex items-center justify-center ring-1 bg-white ring-gray-300",
|
|
4
4
|
wrapper: "flex flex-col items-center w-full",
|
|
5
5
|
disabled: "bg-gray-100 border-none grayscale cursor-not-allowed",
|
|
6
|
-
failed: "border-
|
|
6
|
+
failed: "border-error",
|
|
7
7
|
placeholderWrapper: "py-4 flex flex-col items-center justify-center",
|
|
8
8
|
placeholder: "text-gray-400 text-center font-light text-sm truncate",
|
|
9
9
|
labelWrapper: "flex items-center space-x-2 text-gray-400 text-center",
|
|
10
10
|
filePreviewIcon: "i-heroicons:document-text-solid",
|
|
11
|
-
uploadIcon: "
|
|
11
|
+
uploadIcon: "ri:upload-cloud-2-line",
|
|
12
12
|
placeholderImgIcon: "i-material-symbols:imagesmode-outline",
|
|
13
13
|
failedImgIcon: "i-material-symbols:imagesmode-outline",
|
|
14
14
|
loadingIcon: "i-svg-spinners:180-ring-with-bg",
|
|
15
|
-
labelIcon: "
|
|
15
|
+
labelIcon: "size-6 text-gray-400 text-center mb-3",
|
|
16
|
+
// Loading state
|
|
17
|
+
onLoadingWrapper: "flex items-center space-x-4 w-full",
|
|
18
|
+
onLoadingPlaceholderWrapper: "flex-shrink-0",
|
|
19
|
+
onLoadingPlaceholderIconClass: "size-12 text-gray-400",
|
|
20
|
+
onLoadingTextWrapper: "flex-1 min-w-0 flex items-center justify-between",
|
|
21
|
+
onLoadingLoadingIconClass: "size-10 text-primary animate-spin",
|
|
22
|
+
// Preview state
|
|
23
|
+
onPreviewWrapper: "flex items-center space-x-4 rounded-lg w-full",
|
|
24
|
+
onPreviewPreviewImgWrapper: "flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden bg-gray-100",
|
|
25
|
+
onPreviewPreviewImgClass: "w-full h-full object-cover",
|
|
26
|
+
onPreviewPreviewFileClass: "size-8 text-gray-400 m-auto mt-4",
|
|
27
|
+
onPreviewTextWrapper: "flex-1 min-w-0 flex items-center justify-between",
|
|
28
|
+
// Failed state
|
|
29
|
+
onFailedWrapper: "flex items-start space-x-4 w-full rounded-lg",
|
|
30
|
+
onFailedFailedImgWrapper: "flex-shrink-0",
|
|
31
|
+
onFailedFailedIconClass: "size-12",
|
|
32
|
+
onFailedTextWrapper: "flex-1 min-w-0 flex items-start justify-between",
|
|
33
|
+
// Actions
|
|
34
|
+
actionWrapper: "flex items-center space-x-2",
|
|
35
|
+
actionIconClass: "size-6 text-dimmed hover:text-dimmed-600 cursor-pointer transition-colors",
|
|
36
|
+
actionDeleteIconClass: "size-6 text-(--ui-color-error-500) hover:text-(--ui-color-error-600) cursor-pointer transition-colors",
|
|
37
|
+
actionPreviewIcon: "ic:outline-remove-red-eye",
|
|
38
|
+
actionDownloadIcon: "material-symbols:download",
|
|
39
|
+
actionDeleteIcon: "material-symbols:delete",
|
|
40
|
+
actionRetryIcon: "stash:arrow-retry",
|
|
41
|
+
actionRetryBtnClass: "px-0"
|
|
16
42
|
},
|
|
17
43
|
variants: {
|
|
18
44
|
dragover: {
|
|
19
45
|
true: {
|
|
20
|
-
base: "ring-primary bg-primary-50"
|
|
46
|
+
base: "ring-primary bg-(--ui-color-primary-50)"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
disabled: {
|
|
50
|
+
true: {
|
|
51
|
+
base: "bg-gray-100 border-none grayscale cursor-not-allowed"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
failed: {
|
|
55
|
+
true: {
|
|
56
|
+
base: "ring-(--ui-color-error-500) bg-(--ui-color-error-50)"
|
|
21
57
|
}
|
|
22
58
|
}
|
|
23
59
|
}
|