마이페이지
This commit is contained in:
parent
2072a41ca9
commit
a5fe714c73
@ -1,127 +1,265 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-xxl flex-grow-1 container-p-y">
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
<div class="card shadow-sm rounded-lg p-6 max-w-2xl mx-auto">
|
<div class="card shadow-sm rounded-lg p-6 max-w-2xl mx-auto">
|
||||||
<h2 class="text-2xl font-semibold mb-6 text-center">마이페이지</h2>
|
<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="mb-6 text-center">
|
<div class="d-flex">
|
||||||
<p class="text-gray-700 font-medium">안녕하세요, <span class="font-bold text-blue-600">{{ userInfo?.name }}</span> 님</p>
|
<UserFormInput
|
||||||
<p class="text-sm text-gray-500">사원번호: {{ userInfo?.employeeNo }}</p>
|
title="입사일"
|
||||||
</div>
|
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>
|
||||||
|
|
||||||
<!-- MBTI 선택 -->
|
<div class="d-flex">
|
||||||
<label class="block font-medium mb-1">MBTI *</label>
|
<UserFormInput
|
||||||
<select v-model="form.mbti" class="border border-gray-300 rounded w-full p-2 mb-4">
|
title="생년월일"
|
||||||
<option disabled value="">선택하세요</option>
|
name="birth"
|
||||||
<option v-for="mbti in mbtiList" :key="mbti.code" :value="mbti.code">{{ mbti.name }}</option>
|
type="date"
|
||||||
</select>
|
: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
|
||||||
<label class="block font-medium mb-1">컬러 *</label>
|
title="주소"
|
||||||
<select v-model="form.color" class="border border-gray-300 rounded w-full p-2 mb-1" @change="checkColor">
|
name="address"
|
||||||
<option disabled value="">선택하세요</option>
|
v-model="form.address"
|
||||||
<option v-for="color in colorList" :key="color.code" :value="color.code">{{ color.name }}</option>
|
:disabled="true"
|
||||||
</select>
|
/>
|
||||||
<p v-if="colorDuplicated" class="text-red-500 text-sm mb-4">이미 사용 중인 색상입니다.</p>
|
|
||||||
|
|
||||||
<!-- 전화번호 -->
|
<UserFormInput
|
||||||
<label class="block font-medium mb-1">전화번호 *</label>
|
title="휴대전화"
|
||||||
<input v-model="form.phone" class="border border-gray-300 rounded w-full p-2 mb-1" @blur="checkPhone" />
|
name="phone"
|
||||||
<p v-if="phoneDuplicated" class="text-red-500 text-sm mb-4">이미 등록된 전화번호입니다.</p>
|
:value="form.phone"
|
||||||
|
@update:data="form.phone = $event"
|
||||||
|
@blur="checkPhoneDuplicate"
|
||||||
|
:maxlength="11"
|
||||||
|
@keypress="onlyNumber"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 비밀번호 변경 -->
|
<div class="d-flex mt-5">
|
||||||
<label class="block font-medium mb-1">현재 비밀번호</label>
|
<button type="submit" class="btn btn-primary w-100" :disabled="!isChanged">
|
||||||
<input type="password" v-model="form.currentPassword" class="border border-gray-300 rounded w-full p-2 mb-4" />
|
정보 수정
|
||||||
|
</button>
|
||||||
<label class="block font-medium mb-1">새 비밀번호</label>
|
</div>
|
||||||
<input type="password" v-model="form.newPassword" class="border border-gray-300 rounded w-full p-2 mb-1" @input="validatePassword" />
|
</div>
|
||||||
<p v-if="passwordWarning" class="text-red-500 text-sm mb-1">비밀번호는 8자 이상, 특수문자/영어/숫자를 포함해야 합니다.</p>
|
</form>
|
||||||
|
|
||||||
<label class="block font-medium mb-1">비밀번호 확인</label>
|
|
||||||
<input type="password" v-model="form.newPasswordConfirm" class="border border-gray-300 rounded w-full p-2 mb-1" />
|
|
||||||
<p v-if="form.newPassword !== form.newPasswordConfirm" class="text-red-500 text-sm mb-4">비밀번호가 일치하지 않습니다.</p>
|
|
||||||
|
|
||||||
<button class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 rounded" @click="submit">
|
|
||||||
변경완료
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import $api from "@api";
|
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';
|
||||||
|
|
||||||
export default {
|
const toastStore = useToastStore();
|
||||||
data() {
|
const originalData = ref({});
|
||||||
return {
|
const form = ref({
|
||||||
userInfo: null, // 사원 정보 저장용
|
entryDate: '',
|
||||||
form: {
|
birth: '',
|
||||||
mbti: '',
|
address: {
|
||||||
color: '',
|
address: '',
|
||||||
phone: '',
|
detailAddress: '',
|
||||||
currentPassword: '',
|
postcode: ''
|
||||||
newPassword: '',
|
},
|
||||||
newPasswordConfirm: ''
|
phone: '',
|
||||||
},
|
color: '',
|
||||||
mbtiList: [],
|
mbti: ''
|
||||||
colorList: [],
|
});
|
||||||
colorDuplicated: false,
|
|
||||||
phoneDuplicated: false,
|
|
||||||
passwordWarning: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.loadUserInfo();
|
|
||||||
this.loadMBTI();
|
|
||||||
this.loadColor();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadUserInfo() {
|
|
||||||
$api.get('/api/user/info').then(res => {
|
|
||||||
this.userInfo = res.data.result;
|
|
||||||
|
|
||||||
// 폼에 사원 데이터 반영 (전화번호, mbti, color 등)
|
const profile = ref('');
|
||||||
this.form.phone = this.userInfo.phone;
|
const uploadedFile = ref(null);
|
||||||
this.form.mbti = this.userInfo.mbti;
|
const profileChanged = ref(false);
|
||||||
this.form.color = this.userInfo.color;
|
const profilerr = ref('');
|
||||||
});
|
const currentBlobUrl = ref('');
|
||||||
},
|
|
||||||
loadMBTI() {
|
|
||||||
$api.get('/api/user/mbti').then(res => this.mbtiList = res.data.result);
|
|
||||||
},
|
|
||||||
loadColor() {
|
|
||||||
$api.get('/api/user/color', { params: { type: 'main' } }).then(res => this.colorList = res.data.result);
|
|
||||||
},
|
|
||||||
checkColor() {
|
|
||||||
$api.get('/api/user/checkColor', { params: { memberCol: this.form.color } })
|
|
||||||
.then(res => this.colorDuplicated = res.data.result);
|
|
||||||
},
|
|
||||||
checkPhone() {
|
|
||||||
$api.get('/api/user/checkPhone', { params: { memberTel: this.form.phone } })
|
|
||||||
.then(res => this.phoneDuplicated = !res.data.result);
|
|
||||||
},
|
|
||||||
validatePassword() {
|
|
||||||
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+]).{8,}$/;
|
|
||||||
this.passwordWarning = !regex.test(this.form.newPassword);
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
if (this.colorDuplicated || this.phoneDuplicated || this.passwordWarning || this.form.newPassword !== this.form.newPasswordConfirm) {
|
|
||||||
alert('입력값을 다시 확인해주세요.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$api.post('/api/user/checkPassword', {
|
const colorDuplicated = ref(false);
|
||||||
currentPassword: this.form.currentPassword
|
const phoneDuplicated = ref(false);
|
||||||
}).then(res => {
|
const mbtiList = ref([]);
|
||||||
if (res.data.result) {
|
const colorList = ref([]);
|
||||||
$api.patch('/api/user/pwNew', {
|
|
||||||
newPassword: this.form.newPassword
|
const isChanged = computed(() => {
|
||||||
}).then(() => alert('비밀번호가 변경되었습니다.'));
|
const f = form.value;
|
||||||
} else {
|
const o = originalData.value;
|
||||||
alert('현재 비밀번호가 일치하지 않습니다.');
|
|
||||||
}
|
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>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.text-red-500 {
|
||||||
|
color: #f56565;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user