[Spring][쇼핑몰 프로젝트][35] 상품 상세 페이지 - 2
프로젝트 Github : https://github.com/sjinjin7/Blog_Project
프로젝트 포스팅 색인(index) : https://kimvampa.tistory.com/188
목표
상품 상세 페이지
저번 포스팅에 이서 이번 포스팅에선 '상품 상세 페이지' 뷰 쪽 처리를 목표로 합니다.
순서
1. search.jsp
2. goodsDetail.jsp
3. 이미지, 출판일 출력
1. search.jsp
검색 페이지 에서 상품 상세 페이지로 이동할 수 있도록 <a> 태그를 추가해주겠습니다. 사용자가 책 이름을 클릭하였을 때 이동할 수 있도록 해주겠습니다.
search.jsp에서 class속성 값이 'title'인 <div>태그에 책 제목이 작성되어 잇습니다. 태그 내부에 작성한 코드를 <a> 태그로 감싸줍니다. 그리고 상품 상세 페이지를 요청하는 URL을 <a> 태그의 href 속성 값으로 부여해주었습니다.
<a href="/goodsDetail/${list.bookId}">
</a>
2. goodsDetail.jsp
home.jsp와 main.jsp 파일이 위치한 'views' 폴더에 goodsDetail.jsp 파일을 생성해줍니다.
더불어 goodsDetail.jsp에 사용될 css 코드를 가지는 goodsDetail.css 파일을 css 폴더에 추가 해주었습니다.
'상품 상세페이지'는 home.jsp와 main.jsp의 큰 틀은 그대로 사용하고 contents <div> 태그만 다르게 해 줄 것이기 때문에 search.jsp의 코드를 모두 복사 후 "goodsDetail.jsp" 코드를 붙여 넣어주었습니다.
goodsDetail.jsp의 <head> 태그 부분에 css 연결 코드를 "goodsDetail.css"와 연결이 되도록 수정을 해주었습니다.
"content_area" <div> 태그 안의 모든 코드를 지워 주었습니다.
Javascript 코드가 작성된 <script> 태그도 모두 지워주었습니다.
goodsDetail.jsp 전체 코드입니다.
※ 로고 이미지 소스코드 부분에 상대경로('resource/img...')을 절대경로('/resource/img...')로 수정해주었습니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome BookMall</title>
<link rel="stylesheet" href="/resources/css/goodsDetail.css">
<script
src="https://code.jquery.com/jquery-3.4.1.js"
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
crossorigin="anonymous"></script>
</head>
<body>
<div class="wrapper">
<div class="wrap">
<div class="top_gnb_area">
<ul class="list">
<c:if test = "${member == null}"> <!-- 로그인 x -->
<li >
<a href="/member/login">로그인</a>
</li>
<li>
<a href="/member/join">회원가입</a>
</li>
</c:if>
<c:if test="${member != null }"> <!-- 로그인 o -->
<c:if test="${member.adminCk == 1 }"> <!-- 관리자 계정 -->
<li><a href="/admin/main">관리자 페이지</a></li>
</c:if>
<li>
<a id="gnb_logout_button">로그아웃</a>
</li>
<li>
마이룸
</li>
<li>
장바구니
</li>
</c:if>
<li>
고객센터
</li>
</ul>
</div>
<div class="top_area">
<!-- 로고영역 -->
<div class="logo_area">
<a href="/main"><img src="/resources/img/mLogo.png"></a>
</div>
<div class="search_area">
<div class="search_wrap">
<form id="searchForm" action="/search" method="get">
<div class="search_input">
<select name="type">
<option value="T">책 제목</option>
<option value="A">작가</option>
</select>
<input type="text" name="keyword" value="<c:out value="${pageMaker.cri.keyword}"/>">
<button class='btn search_btn'>검 색</button>
</div>
</form>
</div>
</div>
<div class="login_area">
<!-- 로그인 하지 않은 상태 -->
<c:if test = "${member == null }">
<div class="login_button"><a href="/member/login">로그인</a></div>
<span><a href="/member/join">회원가입</a></span>
</c:if>
<!-- 로그인한 상태 -->
<c:if test="${ member != null }">
<div class="login_success_area">
<span>회원 : ${member.memberName}</span>
<span>충전금액 : <fmt:formatNumber value="${member.money }" pattern="\#,###.##"/></span>
<span>포인트 : <fmt:formatNumber value="${member.point }" pattern="#,###" /></span>
<a href="/member/logout.do">로그아웃</a>
</div>
</c:if>
</div>
<div class="clearfix"></div>
</div>
<div class="content_area">
</div>
<!-- Footer 영역 -->
<div class="footer_nav">
<div class="footer_nav_container">
<ul>
<li>회사소개</li>
<span class="line">|</span>
<li>이용약관</li>
<span class="line">|</span>
<li>고객센터</li>
<span class="line">|</span>
<li>광고문의</li>
<span class="line">|</span>
<li>채용정보</li>
<span class="line">|</span>
</ul>
</div>
</div> <!-- class="footer_nav" -->
<div class="footer">
<div class="footer_container">
<div class="footer_left">
<img src="/resources/img/Logo.png">
</div>
<div class="footer_right">
(주) VamBook 대표이사 : OOO
<br>
사업자등록번호 : ooo-oo-ooooo
<br>
대표전화 : oooo-oooo(발신자 부담전화)
<br>
<br>
COPYRIGHT(C) <strong>kimvampa.tistory.com</strong> ALL RIGHTS RESERVED.
</div>
<div class="clearfix"></div>
</div>
</div> <!-- class="footer" -->
</div> <!-- class="wrap" -->
</div> <!-- class="wrapper" -->
</body>
</html>
goodsDetail.css 파일에도 search.css 코드를 모두 복사해준 후 붙여 넣어줍니다. 그리고 복사된 코드들 중 jsp에서 지운 콘텐츠 관련 css 코드들을 모두 지워 주었습니다.
goodsDetail.css
@charset "UTF-8";
*{
margin: 0;
padding:0;
}
a{
text-decoration: none;
}
/* 화면 전체 렙 */
.wrapper{
width: 100%;
}
/* content 랩 */
.wrap{
width : 1080px;
margin: auto;
}
/* 홈페이지 기능 네비 */
.top_gnb_area{
width: 100%;
height: 50px;
background-color: #f0f0f1;
position:relative;
}
.top_gnb_area .list{
position: absolute;
top: 0px;
right: 0;
}
.top_gnb_area .list li{
list-style: none;
float : left;
padding: 13px 15px 0 10px;
font-weight: 900;
cursor: pointer;
}
/* 로고, 검색, 로그인 */
.top_area{
width: 100%;
height: 150px;
/* background-color: #f7f0b9; */
}
/* 로고 영역 */
.logo_area{
width: 25%;
height: 100%;
float:left;
}
.logo_area img{
width: 100%;
height: 100%;
}
/* 검색 박스 영역 */
.search_area{
width: 50%;
height: 100%;
float:left;
}
.search_wrap{
width: 100%;
height: 100%;
}
#searchForm{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.search_input{
display: flex;
height: 30%;
width: 80%;
}
.search_input select{
width: 20%;
text-align: center;
font-size: 15px;
}
.search_input input{
margin-left: 10px;
width: 57%;
font-size: 18px;
padding-left: 2%;
}
.search_btn{
margin-left: 10px;
width: 17%;
border-radius: 14px;
font-size: 17px;
font-weight: 600;
}
/* 로그인 버튼 영역 */
.login_area{
width: 25%;
height: 100%;
display: inline-block;
text-align: center;
}
.login_button{
height: 50%;
background-color: #D4DFE6;
margin-top: 30px;
line-height: 77px;
font-size: 40px;
font-weight: 900;
border-radius: 10px;
cursor: pointer;
}
.login_area>span{
margin-top: 10px;
font-weight: 900;
display: inline-block;
}
.login_button{
height : 50%;
background-color: #D4DFE6;
margin-top:30px;
}
/* 제품 목록 네비 */
.navi_bar_area{
width: 100%;
height: 70px;
background-color: #7696fd;
}
/* 홈페이지 메인 제품 목록 */
.content_area{
width: 100%;
min-height: 1000px;
}
/* 로그인 성공 영역 */
.login_success_area{
height: 62%;
width: 80%;
border: 2px solid #7474ad;
border-radius: 15px;
margin: 5% auto;
padding-top: 5%;
}
.login_success_area>a{
font-size: 15px;
font-weight: 900;
display: inline-block;
margin-top: 5px;
background: #e1e5e8;
width: 82px;
height: 22px;
line-height: 22px;
border-radius: 25px;
color: #606267;
}
.login_success_area>span{
display : block;
text-align: left;
margin-left: 10%;
}
/* 검색결과 없음 */
.table_empty{
height: 50px;
text-align: center;
margin: 200px 0 215px 0px;
font-size: 25px;
}
/* 필터정보 */
.search_filter {
width: 85%;
margin: auto;
margin-top: 30px;
margin-bottom: 50px;
}
.filter_button_wrap {
width: 100%;
}
.filter_button_wrap button {
width: 50%;
}
.filter_button{
background-color: #04AA6D;
border: 1px solid green;
color: white;
padding: 10px 24px;
cursor: pointer;
float: left;
}
.filter_button_wrap:after {
content: "";
clear: both;
display: table;
}
.filter_button_wrap button:not(:last-child) {
border-right: none;
}
.filter_button:hover {
background-color: #3e8e41;
}
.filter_active{
background-color: #045d3c;
}
.filter_content{
padding:20px 50px 20px 50px;
border: 1px solid gray;
}
.filter_content a:not(:first-child){
margin-left: 10px;
}
.filter_a{
display: block;
}
.filter_b{
display: none;
}
/* footer navai 영역 */
.footer_nav{
width:100%;
height:50px;
}
.footer_nav_container{
width: 100%;
height: 100%;
background-color:#8EC0E4;
}
.footer_nav_container>ul{
font-weight : bold;
float:left;
list-style:none;
position:relative;
padding-top:10px;
line-height: 27px;
font-family: dotum;
margin-left: 10px;
}
.footer_nav_container>ul>li{
display:inline;
width: 45px;
height: 19px;
padding: 10px 9px 0 10px;
}
.footer_nav_container>ul>span{
margin: 0 4px;
}
/* footer 영역 */
.footer{
width:100%;
height:130px;
background-color:#D4DFE6;
padding-bottom : 50px;
}
.footer_container{
width: 100%;
height: 100%;
margin: auto;
}
.footer_left>img {
width: 150%;
height: 130px;
margin-left: -20px;
margin-top: -12px;
}
.footer_left{
float :left;
width: 170px;
margin-left: 20px;
margin-top : 30px;
}
.footer_right{
float :left;
width: 680px;
margin-left: 70px;
margin-top : 30px;
}
/* float 속성 해제 */
.clearfix{
clear: both;
}
"content_area" <div> 태그 내부 부분은 교보문고 상품 페이지를 참고하여 단순하게 만들었습니다.
goodsDetail.jsp
<div class="line">
</div>
<div class="content_top">
<div class="ct_left_area">
<div class="image_wrap" data-bookid="${goodsInfo.imageList[0].bookId}" data-path="${goodsInfo.imageList[0].uploadPath}" data-uuid="${goodsInfo.imageList[0].uuid}" data-filename="${goodsInfo.imageList[0].fileName}">
<img>
</div>
</div>
<div class="ct_right_area">
<div class="title">
<h1>
${goodsInfo.bookName}
</h1>
</div>
<div class="line">
</div>
<div class="author">
<span>
${goodsInfo.authorName} 지음
</span>
<span>|</span>
<span>
${goodsInfo.publisher}
</span>
<span>|</span>
<span class="publeyear">
${goodsInfo.publeYear}
</span>
</div>
<div class="line">
</div>
<div class="price">
<div class="sale_price">정가 : <fmt:formatNumber value="${goodsInfo.bookPrice}" pattern="#,### 원" /></div>
<div class="discount_price">
판매가 : <span class="discount_price_number"><fmt:formatNumber value="${goodsInfo.bookPrice - (goodsInfo.bookPrice*goodsInfo.bookDiscount)}" pattern="#,### 원" /></span>
[<fmt:formatNumber value="${goodsInfo.bookDiscount*100}" pattern="###" />%
<fmt:formatNumber value="${goodsInfo.bookPrice*goodsInfo.bookDiscount}" pattern="#,### 원" /> 할인]</div>
</div>
<div class="line">
</div>
<div class="button">
<div class="button_quantity">
주문수량
<input type="text" value="1">
<span>
<button>+</button>
<button>-</button>
</span>
</div>
<div class="button_set">
<a class="btn_cart">장바구니 담기</a>
<a class="btn_buy">바로구매</a>
</div>
</div>
</div>
</div>
<div class="line">
</div>
<div class="content_middle">
<div class="book_intro">
${goodsInfo.bookIntro}
</div>
<div class="book_content">
${goodsInfo.bookContents }
</div>
</div>
<div class="line">
</div>
<div class="content_bottom">
리뷰
</div>
goodsDetail.css
.content_top{
width: 100%;
height: 400px;
}
.content_top:after {
content: "";
clear: both;
display: table;
}
.ct_left_area{
float: left;
width: 30%;
height: 100%;
}
.image_wrap{
height: 80%;
width: 80%;
margin: auto;
top: 10%;
position: relative;
}
.image_wrap img{
max-width: 100%;
height: auto;
display: block;
}
.line{
width: 100%;
border-top:1px solid #c6c6cf;
}
.ct_right_area{
float: left;
width: 70%;
height: 100%;
}
.title{
height: 28%;
font-size: 17px;
line-height: 110px;
color: #3a60df;
padding-left: 3%;
}
.author{
font-size: 16px;
line-height: 50px;
padding-left: 3%;
}
.price{
line-height: 30px;
padding: 2% 0 2% 3%;
}
.discount_price_number{
line-height: 30px;
font-size: 22px;
color: #f84450;
font-weight: bold;
}
.button{
padding: 2% 0 2% 3%;
}
.button_quantity{
margin-bottom: 2%;
}
.button_quantity input{
height: 26px;
width: 40px;
text-align: center;
font-weight: bold;
}
.button_quantity button{
border: 1px solid #aaa;
color: #3a60df;
width: 20px;
height: 20px;
padding: 3px;
background-color: #fff;
font-weight: bold;
font-size: 15px;
line-height: 15px;
}
.btn_cart{
display: inline-block;
width: 140px;
text-align: center;
height: 50px;
line-height: 50px;
background-color: #5e6b9f;
border: 1px solid #5e6b9f;
color: #fff;
margin-right: 2px;
}
.btn_buy{
display: inline-block;
width: 140px;
text-align: center;
height: 50px;
line-height: 50px;
background-color: #7b8ed1;
border: 1px solid #7b8ed1;
color: #fff;
}
.content_middle{
width: 100%;
min-height: 600px;
}
.book_intro{
width: 80%;
margin: auto;
margin-top: 40px;
}
.book_content{
width: 80%;
margin: auto;
margin-top: 40px;
margin-bottom: 40px;
}
.content_bottom{
width: 100%;
min-height: 400px;
background-color: #e7dbdb;
}
결과는 다음과 같습니다.
이미지가 들어갈 부분은 search.jsp 에서 사용한 방법과 동일합니다. 태그에 심어둔 이미지 데이터를 사용하여 Javascript코드를 통해 이미지를 출력시킬 것입니다.
'가격'관련 부분은 fmt 태그를 통해서 원하는 방식으로 출력되도록 하였습니다.
날짜 부분은 이전 포스팅에서 말하였지만 BookVO에 선언한 publeYear 변수의 타입이 Data인 경우 fmt태그를 화용하여 원하는 형식으로 변경이 가능하지만 String 타입이어서 fmt태그를 활용할 수 없습니다. 따라서 Javascript 코드를 통해서 원하는 형식으로 출력시킬 것입니다.
3. 이미지, '출판일' 출력
이미지와 '출판일'을 원하는 형식으로 출력되도록 해보겠습니다. 둘 다 사용자가 페이지에 들어갔을 때 출력이 되어야 하기 때문에 페이지가 로딩될대 실행이 되도록 <scirpt> 태그와 docuemt ready 메서드를 <body> 태그 하단에 추가해주었습니다.
<script>
$(document).ready(function(){
});
</script>
이미지가 출력되도록 아래의 코드를 추가해주었습니다. 구현 방식은 search.jsp에서 사용했던 방식과 동일합니다.
/* 이미지 삽입 */
const bobj = $(".image_wrap");
if(bobj.data("bookid")){
const uploadPath = bobj.data("path");
const uuid = bobj.data("uuid");
const fileName = bobj.data("filename");
const fileCallPath = encodeURIComponent(uploadPath + "/s_" + uuid + "_" + fileName);
bobj.find("img").attr('src', '/display?fileName=' + fileCallPath);
} else {
bobj.find("img").attr('src', '/resources/img/goodsNoImage.png');
}
'출판일' 날짜를 '연월일' 형식으로 출력되도록 아래의 코드를 추가해주었습니다.(substr(), split() 메서드를 활용하였습니다.)
/* publeyear */
const year = "${goodsInfo.publeYear}";
let tempYear = year.substr(0,10);
let yearArray = tempYear.split("-")
let publeYear = yearArray[0] + "년 " + yearArray[1] + "월 " + yearArray[2] + "일";
$(".publeyear").html(publeYear);
결과물은 아래와 같습니다.
REFERENCE
DATE
- 2020.11.10