프론트 라이브러리 설치
- Cropper.js
- npm install cropper
- npm install jquery-cropper
Cropper.js 사용하기
Profile DTO에 이미지 경로를 위해 필드 추가
package me.weekbelt.studyolle.settings;
@NoArgsConstructor
@Data
public class Profile {
// 기존코드 ........
private String profileImage;
public Profile(Account account) {
// 기존코드 ............
this.profileImage = account.getProfileImage();
}
}
/settings/profile.html에 이미지 폼 추가
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org">
<!--head-->
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<!--네비게이션 바-->
<div th:replace="fragments.html::main-nav"></div>
<div class="container">
<div class="row mt-5 justify-content-center">
<div class="col-2">
<div th:replace="fragments.html :: settings-menu(currentMenu='profile')"></div>
</div>
<div class="col-8">
<div th:if="${message}" class="alert alert-info alert-dismissible fade show mt-3" role="alert">
<span th:text="${message}">메시지</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">x</span>
</button>
</div>
<div class="row">
<h2 class="col-sm-12" th:text="${account.nickname}">weekbelt</h2>
</div>
<div class="row mt-3">
<form action="#" class="col-sm-6"
th:action="@{/settings/profile}" th:object="${profile}" method="post" novalidate>
// 기존 코드 .........
// 추가
<div class="form-group">
<input id="profileImage" type="hidden" th:field="*{profileImage}" class="form-control"/>
</div>
// 기존 코드 .........
</form>
// 추가
<div class="col-sm-6">
<div class="card text-center">
<div class="card-header">
프로필 이미지
</div>
<div id="current-profile-image" class="mt-3">
<svg th:if="${#strings.isEmpty(profile.profileImage)}" class="rounded"
th:data-jdenticon-value="${account.nickname}" width="125" height="125"></svg>
<img th:if="${!#strings.isEmpty(profile.profileImage)}" class="rounded"
th:src="${profile.profileImage}" width="125" height="125" alt="name"
th:alt="${account.nickname}"/>
</div>
<div id="new-profile-image" class="mt-3"></div>
<div class="card-body">
<div class="custom-file">
<input type="file" class="custom-file-input" id="profile-image-file">
<label class="custom-file-label" for="profile-image-file">프로필 이미지 변경</label>
</div>
<div id="new-profile-image-control" class="mt-3">
<button class="btn btn-outline-primary btn-block" id="cut-button">자르기</button>
<button class="btn btn-outline-success btn-block" id="confirm-button">확인</button>
<button class="btn btn-outline-warning btn-block" id="reset-button">취소</button>
</div>
<div id="cropped-new-profile-image" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- footer -->
<div th:replace="fragments.html::footer"></div>
</div>
// cropper 관련 링크 추가
<link href="/node_modules/cropper/dist/cropper.min.css" rel="stylesheet">
<script src="/node_modules/cropper/dist/cropper.min.js"></script>
<script src="/node_modules/jquery-cropper/dist/jquery-cropper.min.js"></script>
</body>
</html>
profile.html에 js로직 추가
<script type="application/javascript">
$(function() {
cropper = '';
let $confirmBtn = $('#confirm-button');
let $resetBtn = $("#reset-button");
let $cutBtn = $('#cut-button');
let $newProfileImage = $('#new-profile-image');
let $currentProfileImage = $("#current-profile-image");
let $resultImage = $('#cropped-new-profile-image');
let $profileImage = $('#profileImage');
// 초기 버튼과 새로운 프로필 이미지 숨기기
$newProfileImage.hide();
$cutBtn.hide();
$resetBtn.hide();
$confirmBtn.hide();
$("#profile-image-file").change(function (e) { // 프로필 사진이 바뀔때
if (e.target.files.length === 1) {
const reader = new FileReader();
reader.onload = e => {
if(e.target.result) { // 파일을 가져왔을 경우
// 이미지태그 만들기
let img = document.createElement("img");
img.id = 'new-profile';
img.src = e.target.result;
img.width = 250;
$newProfileImage.html(img); // newProfileImage영역에 위에서 만든 이미지 태그 삽입
$newProfileImage.show();
$currentProfileImage.hide();
// Cropper 적용
let $newImage = $(img);
$newImage.cropper({aspectRatio: 1});
cropper = $newImage.data('cropper');
$cutBtn.show();
$confirmBtn.hide();
$resetBtn.show();
}
};
reader.readAsDataURL(e.target.files[0]);
}
});
$resetBtn.click(function () {
$currentProfileImage.show();
$newProfileImage.hide();
$resultImage.hide();
$resetBtn.hide();
$cutBtn.hide();
$confirmBtn.hide();
$profileImage.val('');
});
$cutBtn.click(function () {
let dataUrl = cropper.getCroppedCanvas().toDataURL();
let newImage = document.createElement("img");
newImage.id = "cropped-new-profile-image";
newImage.src = dataUrl;
newImage.width = 125;
$resultImage.html(newImage);
$resultImage.show();
$confirmBtn.show();
$confirmBtn.click(function() {
$newProfileImage.html(newImage);
$cutBtn.hide();
$confirmBtn.hide();
$profileImage.val(dataUrl);
});
});
});
</script>
AccountService의 updateProfile메소드에서 이미지 업데이트 처리
package me.weekbelt.studyolle.account;
@Transactional
@RequiredArgsConstructor
@Service
public class AccountService implements UserDetailsService {
// 기존 코드 ......
public void updateProfile(Account account, Profile profile) {
// 기존 코드 ....
account.setProfileImage(profile.getProfileImage()); // 추가
accountRepository.save(account);
}
}
네비게이션 바에서 보이는 이미지도 바꿔주기 위해 fragment.html에서 nav태그 수정
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
// 기존 코드 .......
<ul class="navbar-nav justify-content-end">
<li class="nav-item" sec:authorize="!isAuthenticated()">
<a class="nav-link" th:href="@{/login}">로그인</a>
</li>
<li class="nav-item" sec:authorize="!isAuthenticated()">
<a class="nav-link" th:href="@{/sign-up}">가입</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link" th:href="@{/notifications}">
<i class="fa fa-bell-o" aria-hidden="true"></i>
</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link btn btn-outline-primary" th:href="@{/notifications}">
<i class="fa fa-plus" aria-hidden="true"></i>스터디 개설
</a>
</li>
<li class="nav-item dropdown" sec:authorize="isAuthenticated()">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
// 추가
<svg th:if="${#strings.isEmpty(account?.profileImage)}"
th:data-jdenticon-value="${#authentication.name}"
width="24" height="24" class="rounded border bg-light"></svg>
// 추가
<img th:if="${!#strings.isEmpty(account?.profileImage)}" th:src="${account.profileImage}"
width="24" height="24" class="rounded border">
</a>
<div class="dropdown-menu dropdown-menu-sm-right" aria-labelledby="userDropdown">
<h6 class="dropdown-header">
<span sec:authentication="name">Username</span>
</h6>
<a class="dropdown-item" th:href="@{'/profile/' + ${#authentication.name}}">프로필</a>
<a class="dropdown-item">스터디</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" th:href="@{'/settings/profile'}">설정</a>
<form class="form-inline my-2 my-lg-0" th:action="@{/logout}" method="post">
<button class="dropdown-item" type="submit">로그아웃</button>
</form>
</div>
</li>
</ul>
</div>
</nav>
// 기존 코드 .......
DataURL이란?
- data: 라는 접두어를 가진 URL로 파일을 문서에 내장 시킬때 사용할 수 있다.
- 이미지를 DataURL로 저장할 수 있다.
참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#
'스프링과 JPA 기반 웹 어플리케이션 개발 > 1부 (개발환경, 회원가입, 로그인, 계정설정)' 카테고리의 다른 글
27. 패스워드 수정 테스트 (0) | 2020.04.22 |
---|---|
26. 패스워드 수정 (0) | 2020.04.21 |
24. 프로필 수정 테스트 (0) | 2020.04.21 |
23. 프로필 수정 처리 (0) | 2020.04.21 |
22. 프로필 수정 (0) | 2020.04.21 |