
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
<template>
<div class="sidemenu">
<div class="myinfo profile">
<div class="name-box">
<div class="img-area">
<div class="img">
<img :src="croppedImg || placeholder" alt="미리보기 이미지" />
<button class="close-btn" @click="removeImage">×</button>
</div>
<div class="info">
<div class="file">
<label for="fileUpload" class="file-label">
<img :src="file" alt="">
<button @click="showCropModal = true">
<p>업로드</p>
</button>
</label>
</div>
</div>
</div>
<ImageCropper :key="cropperKey" field="avatar" v-model="showCropModal" :width="200" :height="200"
img-format="png" lang-type="ko" @crop-success="cropSuccess" :no-square="true" :round-cropping="true"
:max-size="1024" />
</div>
</div>
</div>
<div class="content">
<div class="d-flex justify-content-center py-4">
<a href="index.html" class="logo d-flex align-items-center w-auto">
</a>
</div><!-- End Logo -->
<div class="card mb-3">
<p class="require"><img :src="require" alt=""> 필수입력</p>
<div class="card-body">
<form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }"
@submit.prevent="handleRegister" novalidate>
<div class="col-12">
<label for="loginId" class="form-label">아이디</label>
<input v-model="selectedUser.loginId" type="text" name="name" class="form-control" id="yourName" required
readonly placeholder="admin">
</div>
<div class="col-12 ">
<div class="col-12 border-x">
<label for="userNm" class="form-label ">
<p>이름
<p class="require"><img :src="require" alt=""></p>
</p>
</label>
<input v-model="requestDTO.userNm" type="text" name="username" class="form-control" id="youremail"
required>
</div>
<div class="col-12 border-x">
<label for="deptNm" class="form-label">부서</label>
<input v-model="selectedUser.deptNm" type="password" name="password" class="form-control"
id="yourPassword" required readonly placeholder="부서">
</div>
</div>
<div class="col-12">
<div class="col-12 border-x">
<label for="clsfNm" class="form-label">직급</label>
<input v-model="selectedUser.clsfNm" type="text" name="username" class="form-control" id="youremail"
required readonly placeholder="직급">
</div>
<div class="col-12 border-x">
<label for="rspofcNm" class="form-label">직책</label>
<input v-model="selectedUser.rspofcNm" type="password" name="password" class="form-control"
id="yourPassword" required readonly placeholder="직책">
</div>
</div>
<div class="col-12">
<label for="mbtlnum" class="form-label">연락처</label>
<input v-model="requestDTO.mbtlnum" type="text" name="name" class="form-control" id="yourName" required>
</div>
<div class="col-12">
<label for="brthdy" class="form-label">생년월일</label>
<input v-model="requestDTO.brthdy" type="text" name="name" class="form-control" id="yourName" required>
</div>
<div class="col-12">
<label for="yourName" class="form-label">입사일</label>
<input v-model="requestDTO.encpn" type="text" name="name" class="form-control" id="yourName" required
readonly placeholder="2025-01-01">
</div>
<div class="col-12">
<label for="oldPassword" class="form-label">현재 비밀번호</label>
<input v-model="updatePasswordDTO.oldPassword" type="password" name="name" class="form-control"
id="yourName" required>
</div>
<div class="col-12">
<label for="newPassword" class="form-label">새 비밀번호</label>
<div class="box">
<input v-model="updatePasswordDTO.newPassword" type="password" name="name" class="form-control"
id="yourName" required>
<div class="invalid-feedback">※ 비밀번호는 6~12자의 영문자와 숫자, 특수기호조합으로 작성해주세요.</div>
</div>
</div>
</form>
<div class="buttons">
<button class="btn sm btn-red " type="submit" @clcick="submitDeleteForm">회원탈퇴</button>
<button class="btn sm secondary " type="submit" @click="submitUpdateForm">수정</button>
</div>
</div>
</div>
</div>
</template>
<script>
import ImageCropper from 'vue-image-crop-upload'; //이미지 자르기기
import { findUsersDetProc, updateUsersProc, updatePassword } from "../../../resources/api/user";
export default {
data() {
return {
previewImg: null,
placeholder: "/client/resources/img/img1.png",
require: "/client/resources/img/require.png",
file: "/client/resources/img/file.png",
acceptTerms: false,
formSubmitted: false,
userData: null,
croppedImg: null, // 최종 크롭되어 표시될 이미지의 Data URL 또는 서버 이미지 URL
cropperKey: 0,
showCropModal: false, // ImageCropper 모달을 보여줄지 말지 결정하는 변수
selectedUser: {}, //내정보
requestDTO: {
loginId: null, // @NotBlank, @Size, @Pattern
clsf: null, // 선택된 직급의 코드 (예: "RESP_JRM")
rspofc: null, // 선택된 직책의 코드 (예: "RSP_DR")
userNm: null, // @NotBlank, @Size
password: null, // @NotBlank, @Size, @Pattern (이 화면에서 password 받는다면)
mbtlnum: null, // 휴대폰
telno: null, // 전화번호 (현재 화면에 없으면 null 유지)
email: null, // 이메일 (현재 화면에 없으면 null 유지)
zip: null, // 우편번호 (현재 화면에 없으면 null 유지)
adres: null, // 주소 (현재 화면에 없으면 null 유지)
detailAdres: null, // 상세주소 (현재 화면에 없으면 null 유지)
fileId: null, // 파일 아이디 (파일 저장 후 백엔드에서 반환될 값, 직접 입력X)
brthdy: null, // 생년월일 (YYYY-MM-DD 문자열)
encpn: null, // 입사일 (YYYY-MM-DD 문자열)
authorList: [], // 선택된 권한의 ID들을 담을 배열 (UserAuthorVO 리스트 형태로 변환하여 전송)
},
updatePasswordDTO: {
oldPassword: "",
newPassword: "",
resetAt: false,
},
};
},
components: {
ImageCropper, findUsersDetProc, updateUsersProc, updatePassword
},
methods: {
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
},
//사용자 정보 상세 조회
async searchUser() {
try {
const response = await findUsersDetProc(this.userData.userId);
if (response.status === 200) {
this.selectedUser = response.data.data.user; // API 응답에서 카테고리 목록을 가져옴
this.requestDTO = {
userNm: this.selectedUser.userNm,
clsf: this.selectedUser.clsf,
rspofc: this.selectedUser.rspofc || '',
mbtlnum: this.selectedUser.mbtlnum,
telno: this.selectedUser.telno,
email: this.selectedUser.email,
zip: this.selectedUser.zip,
adres: this.selectedUser.adres,
detailAdres: this.selectedUser.detailAdres,
brthdy: this.selectedUser.brthdy,
encpn: this.selectedUser.encpn,
userSttus: this.selectedUser.userSttus,
useAt: this.selectedUser.useAt,
fileId: this.selectedUser.fileId, // 기존 파일 ID 보존
authorList: this.selectedUser.authorList
}
this.croppedImg = this.selectedUser.thumbnail?.filePath || null;
console.log("유저 아이디", this.selectedUser);
}
} catch (error) {
console.error("검색 중 오류 발생:", error);
}
},
//이미지 지우기
removeImage() {
this.croppedImg = null;
this.cropperKey++;
},
//이미지 저장
cropSuccess(imgDataUrl) {
this.croppedImg = imgDataUrl;
this.showCropModal = false;
},
//회원탈퇴
async submitDeleteForm() {
const confirmDelete = confirm("정말로 사용자를 탈퇴시키겠습니까?\n이 작업은 되돌릴 수 없습니다.");
if (!confirmDelete) { // 사용자가 '아니오'를 눌렀을 때
alert("사용자 탈퇴가 취소되었습니다.");
return; // 함수 실행 중단
}
// 2. FormData 객체 생성
const formData = new FormData();
// 수정용 DTO 생성 (기존 데이터 + 수정 데이터)
const updateDTO = {
userNm: this.selectedUser.userNm,
useAt: "N",
fileId: this.selectedUser.fileId,
};
formData.append('updateUserDTO', new Blob([JSON.stringify(updateDTO)], {
type: 'application/json'
}));
// 4. API 통신
try {
const response = await updateUsersProc(this.$route.query.id, formData);
if (response.status === 200) {
alert('사용자 탈퇴퇴가 성공적으로 저장되었습니다!');
}
} catch (error) {
console.error("사용자 정보 저장 중 오류 발생:", error);
alert("사용자 정보 저장 중 오류가 발생했습니다.");
if (error.response && error.response.data && error.response.data.message) {
alert(`오류: ${error.response.data.message}`);
}
}
},
//수정용
async submitUpdateForm() {
// 1. 기본 유효성 검사
if (this.$isEmpty(this.requestDTO.userNm)) {
alert("이름을 입력해 주세요.");
return;
}
const oldPwEmpty = this.$isEmpty(this.updatePasswordDTO.oldPassword);
const newPwEmpty = this.$isEmpty(this.updatePasswordDTO.newPassword);
// 2. 비밀번호 입력 상황별 분기 처리
if (!oldPwEmpty && newPwEmpty) {
alert("새 비밀번호를 입력해주세요.");
return;
}
if (oldPwEmpty && !newPwEmpty) {
alert("기존 비밀번호를 입력해주세요.");
return;
}
if (oldPwEmpty && newPwEmpty) {
const confirmUpdate = confirm("비밀번호 변경 없이 내 정보를 수정하시겠습니까?");
if (!confirmUpdate) {
return; // 취소 시 함수 종료
}
}
console.log("비밀번호 변경 여부:", !oldPwEmpty && !newPwEmpty);
// 2. FormData 객체 생성
const formData = new FormData();
const updateDTO = {
...this.requestDTO,
};
formData.append('updateUserDTO', new Blob([JSON.stringify(updateDTO)], {
type: 'application/json'
}));
// 3. 파일 처리
// 기존 파일이 그대로 유지되는 경우 (fileId만 전송)
if (this.selectedUser.fileId && !this.croppedImg.startsWith('data:')) {
// 기존 파일 ID 유지
updateDTO.fileId = this.selectedUser.fileId;
}
// 새로운 파일이 업로드된 경우
else if (this.croppedImg && this.croppedImg.startsWith('data:')) {
const profileImageFile = this.dataURLtoFile(this.croppedImg, 'profile_image.png');
formData.append("multipartFiles", profileImageFile);
// 기존 파일 ID 제거 (새 파일로 교체)
updateDTO.fileId = null;
}
// 4. API 통신
try {
const response = await updateUsersProc(this.userData.userId, formData);
if (response.status === 200) {
if (!oldPwEmpty && !newPwEmpty) {
await this.resetPassword();
} else {
alert('사용자 정보가 성공적으로 저장되었습니다!');
}
}
} catch (error) {
console.error("사용자 정보 저장 중 오류 발생:", error);
alert("사용자 정보 저장 중 오류가 발생했습니다.");
if (error.response && error.response.data && error.response.data.message) {
alert(`오류: ${error.response.data.message}`);
}
}
},
async resetPassword() {
try {
const response = await updatePassword(this.userData.userId, this.updatePasswordDTO);
if (response.status === 200) {
alert("내 정보가 수정되었습니다.");
}
} catch (error) {
console.error("검색 중 오류 발생:", error);
}
},
},
created() {
const storedData = localStorage.getItem('vuex'); // 'vuex' 키로 바꿔야 함
if (storedData) {
const parsedData = JSON.parse(storedData);
// vuex 스토어 구조에 따라 userInfo 경로 찾아야 함
if (parsedData && parsedData.userInfo) {
this.userData = parsedData.userInfo;
console.log("userData 확인:", this.userData);
} else {
console.log("vuex 스토어에 userInfo가 없습니다.");
}
} else {
console.log("로컬스토리지에 vuex 데이터가 없습니다.");
}
},
mounted() {
this.searchUser();
},
};
</script>
<style scoped>
.profile-container {
padding: 20px;
text-align: center;
}
.info {
margin-bottom: 20px;
}
.profile-image-area {
width: 150px;
height: 150px;
border-radius: 50%;
/* 원형으로 만들게 */
overflow: hidden;
/* 영역 밖으로 넘치는 이미지 자르기 */
margin: 0 auto 20px;
/* 가운데 정렬 및 아래 여백 */
border: 2px solid #eee;
/* 테두리 */
display: flex;
align-items: center;
justify-content: center;
background-color: #f9f9f9;
/* 기본 배경색 */
}
.cropped-profile-img {
width: 100%;
height: 100%;
object-fit: cover;
/* 이미지를 영역에 꽉 채우되 비율 유지 */
}
.default-profile-icon {
font-size: 60px;
color: #ccc;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.remove-btn {
display: inline-block;
/* 버튼 형태로 보이기 위해 */
padding: 10px 20px;
margin: 5px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s ease;
}
.file-label.upload-btn:hover,
.remove-btn:hover {
background-color: #0056b3;
}
.remove-btn {
background-color: #dc3545;
}
.remove-btn:hover {
background-color: #c82333;
}
</style>