pukaad-ui-lib 1.292.0 → 1.294.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/runtime/assets/svg/heart-fill.svg +3 -0
- package/dist/runtime/components/drawer/drawer-post-review.d.vue.ts +9 -3
- package/dist/runtime/components/drawer/drawer-post-review.vue +165 -84
- package/dist/runtime/components/drawer/drawer-post-review.vue.d.ts +9 -3
- package/dist/runtime/components/image/image-cropper.d.vue.ts +2 -2
- package/dist/runtime/components/image/image-cropper.vue.d.ts +2 -2
- package/dist/runtime/components/input/input-file.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-file.vue.d.ts +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
2
|
+
<path d="M12.6666 9.33333C13.66 8.36 14.6666 7.19333 14.6666 5.66667C14.6666 4.69421 14.2803 3.76158 13.5927 3.07394C12.9051 2.38631 11.9724 2 11 2C9.82665 2 8.99998 2.33333 7.99998 3.33333C6.99998 2.33333 6.17331 2 4.99998 2C4.02752 2 3.09489 2.38631 2.40725 3.07394C1.71962 3.76158 1.33331 4.69421 1.33331 5.66667C1.33331 7.2 2.33331 8.36667 3.33331 9.33333L7.99998 14L12.6666 9.33333Z" fill="#1976B8" stroke="#1976B8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
</svg>
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
interface IFileItem {
|
|
2
|
+
file?: File;
|
|
3
|
+
url: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
1
6
|
export interface DrawerPostReviewItem {
|
|
7
|
+
placeId: string;
|
|
2
8
|
placeName: string;
|
|
3
9
|
address: string;
|
|
4
10
|
coverImage: string;
|
|
5
11
|
rating: number;
|
|
6
12
|
description: string;
|
|
7
|
-
photos:
|
|
13
|
+
photos: IFileItem[];
|
|
8
14
|
}
|
|
9
15
|
export interface DrawerPostReviewProps {
|
|
10
16
|
item?: DrawerPostReviewItem;
|
|
@@ -17,9 +23,9 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
|
17
23
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
24
|
"update:modelValue": (value: boolean) => any;
|
|
19
25
|
} & {
|
|
20
|
-
|
|
26
|
+
success: (data: any) => any;
|
|
21
27
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
22
|
-
|
|
28
|
+
onSuccess?: ((data: any) => any) | undefined;
|
|
23
29
|
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
24
30
|
}>, {
|
|
25
31
|
item: DrawerPostReviewItem;
|
|
@@ -1,80 +1,90 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Drawer
|
|
3
|
-
class="w-[748px]"
|
|
4
|
-
:title="drawerTitle"
|
|
5
|
-
@close="onClose"
|
|
6
|
-
disabled-auto-close
|
|
7
|
-
@submit="onSubmit"
|
|
8
|
-
v-model="isOpen"
|
|
9
|
-
>
|
|
10
|
-
<div class="flex flex-col gap-[16px]">
|
|
11
|
-
<div class="flex gap-[16px]">
|
|
12
|
-
<div
|
|
13
|
-
class="w-[178px] h-[100px] rounded-[8px] overflow-hidden flex-shrink-0"
|
|
14
|
-
>
|
|
15
|
-
<Image
|
|
16
|
-
v-if="form.coverImage"
|
|
17
|
-
:src="form.coverImage"
|
|
18
|
-
width="auto"
|
|
19
|
-
height="auto"
|
|
20
|
-
fit="cover"
|
|
21
|
-
/>
|
|
22
|
-
</div>
|
|
23
|
-
<div class="flex flex-col gap-[4px]">
|
|
24
|
-
<div class="font-body-large-prominent">
|
|
25
|
-
{{ form.placeName }}
|
|
26
|
-
</div>
|
|
27
|
-
<div class="font-body-small text-gray">
|
|
28
|
-
{{ form.address }}
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
<div class="flex items-center justify-center">
|
|
33
|
-
<InputRating v-model="form.rating" :size="
|
|
34
|
-
</div>
|
|
35
|
-
<InputTextarea
|
|
36
|
-
name="description"
|
|
37
|
-
label="คำอธิบาย"
|
|
38
|
-
placeholder="เขียนรีวิวของคุณ"
|
|
39
|
-
showCounter
|
|
40
|
-
v-model="form.description"
|
|
41
|
-
class="min-h-[120px]"
|
|
42
|
-
/>
|
|
43
|
-
<div class="flex flex-col gap-[8px]">
|
|
44
|
-
<div class="flex flex-col gap-[4px]">
|
|
45
|
-
<div class="text-gray font-body-large">เพิ่มภาพถ่าย</div>
|
|
46
|
-
<div class="text-gray font-body-small">อัปโหลด 9 รายการ</div>
|
|
47
|
-
</div>
|
|
48
|
-
<InputFile
|
|
49
|
-
:limit="9"
|
|
50
|
-
name="photos"
|
|
51
|
-
accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
|
|
52
|
-
v-model="form.photos"
|
|
53
|
-
/>
|
|
54
|
-
<div class="flex flex-col text-gray font-body-small">
|
|
55
|
-
<div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
|
|
56
|
-
<div>ขนาดไฟล์สูงสุด 30 mb</div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
<template #footer="{ meta }">
|
|
61
|
-
<div class="flex justify-end gap-[16px] items-center">
|
|
62
|
-
<Button variant="outline" @click="onClose"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
2
|
+
<Drawer
|
|
3
|
+
class="w-[748px]"
|
|
4
|
+
:title="drawerTitle"
|
|
5
|
+
@close="onClose"
|
|
6
|
+
disabled-auto-close
|
|
7
|
+
@submit="onSubmit"
|
|
8
|
+
v-model="isOpen"
|
|
9
|
+
>
|
|
10
|
+
<div class="flex flex-col gap-[16px]">
|
|
11
|
+
<div class="flex gap-[16px]">
|
|
12
|
+
<div
|
|
13
|
+
class="w-[178px] h-[100px] rounded-[8px] overflow-hidden flex-shrink-0"
|
|
14
|
+
>
|
|
15
|
+
<Image
|
|
16
|
+
v-if="form.coverImage"
|
|
17
|
+
:src="form.coverImage"
|
|
18
|
+
width="auto"
|
|
19
|
+
height="auto"
|
|
20
|
+
fit="cover"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex flex-col gap-[4px]">
|
|
24
|
+
<div class="font-body-large-prominent">
|
|
25
|
+
{{ form.placeName }}
|
|
26
|
+
</div>
|
|
27
|
+
<div class="font-body-small text-gray">
|
|
28
|
+
{{ form.address }}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="flex items-center justify-center py-[16px]">
|
|
33
|
+
<InputRating v-model="form.rating" :size="28" />
|
|
34
|
+
</div>
|
|
35
|
+
<InputTextarea
|
|
36
|
+
name="description"
|
|
37
|
+
label="คำอธิบาย"
|
|
38
|
+
placeholder="เขียนรีวิวของคุณ"
|
|
39
|
+
showCounter
|
|
40
|
+
v-model="form.description"
|
|
41
|
+
class="min-h-[120px]"
|
|
42
|
+
/>
|
|
43
|
+
<div class="flex flex-col gap-[8px]">
|
|
44
|
+
<div class="flex flex-col gap-[4px]">
|
|
45
|
+
<div class="text-gray font-body-large">เพิ่มภาพถ่าย</div>
|
|
46
|
+
<div class="text-gray font-body-small">อัปโหลด 9 รายการ</div>
|
|
47
|
+
</div>
|
|
48
|
+
<InputFile
|
|
49
|
+
:limit="9"
|
|
50
|
+
name="photos"
|
|
51
|
+
accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
|
|
52
|
+
v-model="form.photos"
|
|
53
|
+
/>
|
|
54
|
+
<div class="flex flex-col text-gray font-body-small">
|
|
55
|
+
<div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
|
|
56
|
+
<div>ขนาดไฟล์สูงสุด 30 mb</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<template #footer="{ meta }">
|
|
61
|
+
<div class="flex justify-end gap-[16px] items-center">
|
|
62
|
+
<Button variant="outline" @click="onClose" :disabled="isSubmitting"
|
|
63
|
+
>ยกเลิก</Button
|
|
64
|
+
>
|
|
65
|
+
<Button
|
|
66
|
+
type="submit"
|
|
67
|
+
color="primary"
|
|
68
|
+
:disabled="form.rating === 0 || isSubmitting"
|
|
69
|
+
:loading="isSubmitting"
|
|
70
|
+
>
|
|
71
|
+
ยืนยัน
|
|
72
|
+
</Button>
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
</Drawer>
|
|
69
76
|
</template>
|
|
70
77
|
|
|
71
78
|
<script setup>
|
|
79
|
+
import { useApi } from "../../composables/useApi";
|
|
72
80
|
import { ref, watch, computed } from "vue";
|
|
73
81
|
import { useNuxtApp } from "nuxt/app";
|
|
74
82
|
const { $alert } = useNuxtApp();
|
|
75
|
-
const
|
|
83
|
+
const api = useApi();
|
|
84
|
+
const emit = defineEmits(["success"]);
|
|
76
85
|
const props = defineProps({
|
|
77
86
|
item: { type: Object, required: false, default: () => ({
|
|
87
|
+
placeId: "",
|
|
78
88
|
placeName: "",
|
|
79
89
|
address: "",
|
|
80
90
|
coverImage: "",
|
|
@@ -83,22 +93,43 @@ const props = defineProps({
|
|
|
83
93
|
photos: []
|
|
84
94
|
}) }
|
|
85
95
|
});
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
const isOpen = defineModel({ type: Boolean, ...{ default: false } });
|
|
97
|
+
const isSubmitting = ref(false);
|
|
98
|
+
const form = ref({
|
|
99
|
+
placeId: props.item.placeId,
|
|
100
|
+
placeName: props.item.placeName,
|
|
101
|
+
address: props.item.address,
|
|
102
|
+
coverImage: props.item.coverImage,
|
|
103
|
+
rating: props.item.rating,
|
|
104
|
+
description: props.item.description,
|
|
105
|
+
photos: props.item.photos ? [...props.item.photos] : []
|
|
93
106
|
});
|
|
107
|
+
const isEdit = computed(() => !!(props.item?.rating && props.item.rating > 0));
|
|
108
|
+
const drawerTitle = computed(
|
|
109
|
+
() => isEdit.value ? "\u0E41\u0E01\u0E49\u0E44\u0E02\u0E23\u0E35\u0E27\u0E34\u0E27" : "\u0E40\u0E02\u0E35\u0E22\u0E19\u0E23\u0E35\u0E27\u0E34\u0E27"
|
|
110
|
+
);
|
|
94
111
|
watch(isOpen, (newVal) => {
|
|
95
112
|
if (newVal) {
|
|
96
|
-
form.value =
|
|
113
|
+
form.value = {
|
|
114
|
+
placeId: props.item.placeId,
|
|
115
|
+
placeName: props.item.placeName,
|
|
116
|
+
address: props.item.address,
|
|
117
|
+
coverImage: props.item.coverImage,
|
|
118
|
+
rating: props.item.rating,
|
|
119
|
+
description: props.item.description,
|
|
120
|
+
photos: props.item.photos ? [...props.item.photos] : []
|
|
121
|
+
};
|
|
97
122
|
}
|
|
98
123
|
});
|
|
124
|
+
const hasChanges = () => {
|
|
125
|
+
const orig = props.item;
|
|
126
|
+
if (form.value.rating !== orig.rating) return true;
|
|
127
|
+
if (form.value.description !== orig.description) return true;
|
|
128
|
+
if (form.value.photos.length !== (orig.photos?.length ?? 0)) return true;
|
|
129
|
+
return false;
|
|
130
|
+
};
|
|
99
131
|
const onClose = async () => {
|
|
100
|
-
|
|
101
|
-
if (isModified) {
|
|
132
|
+
if (hasChanges()) {
|
|
102
133
|
const { isConfirmed } = await $alert.show({
|
|
103
134
|
type: "warning",
|
|
104
135
|
title: "\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E2B\u0E19\u0E49\u0E32\u0E19\u0E35\u0E49\u0E2B\u0E23\u0E37\u0E2D\u0E44\u0E21\u0E48 ?",
|
|
@@ -107,15 +138,65 @@ const onClose = async () => {
|
|
|
107
138
|
cancelText: "\u0E41\u0E01\u0E49\u0E44\u0E02\u0E15\u0E48\u0E2D",
|
|
108
139
|
showCancelBtn: true
|
|
109
140
|
});
|
|
110
|
-
if (isConfirmed)
|
|
111
|
-
isOpen.value = false;
|
|
112
|
-
}
|
|
141
|
+
if (isConfirmed) isOpen.value = false;
|
|
113
142
|
} else {
|
|
114
143
|
isOpen.value = false;
|
|
115
144
|
}
|
|
116
145
|
};
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
146
|
+
const uploadNewPhotos = async (items) => {
|
|
147
|
+
const newItems = items.filter((it) => it.file);
|
|
148
|
+
if (newItems.length === 0) return [];
|
|
149
|
+
const fileNames = newItems.map((it) => it.file.name);
|
|
150
|
+
const presignedRes = await api("/storage/presigned-urls", {
|
|
151
|
+
method: "post",
|
|
152
|
+
body: { state: "suggest-place-review", file_name: fileNames }
|
|
153
|
+
});
|
|
154
|
+
const uploadItems = presignedRes?.data?.items ?? [];
|
|
155
|
+
if (uploadItems.length !== newItems.length) {
|
|
156
|
+
throw new Error("\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E2A\u0E23\u0E49\u0E32\u0E07 upload URL \u0E44\u0E14\u0E49 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E43\u0E2B\u0E21\u0E48\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07");
|
|
157
|
+
}
|
|
158
|
+
await Promise.all(
|
|
159
|
+
uploadItems.map((slot, i) => {
|
|
160
|
+
const file = newItems[i]?.file;
|
|
161
|
+
if (!slot || !file) return Promise.resolve();
|
|
162
|
+
return $fetch(slot.upload_url, {
|
|
163
|
+
method: "PUT",
|
|
164
|
+
body: file,
|
|
165
|
+
headers: { "Content-Type": file.type }
|
|
166
|
+
});
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
return uploadItems.map((u) => u.public_url);
|
|
170
|
+
};
|
|
171
|
+
const onSubmit = async () => {
|
|
172
|
+
if (isSubmitting.value) return;
|
|
173
|
+
isSubmitting.value = true;
|
|
174
|
+
try {
|
|
175
|
+
const existingUrls = form.value.photos.filter((it) => !it.file).map((it) => it.url);
|
|
176
|
+
const newUrls = await uploadNewPhotos(form.value.photos);
|
|
177
|
+
const allPhotos = [...existingUrls, ...newUrls];
|
|
178
|
+
const response = await api(
|
|
179
|
+
`/personal/suggest-places/${form.value.placeId}/review`,
|
|
180
|
+
{
|
|
181
|
+
method: "put",
|
|
182
|
+
body: {
|
|
183
|
+
rating: form.value.rating,
|
|
184
|
+
description: form.value.description,
|
|
185
|
+
photos: allPhotos
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
emit("success", response?.data);
|
|
190
|
+
isOpen.value = false;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
const msg = err?.data?.message || err?.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E43\u0E2B\u0E21\u0E48\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07";
|
|
193
|
+
await $alert.show({
|
|
194
|
+
type: "error",
|
|
195
|
+
title: "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
|
|
196
|
+
description: msg
|
|
197
|
+
});
|
|
198
|
+
} finally {
|
|
199
|
+
isSubmitting.value = false;
|
|
200
|
+
}
|
|
120
201
|
};
|
|
121
202
|
</script>
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
interface IFileItem {
|
|
2
|
+
file?: File;
|
|
3
|
+
url: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
1
6
|
export interface DrawerPostReviewItem {
|
|
7
|
+
placeId: string;
|
|
2
8
|
placeName: string;
|
|
3
9
|
address: string;
|
|
4
10
|
coverImage: string;
|
|
5
11
|
rating: number;
|
|
6
12
|
description: string;
|
|
7
|
-
photos:
|
|
13
|
+
photos: IFileItem[];
|
|
8
14
|
}
|
|
9
15
|
export interface DrawerPostReviewProps {
|
|
10
16
|
item?: DrawerPostReviewItem;
|
|
@@ -17,9 +23,9 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
|
17
23
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
24
|
"update:modelValue": (value: boolean) => any;
|
|
19
25
|
} & {
|
|
20
|
-
|
|
26
|
+
success: (data: any) => any;
|
|
21
27
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
22
|
-
|
|
28
|
+
onSuccess?: ((data: any) => any) | undefined;
|
|
23
29
|
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
24
30
|
}>, {
|
|
25
31
|
item: DrawerPostReviewItem;
|
|
@@ -64,15 +64,15 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
|
|
|
64
64
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageCropperProps> & Readonly<{}>, {
|
|
65
65
|
src: string;
|
|
66
66
|
center: boolean;
|
|
67
|
+
background: boolean;
|
|
68
|
+
modal: boolean;
|
|
67
69
|
responsive: boolean;
|
|
68
70
|
restore: boolean;
|
|
69
71
|
checkCrossOrigin: boolean;
|
|
70
72
|
checkOrientation: boolean;
|
|
71
73
|
crossorigin: "" | "anonymous" | "use-credentials";
|
|
72
|
-
modal: boolean;
|
|
73
74
|
guides: boolean;
|
|
74
75
|
highlight: boolean;
|
|
75
|
-
background: boolean;
|
|
76
76
|
autoCrop: boolean;
|
|
77
77
|
movable: boolean;
|
|
78
78
|
rotatable: boolean;
|
|
@@ -64,15 +64,15 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
|
|
|
64
64
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageCropperProps> & Readonly<{}>, {
|
|
65
65
|
src: string;
|
|
66
66
|
center: boolean;
|
|
67
|
+
background: boolean;
|
|
68
|
+
modal: boolean;
|
|
67
69
|
responsive: boolean;
|
|
68
70
|
restore: boolean;
|
|
69
71
|
checkCrossOrigin: boolean;
|
|
70
72
|
checkOrientation: boolean;
|
|
71
73
|
crossorigin: "" | "anonymous" | "use-credentials";
|
|
72
|
-
modal: boolean;
|
|
73
74
|
guides: boolean;
|
|
74
75
|
highlight: boolean;
|
|
75
|
-
background: boolean;
|
|
76
76
|
autoCrop: boolean;
|
|
77
77
|
movable: boolean;
|
|
78
78
|
rotatable: boolean;
|
|
@@ -35,8 +35,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
35
35
|
fullHeight: boolean;
|
|
36
36
|
name: string;
|
|
37
37
|
limit: number;
|
|
38
|
-
accept: string;
|
|
39
38
|
disabledErrorMessage: boolean;
|
|
39
|
+
accept: string;
|
|
40
40
|
labelIcon: string;
|
|
41
41
|
disabledDrop: boolean;
|
|
42
42
|
column: boolean;
|
|
@@ -35,8 +35,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
35
35
|
fullHeight: boolean;
|
|
36
36
|
name: string;
|
|
37
37
|
limit: number;
|
|
38
|
-
accept: string;
|
|
39
38
|
disabledErrorMessage: boolean;
|
|
39
|
+
accept: string;
|
|
40
40
|
labelIcon: string;
|
|
41
41
|
disabledDrop: boolean;
|
|
42
42
|
column: boolean;
|