localhost-front/src/views/mypage/MyPage.vue
2025-04-03 15:51:30 +09:00

266 lines
7.2 KiB
Vue

<template>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card shadow-sm rounded-lg p-6 max-w-2xl mx-auto">
<h3 class="text-2xl font-semibold mb-3 text-center">마이 페이지</h3>
<form @submit.prevent="handleSubmit">
<div class="text-center">
<label
for="profilePic"
class="rounded-circle m-auto ui-bg-cover position-relative cursor-pointer"
id="profileLabel"
:style="profilePreviewStyle"
></label>
<input type="file" id="profilePic" class="d-none" @change="profileUpload" />
<span v-if="profilerr" class="invalid-feedback d-block">{{ profilerr }}</span>
</div>
<div class="col-xl-12">
<div class="d-flex">
<UserFormInput
title="입사일"
name="entryDate"
type="date"
:value="form.entryDate"
@update:data="form.entryDate = $event"
class="me-2 w-50"
/>
<FormSelect
title="컬러"
name="color"
:is-row="false"
:is-label="true"
:is-common="true"
:is-color="true"
:data="colorList"
:value="form.color"
@update:data="handleColorUpdate"
class="w-50"
/>
</div>
<div class="d-flex">
<UserFormInput
title="생년월일"
name="birth"
type="date"
:value="form.birth"
@update:data="form.birth = $event"
class="me-2 w-50"
/>
<FormSelect
title="MBTI"
name="mbti"
:is-row="false"
:is-label="true"
:is-common="true"
:is-mbti="true"
:data="mbtiList"
:value="form.mbti"
@update:data="form.mbti = $event"
class="w-50"
/>
</div>
<ArrInput
title="주소"
name="address"
v-model="form.address"
:disabled="true"
/>
<UserFormInput
title="휴대전화"
name="phone"
:value="form.phone"
@update:data="form.phone = $event"
@blur="checkPhoneDuplicate"
:maxlength="11"
@keypress="onlyNumber"
/>
<div class="d-flex mt-5">
<button type="submit" class="btn btn-primary w-100" :disabled="!isChanged">
정보 수정
</button>
</div>
</div>
</form>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import $api from '@api';
import UserFormInput from '@c/input/UserFormInput.vue';
import FormSelect from '@c/input/FormSelect.vue';
import ArrInput from '@c/input/ArrInput.vue';
import { useToastStore } from '@s/toastStore';
const toastStore = useToastStore();
const originalData = ref({});
const form = ref({
entryDate: '',
birth: '',
address: {
address: '',
detailAddress: '',
postcode: ''
},
phone: '',
color: '',
mbti: ''
});
const profile = ref('');
const uploadedFile = ref(null);
const profileChanged = ref(false);
const profilerr = ref('');
const currentBlobUrl = ref('');
const colorDuplicated = ref(false);
const phoneDuplicated = ref(false);
const mbtiList = ref([]);
const colorList = ref([]);
const isChanged = computed(() => {
const f = form.value;
const o = originalData.value;
const baseChanged =
f.entryDate !== o.entryDate ||
f.birth !== o.birth ||
f.phone !== o.phone ||
f.color !== o.color ||
f.mbti !== o.mbti ||
f.address.address !== o.address.address ||
f.address.detailAddress !== o.address.detailAddress ||
f.address.postcode !== o.address.postcode;
return baseChanged || profileChanged.value;
});
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, "");
const defaultProfile = "img/avatars/default-Profile.jpg";
const getProfileImageUrl = (fileName) => {
return fileName && fileName.trim()
? `${baseUrl}upload/img/profile/${fileName}?t=${Date.now()}`
: defaultProfile;
};
const profilePreviewStyle = computed(() => {
return {
width: '100px',
height: '100px',
backgroundImage: `url(${profile.value})`,
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
backgroundPosition: 'center'
};
});
const profileUpload = (e) => {
const file = e.target.files[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024 || !['image/jpeg', 'image/png'].includes(file.type)) {
profilerr.value = '5MB 이하의 JPG/PNG 파일만 업로드 가능합니다.';
return;
}
profilerr.value = '';
// 기존 blob 주소 해제
if (currentBlobUrl.value) {
URL.revokeObjectURL(currentBlobUrl.value);
}
uploadedFile.value = file;
const newBlobUrl = URL.createObjectURL(file);
profile.value = newBlobUrl;
currentBlobUrl.value = newBlobUrl;
profileChanged.value = true;
};
const checkPhoneDuplicate = async () => {
const res = await $api.get('/user/checkPhone', { params: { memberTel: form.value.phone } });
phoneDuplicated.value = !res.data.data;
};
const handleColorUpdate = async (colorVal) => {
form.value.color = colorVal;
const res = await $api.get('/user/checkColor', { params: { memberCol: colorVal } });
colorDuplicated.value = res.data.data;
};
const onlyNumber = (e) => {
if (!/[0-9]/.test(e.key)) e.preventDefault();
};
const formatDate = (isoDate) => {
return isoDate ? isoDate.split('T')[0] : '';
};
const loadInitialData = async () => {
const userRes = await $api.get('/user/userInfo');
const user = userRes.data.data;
const initData = {
entryDate: formatDate(user.isCdt),
birth: formatDate(user.birth),
address: {
address: user.address || '',
detailAddress: user.addressDetail || '',
postcode: user.zipcode || ''
},
phone: user.phone || '',
color: user.color || '',
mbti: user.mbit || ''
};
form.value = { ...initData };
originalData.value = { ...initData };
profile.value = getProfileImageUrl(user.profile);
profileChanged.value = false;
const colorRes = await $api.get('/user/color', { params: { type: 'main' } });
colorList.value = colorRes.data.data.map(c => ({ value: c.CMNCODVAL, label: c.CMNCODNAM }));
const mbtiRes = await $api.get('/user/mbti');
mbtiList.value = mbtiRes.data.data.map(m => ({ value: m.CMNCODVAL, label: m.CMNCODNAM }));
};
const handleSubmit = async () => {
const formData = new FormData();
formData.append('entryDate', form.value.entryDate);
formData.append('birth', form.value.birth);
formData.append('phone', form.value.phone);
formData.append('color', form.value.color);
formData.append('mbti', form.value.mbti);
formData.append('address', form.value.address.address);
formData.append('detailAddress', form.value.address.detailAddress);
formData.append('postcode', form.value.address.postcode);
if (uploadedFile.value) {
formData.append('profileFile', uploadedFile.value);
}
await $api.patch('/user/updateInfo', formData, { isFormData: true });
originalData.value = { ...form.value };
profileChanged.value = false;
toastStore.onToast('정보가 수정되었습니다.', 's');
};
onMounted(() => {
loadInitialData();
});
</script>
<style scoped>
.text-red-500 {
color: #f56565;
}
</style>