[Spring Boot] 21. 상세 페이지 ①, 리뷰 읽기 · 작성 · 삭제 ②

shr·2022년 3월 4일
0

Spring

목록 보기
20/23
post-thumbnail

상세 페이지


  1. DTO, 엔티티 생성
  • src/main/java - com.example.demo.dto - ProductDto
        @Data
        @AllArgsConstructor
        @Builder
        public static class Read {
            private Integer pno;
            private String vendor;
            private String name;
            private String info;
            private String imagename;
            private Integer price;
            private Integer salesVolume;
            private Double star;
            private Integer countOfReview;
            private String categoryCode;
            private List<Review> reviews;
        }
    • src/main/java - com.example.demo.entity - Product
        public ProductDto.Read toRead(String path) {
            Double star = countOfStar == 0 ? -1.0 : sumOfStar/countOfStar;
            return ProductDto.Read.builder().pno(pno).name(name).info(info).imagename(path + imagename).price(price)
                    .salesVolume(salesVolume).countOfReview(countOfReview).categoryCode(categoryCode).star(star).build();
        }

  1. 서비스 설정
  • src/main/java - com.example.demo.service - ProductService
        public ProductDto.Read read(Integer pno) {
            ProductDto.Read dto = productDao.findById(pno).toRead(imagePath);
            dto.setReviews(reviewDao.findByPno(pno));
            return dto;
        }

  1. 컨트롤러 설정
  • src/main/java - com.example.demo.controller.mvc - ProductController
        @GetMapping("/product/read")
        public ModelAndView read(Integer pno) {
            return new ModelAndView("product/read").addObject("product", service.read(pno));
        }

리뷰 읽기 · 작성 · 삭제 ②


📝 리뷰 작성과 삭제는 로그인을 한 상태에서만 가능하기 때문에, 비로그인 상태에서는 관련 항목들이 아예 출력되지 않는다.

지난 포스팅에 이어 DTO를 보충하고, REST 컨트롤러 설정, HTML과 자바스크립트를 완성한다.

  1. DTO 보충
  • src/main/java - com.example.demo.dto - ReviewDto

        @Data
        @AllArgsConstructor
        public static class Write {
            private Integer pno;
            private String content;
            private Integer star;
    
            public Review toEntity() {
                return Review.builder().pno(pno).content(content).star(star).build();
            }
        }

  1. REST 컨트롤러 설정
  • src/main/java - com.example.demo.controller.rest - ReviewRestController

    package com.example.demo.controller.rest;
    
    import java.security.Principal;
    import java.util.List;
    
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.access.annotation.Secured;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.*;
    
    import com.example.demo.dto.*;
    import com.example.demo.service.*;
    
    import lombok.*;
    
    @RestController
    @AllArgsConstructor
    @Secured("ROLE_USER")
    public class ReviewRestController {
    
        private ReviewService service;
    
        @PostMapping("/review/write")
        public ResponseEntity<List<ReviewDto.RestRead>> write(ReviewDto.Write dto, BindingResult bindingresult, Principal principal) {
            return ResponseEntity.ok(service.write(dto, principal.getName()));
        }
    
        @DeleteMapping("/review/delete")
        public ResponseEntity<List<ReviewDto.RestRead>> delete(Integer rno, Integer pno, Principal principal) {
            return ResponseEntity.ok(service.deleteByRno(rno, pno, principal.getName()));
        }
    
    }

  1. HTML과 자바스크립트 설정
  • src/main/resources - templates - product - read.html

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
        <title>Insert title here</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
        <link rel="stylesheet" href="/css/main.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
        <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
        <script src="/ckeditor/ckeditor.js"></script>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
        <link rel="stylesheet" href="/css/read.css">
    <th:block sec:authorize="isAnonymous()">
        <script>
            const isLogin = false;
        </script>
    </th:block>
    <th:block sec:authorize="isAuthenticated()">
        <script th:inline="javascript">
            const isLogin = true;
            const loginId = /*[[${session.SPRING_SECURITY_CONTEXT.authentication.principal.username}]]*/
        </script>
    </th:block>
    <script th:inline="javascript">
    
        // 제품 가격과 주문 수량, 리뷰는 변수에 저장한다.
    
        const price = /*[[${product.price}]]*/
        const reviews = /*[[${product.reviews}]]*/
        let count = 1;
        let star = 0;
    
        function printReviews(reviews) {
            $('#reviews').empty();
            const $table = $('<table style="width:100%" th:each="{review: ${reviews}">').appendTo($('#reviews'));
            $.each(reviews, function(idx, review) {
                const $tr = $('<tr>').appendTo($table);
                $('<td class="one">').text(review.rno).appendTo($tr);
                const $td2 = $('<td class="two">').appendTo($tr);
                for (let i = 1; i <= review.star; i++) {
                    if (i <= review.star)
                        $('<i class="fa fa-star">').appendTo($td2);
                    else
                        $('<i class="fa fa-star-o">').appendTo($td2);
                }
                $('<td class="three">').text(review.content).appendTo($tr);
                $('<td class="four">').text(review.writer).appendTo($tr);
                $('<td class="five">').text(review.writeday).appendTo($tr);
                const $td6 = $('<td class="six">').appendTo($tr);
                if (isLogin == true && loginId == review.writer) {
                    $('<button class="btn btn-info">삭제</button>').attr("data-rno", review.rno)
                    .attr("data-pno", review.pno).appendTo($td6);
                }
            })
        }
    
        $(document).ready(function() {
            if (isLogin == false)
                $('#review_div').remove();
    
            $('#total_count').text(count);
            $('#total_price').text(count * price);
            printReviews(reviews);
    
            // 리뷰 삭제
            $('.delete_review').click(function() {
                const params = {
                    rno : $(this).attr('data-rno'),
                    pno : $(this).attr('data-pno')
                }
                $.ajax({
                    url : "/review/delete",
                    method : "delete",
                    data : params
                }).done(result => printReviews(result)).fail(response => console.log(response));
            })
    
            // 리뷰 작성
            $('#review_write').click(function() {
                if (star < 0) {
                    alert("리뷰를 남기시려면 별점을 선택해 주세요.");
                    return false;
                }
                const params = {
                    pno : $('#pno').text(),
                    content : $('#review_textarea').val(),
                    star : star
                }
                $.ajax({
                    url : "/review/write",
                    method : "post",
                    data : params
                }).done(result => printReviews(result)).fail(response => console.log(response)).always(() => {
                    $('#review_textarea').val("");
                    $(".star").each(function(idx, s) {
                        $(s).removeClass('fa-star');
                        $(s).addClass('fa-star-o');
                    })
                })
            })
    
            // 별점 주기
            $('.star').click(function() {
                star = $(this).attr("data-rating");
                $(".star").each(function(idx, s) {
                    if (star >= $(s).attr("data-rating")) {
                        $(s).removeClass("fa-star-o");
                        $(s).addClass("fa-star")
                    } else {
                        $(s).removeClass("fa-star");
                        $(s).addClass("fa-star-o");
                    }
                })
            })
        })
    </script>
    </head>
    <body>
    <div id="page">
        <header th:replace="/fragments/header.html">
        </header>
        <nav th:replace="/fragments/nav.html">
        </nav>
        <div id="main">
            <aside th:replace="/fragments/aside.html">
            </aside>
            <section>
                <!-- <input type="hidden" id="_csrf" name="_csrf"> -->
                <div id="upper">
                    <div id="left">
                        <img id="image" style="width:100%" th:src="${product.imagename}">
                    </div>
                    <div id="right">
                        <div id="pno" th:text="${product.pno}" style="display:none"></div>
                        <div id="vendor" th:text="${product.vendor}"></div>
                        <div id="name" th:text="${product.name}"></div>
                        <div id="price"><span th:text="${product.price}"></span></div>
                        <hr>
                        <div id="product_div">
                            <div class='count' id='minus'>-</div>
                            <div class='count number' id="count_of_product">1</div>
                            <div class='count' id='plus'>+</div>
                        </div>
                        <hr>
                        <div id="price_div" style="overflow:hidden;">
                            <span style="font-weight:bold; font-size:1.25em;">총 금액</span>
                            <div style="float:right;">
                                <span style="color:#999">총 수량<span id="total_count"></span></span>
                                <span><span id="total_price"></span></span>
                            </div>
                        </div>
                        <div>
                            <button id="order">구매하기</button>
                            <button id="cart">장바구니</button>
                        </div>
                    </div>
                </div>
                <div id="lower">
                    <div id="content" th:utext="${product.info}" style="overflow:auto; height:600px; border: 1px solid #ccc; border-radius:5px;"></div>
                </div>
                <hr>
                <div id="review_div">
                    <div>
                        <label>별점 주기</label>
                        <i class="fa fa-star-o star" data-rating="1" title='1점'></i>
                        <i class="fa fa-star-o star" data-rating="2" title='2점'></i>
                        <i class="fa fa-star-o star" data-rating="3" title='3점'></i>
                        <i class="fa fa-star-o star" data-rating="4" title='4점'></i>
                        <i class="fa fa-star-o star" data-rating="5" title='5점'></i>
                    </div>
                    <div class="form-group">
                        <label for="review_teaxarea">리뷰를 입력하세요.</label>
                        <textarea class="form-control" rows="5" id="review_textarea" placeholder="욕설이나 모욕적인 댓글은 삭제될 수 있습니다."></textarea>
                    </div>
                    <button type="button" class="btn btn-info" id="review_write">
                        <span class="glyphicon glyphicon-ok"></span>작성
                    </button>
                </div>
                <hr>
                <div id="reviews">
                </div>
            </section>
        </div>	
        <footer th:replace="/fragments/footer.html">
        </footer>
    </div>
    </body>
    </html>

    따로 빼 놓은 CSS는 아래와 같다.

    @charset "UTF-8";
    #upper {
        height: 400px;
    }
    
    #left {
        float: left;
        width: 48%;
    }
    #right {
        float: right;
        width: 48%;
    }
    #section {
        overflow: hidden;			/* float를 clear하는 방법   */
    }
    
    .count {
        width: 35px;
        height: 35px;
        line-height: 35px;
        display: inline-block;
        text-align: center;
        font-size: 1.25em;
    }
    .plus, .minus, #plus, #minus {
        background-color: #ddd;		/* color를 16진수로 지정. 16진수로 지정할 때 #555555 -> #555로 줄여쓸 수 있다 */
        cursor: pointer;
    }
    
    #vendor, #name {
        font-size: 1.5em;
        font-weight: bold;
        color: #222;
    }
    
    #price {
        margin-top: 15px;
        font-size: 24px;
        color: rgb(107,144,220);	/* color를 10진수로 지정. rgb, rgba 두 가지.*/
        font-weight: 700;
        text-align: right;
    }
    
    #total_price {
        font-size: 1.75em;			/* em은 퍼센트(175%). %는 문서 원래 크기 기준. em은 부모 기준.*/ 
        font-weight: bold;
        color:rgb(233,96,97);
    }
    
    #order, #cart {
        width: 125px;
        height: 50px;
        line-height: 50px;
        text-align: center;
        color: white;
        font-weight: bold;
    
        /* 버튼을 커스터마이즈할 때 외곽선 */
        border: 0;
        outline: 0;
    }
    
    #order {
        background-color: #3fc910;
    }
    
    #cart {
        background-color: #565656;
    }
profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글