refactor/feat: 02 image & download image

This commit is contained in:
puriphatt 2024-08-22 13:48:59 +07:00
parent 251c460787
commit 78c0af9aae
4 changed files with 80 additions and 79 deletions

View file

@ -85,15 +85,15 @@
"prefix": [ "prefix": [
{ {
"label": "Mr.", "label": "Mr",
"value": "mr" "value": "mr"
}, },
{ {
"label": "Mrs.", "label": "Mrs",
"value": "mrs" "value": "mrs"
}, },
{ {
"label": "Miss.", "label": "Miss",
"value": "miss" "value": "miss"
} }
], ],

View file

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import AppBox from './app/AppBox.vue'; import AppBox from './app/AppBox.vue';
import { CancelButton, SaveButton } from './button';
defineExpose({ browse }); defineExpose({ browse });
defineProps<{ defineProps<{
@ -58,6 +59,22 @@ function change(e: Event) {
} }
} }
} }
async function downloadImage(url: string) {
const res = await fetch(url);
const blob = await res.blob();
let extension = '';
if (blob.type === 'image/jpeg') extension = '.jpg';
if (blob.type === 'image/png') extension = '.png';
let a = document.createElement('a');
a.download = `download${extension}`;
a.href = window.URL.createObjectURL(blob);
a.click();
a.remove();
}
</script> </script>
<template> <template>
<q-dialog v-model="dialogState"> <q-dialog v-model="dialogState">
@ -90,12 +107,22 @@ function change(e: Event) {
<div style="position: fixed; padding: var(--size-2)"> <div style="position: fixed; padding: var(--size-2)">
<q-btn <q-btn
class="upload-image-btn" class="upload-image-btn"
icon="mdi-camera" icon="mdi-camera-outline"
size="md" size="md"
unelevated unelevated
round round
v-if="!changeDisabled" v-if="!changeDisabled"
@click="inputFile?.click()" @click="inputFile?.click()"
style="color: hsla(var(--stone-0-hsl) / 0.7)"
></q-btn>
<q-btn
class="upload-image-btn q-ml-md"
icon="mdi-download-outline"
size="md"
unelevated
round
@click="downloadImage(imageUrl)"
style="color: hsla(var(--stone-0-hsl) / 0.7)"
></q-btn> ></q-btn>
</div> </div>
</div> </div>
@ -106,6 +133,7 @@ function change(e: Event) {
<q-btn <q-btn
dense dense
unelevated unelevated
flat
:color="$q.dark.isActive ? 'grey-9' : 'grey-4'" :color="$q.dark.isActive ? 'grey-9' : 'grey-4'"
:text-color="$q.dark.isActive ? 'grey-1' : 'grey-10'" :text-color="$q.dark.isActive ? 'grey-1' : 'grey-10'"
:label="$t('clear')" :label="$t('clear')"
@ -123,29 +151,19 @@ function change(e: Event) {
" "
v-if="clearButton" v-if="clearButton"
/> />
<q-btn <CancelButton
dense outlined
unelevated class="q-px-md q-mr-sm"
:color="$q.dark.isActive ? 'grey-9' : 'grey-4'"
:text-color="$q.dark.isActive ? 'grey-1' : 'grey-10'"
:label="$t('cancel')"
@click=" @click="
inputFile && (inputFile.value = ''), inputFile && (inputFile.value = ''),
(imageUrl = defaultUrl || fallbackUrl || ''), (imageUrl = defaultUrl || fallbackUrl || ''),
(file = null) (file = null)
" "
class="q-px-md q-mr-sm"
v-close-popup v-close-popup
/> />
<q-btn <SaveButton
dense outlined
unelevated
id="submit-btn"
type="submit"
color="primary"
class="q-px-md"
@click="$emit('save', inputFile?.files?.[0] || null, imageUrl)" @click="$emit('save', inputFile?.files?.[0] || null, imageUrl)"
:label="$t('save')"
/> />
</div> </div>
</AppBox> </AppBox>

View file

@ -74,7 +74,6 @@ const showOverlay = ref(false);
class="relative-position" class="relative-position"
style="z-index: 1" style="z-index: 1"
:style="{ :style="{
background: `${bgColor || 'var(--brand-1)'}`,
color: `${color || 'white'}`, color: `${color || 'white'}`,
cursor: `${noImageAction ? 'default' : 'pointer'}`, cursor: `${noImageAction ? 'default' : 'pointer'}`,
}" }"
@ -93,8 +92,8 @@ const showOverlay = ref(false);
<template #error> <template #error>
<div <div
class="full-width full-height flex items-center justify-center" class="full-width full-height flex items-center justify-center"
style="background-color: transparent"
:style="{ :style="{
background: `${bgColor || 'var(--brand-1)'}`,
color: `${color || 'white'}`, color: `${color || 'white'}`,
}" }"
> >
@ -105,8 +104,8 @@ const showOverlay = ref(false);
<div <div
v-else v-else
class="full-width full-height flex items-center justify-center" class="full-width full-height flex items-center justify-center"
style="background-color: transparent"
:style="{ :style="{
background: `${bgColor || 'var(--brand-1)'}`,
color: `${color || 'white'}`, color: `${color || 'white'}`,
}" }"
> >
@ -114,7 +113,16 @@ const showOverlay = ref(false);
</div> </div>
</template> </template>
</q-img> </q-img>
<q-icon id="profile-view" v-else :name="icon || 'mdi-account'" /> <div
v-else
class="full-width full-height flex items-center justify-center"
:style="{
background: `${bgColor || 'var(--brand-1)'}`,
color: `${color || 'white'}`,
}"
>
<q-icon :name="icon || 'mdi-account'" />
</div>
<q-badge <q-badge
v-if="!hideActive" v-if="!hideActive"
class="absolute-bottom-right" class="absolute-bottom-right"

View file

@ -237,33 +237,6 @@ const imageUrl = ref<string>('');
const profileFileImg = ref<File | null>(null); const profileFileImg = ref<File | null>(null);
const imageDialog = ref(false); const imageDialog = ref(false);
const inputFileImg = (() => {
const element = document.createElement('input');
element.type = 'file';
element.accept = 'image/*';
const reader = new FileReader();
reader.addEventListener('load', () => {
if (typeof reader.result === 'string') imageUrl.value = reader.result;
if (infoDrawerEdit.value && currentUser.value)
currentUser.value.profileImageUrl = imageUrl.value as string;
// profileUrl.value = currentUser.value?.profileImageUrl as string;
// if (infoDrawerEmployeeEdit.value && infoEmployeePersonCard.value)
// infoEmployeePersonCard.value[0].img = profileUrl.value as string;
});
element.addEventListener('change', () => {
profileFileImg.value = element.files?.[0] || null;
if (profileFileImg.value) {
reader.readAsDataURL(profileFileImg.value);
}
});
return element;
})();
// const inputFile = document.createElement('input'); // const inputFile = document.createElement('input');
// inputFile.type = 'file'; // inputFile.type = 'file';
// inputFile.accept = 'image/*'; // inputFile.accept = 'image/*';
@ -317,23 +290,18 @@ const columns = [
}, },
] satisfies QTableProps['columns']; ] satisfies QTableProps['columns'];
reader.addEventListener('load', () => { // reader.addEventListener('load', () => {
if (typeof reader.result === 'string') { // if (typeof reader.result === 'string') {
urlProfile.value = reader.result; // urlProfile.value = reader.result;
} // }
if (infoDrawerEdit.value) infoPersonCard.value[0].img = urlProfile.value; // if (infoDrawerEdit.value) infoPersonCard.value[0].img = urlProfile.value;
}); // });
watch(profileFileImg, () => { watch(profileFileImg, () => {
if (profileFileImg.value) reader.readAsDataURL(profileFileImg.value); if (profileFileImg.value) reader.readAsDataURL(profileFileImg.value);
}); });
inputFileImg.addEventListener('change', (e) => {
profileFileImg.value =
(e.currentTarget as HTMLInputElement).files?.[0] || null;
});
const selectorList = computed(() => [ const selectorList = computed(() => [
{ label: 'USER', count: typeStats.value?.USER || 0 }, { label: 'USER', count: typeStats.value?.USER || 0 },
{ label: 'MESSENGER', count: typeStats.value?.MESSENGER || 0 }, { label: 'MESSENGER', count: typeStats.value?.MESSENGER || 0 },
@ -427,7 +395,7 @@ function onClose() {
brId.value = ''; brId.value = '';
userId.value = ''; userId.value = '';
userCode.value = ''; userCode.value = '';
urlProfile.value = undefined; urlProfile.value = '';
profileFileImg.value = null; profileFileImg.value = null;
infoDrawerEdit.value = false; infoDrawerEdit.value = false;
agencyFile.value = []; agencyFile.value = [];
@ -441,7 +409,6 @@ function onClose() {
mapUserType(selectorLabel.value); mapUserType(selectorLabel.value);
flowStore.rotate(); flowStore.rotate();
} }
async function onSubmit() { async function onSubmit() {
if (profileSubmit.value) { if (profileSubmit.value) {
formData.value.profileImage = profileFileImg.value as File; formData.value.profileImage = profileFileImg.value as File;
@ -1171,7 +1138,7 @@ watch(
<q-img <q-img
class="text-center" class="text-center"
:ratio="1" :ratio="1"
:src="`${apiBaseUrl}/user/${props.row.id}/image`" :src="`${apiBaseUrl}/user/${props.row.id}/image?ts=${Date.now()}`"
> >
<template #error> <template #error>
<div <div
@ -1650,7 +1617,7 @@ watch(
:menu="formMenuIcon" :menu="formMenuIcon"
:readonly="!infoDrawerEdit" :readonly="!infoDrawerEdit"
:toggleTitle="$t('formDialogTitleUserStatus')" :toggleTitle="$t('formDialogTitleUserStatus')"
:title="`${formData.firstName} ${formData.lastName}`" :title="`${$i18n.locale === 'en-US' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
:caption="userCode" :caption="userCode"
:img=" :img="
urlProfile || urlProfile ||
@ -1659,8 +1626,14 @@ watch(
female: '/no-img-female.png', female: '/no-img-female.png',
}[formData.gender] }[formData.gender]
" "
:fallbackImg="
{
male: '/no-img-man.png',
female: '/no-img-female.png',
}[formData.gender]
"
@view="openImageDialog" @view="openImageDialog"
@edit="inputFileImg.click()" @edit="refImageUpload && refImageUpload.browse()"
@update:toggle-status=" @update:toggle-status="
async (v) => { async (v) => {
await triggerChangeStatus(infoPersonId, v); await triggerChangeStatus(infoPersonId, v);
@ -1871,9 +1844,21 @@ watch(
bgColor="linear-gradient(135deg, rgba(43,137,223,1) 0%, rgba(230,51,81,1) 100%)" bgColor="linear-gradient(135deg, rgba(43,137,223,1) 0%, rgba(230,51,81,1) 100%)"
v-model:toggle-status="formData.status" v-model:toggle-status="formData.status"
:menu="formMenuIcon" :menu="formMenuIcon"
:img="urlProfile || undefined" :img="
urlProfile ||
{
male: '/no-img-man.png',
female: '/no-img-female.png',
}[formData.gender]
"
:toggleTitle="$t('formDialogTitleUserStatus')" :toggleTitle="$t('formDialogTitleUserStatus')"
:title="`${formData.firstName} ${formData.lastName}`" :title="`${$i18n.locale === 'en-US' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
:fallbackImg="
{
male: '/no-img-man.png',
female: '/no-img-female.png',
}[formData.gender]
"
hideFade hideFade
@view="imageDialog = true" @view="imageDialog = true"
@edit="refImageUpload && refImageUpload.browse()" @edit="refImageUpload && refImageUpload.browse()"
@ -1885,16 +1870,6 @@ watch(
" "
/> />
</div> </div>
<!-- <template #prepend>
<ProfileUpload
prefix-id="form-dialog-personnel"
v-model:url-profile="urlProfile"
v-model:status-toggle="statusToggle"
v-model:profile-submit="profileSubmit"
@input-file="inputFileImg.click()"
@cancel-file="inputFileImg.value = ''"
/>
</template> -->
<div <div
class="col surface-1 q-ma-lg rounded bordered row relative-position" class="col surface-1 q-ma-lg rounded bordered row relative-position"
@ -2015,12 +1990,12 @@ watch(
</div> </div>
</DialogForm> </DialogForm>
<!-- :default-url="`${baseUrl}/user/${currentUser?.id}/image`" -->
<ImageUploadDialog <ImageUploadDialog
ref="refImageUpload" ref="refImageUpload"
v-model:dialogState="imageDialog" v-model:dialogState="imageDialog"
v-model:file="profileFileImg" v-model:file="profileFileImg"
v-model:image-url="urlProfile" v-model:image-url="urlProfile"
:default-url="`${baseUrl}/user/${currentUser?.id}/image`"
:hiddenFooter="!isImageEdit" :hiddenFooter="!isImageEdit"
:changeDisabled="!isImageEdit" :changeDisabled="!isImageEdit"
@save="imageDialog = false" @save="imageDialog = false"