[Spring Boot] 22. 상세 페이지 ②, 주문 확인

shr·2022년 3월 8일
0

Spring

목록 보기
21/23
post-thumbnail

상세 페이지 ②


  1. 해야 할 일

    • 수량 +, - 버튼 동작
    • 수량 변경에 따라 총 금액 변경
    • 구매하기 버튼 동작 → 주문 확인 페이지로 이동

  1. +, - 버튼 (자바스크립트)
  • src/main/resources - templates - product - read.html

        // + 플러스 버튼 처리
        function plus() {
            let $count = $('#count_of_product').text();
            let $pno = $('#pno').text();
            const $price = $('#price').find('span').text();
            $count++;
            const params = "count=" + $count + "&pno=" + $pno;
            $.ajax("/product/check/stock?" + params).done(() => {
                $(this).prev().text($count);
                $(this).parent().next().next().find('.total_count').text($count);
                $(this).parent().next().next().find('.total_price').text($count * $price);
            }).fail(() => alert("더 이상 구입할 수 없습니다."));
        }
    
        // - 마이너스 버튼 처리
        function minus() {
            let $count = $('#count_of_product').text();
            const $price = $('#price').find('span').text();
            if ($count > 1) {
                $count--;
                $(this).next().text($count);
                $(this).parent().next().next().find('.total_count').text($count);
                $(this).parent().next().next().find('.total_price').text($count * $price);
            }
        }
    $(document).ready(function() {
            $('#minus').click(minus);
            $('#plus').click(plus);
    }

    📝 $(document) 안에 이벤트 동작 넣어 주는 것 까먹지 말기!


  1. 재고 확인 (컨트롤러와 서비스)
  • src/main/java - com.example.demo.controller.rest - ProductRestController
        @GetMapping("/product/check/stock")
        public ResponseEntity<Void> checkStock(Integer pno, Integer count) {
            Boolean result = service.checkStock(pno, count);
            if (result == true)
                return ResponseEntity.ok(null);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(null);
        }
  • src/main/java - com.example.demo.service - ProductService
        public Boolean checkStock(Integer pno, Integer count) {
            return productDao.findById(pno).getStock() >= count;
        }

  1. 구매하기 버튼 (자바스크립트)
  • src/main/resources - templates - product - read.html

        // 구매하기 버튼 처리
        function order() {
            const $form = $('<form>').attr('method', 'get').attr('action', '/order/view').appendTo($('body'));
            $('<input>').attr('type', 'hidden').attr('name', 'pno').val($('#pno').text()).appendTo($form);
            $('<input>').attr('type', 'hidden').attr('name', 'count').val($('#count_of_product').text()).appendTo($form);
            $form.submit();
        }
    $(document).ready(function() {
            // 결제 버튼
            $('#order').click(order);
    }

    구매하기 버튼을 누르면 주문 확인 페이지(/order/view)로 넘어간다.


주문 확인


  1. 해야 할 일

    주문 확인 화면은 위와 같다. 새 배송지 버튼을 클릭하면 아래와 같은 새 창이 뜨게끔 되어 있다.


  1. 주문, 주문 상세, 주소 엔티티
  • src/main/java - com.example.demo.entity - Order

    package com.example.demo.entity;
    
    import java.time.*;
    
    import lombok.*;
    
    @Data
    @AllArgsConstructor
    public class Order {
        private Integer orderNo;
        private String username;
        private DeliveryStatus deliveryStatus;
        private LocalDate orderday;
        private Integer totalPrice;
        private Integer addressNo;
    }
    
  • src/main/java - com.example.demo.entity - OrderItem

    package com.example.demo.entity;
    
    import lombok.*;
    
    @Data
    @AllArgsConstructor
    public class OrderItem {
        private Integer orderNo;
        private Integer pno;
        private String vendor;
        private String name;
        private String imagename;
        private Integer price;
        private Integer count;
        private Integer orderItemPrice;
    }
    
  • src/main/java - com.example.demo.entity - DeliveryStatus

    package com.example.demo.entity;
    
    public enum DeliveryStatus {
        PAY("결제 완료"), SHIPPING("배송 중"), CANCEL("취소"), RETURN("환불"), COMPLETE("배송 완료");
    
        @Getter
        public String korean;
    
        DeliveryStatus(String kor) {
            this.korean = kor;
        }
    
    }
  • src/mian/java - com.example.demo.entity - Address

    package com.example.demo.entity;
    
    import lombok.*;
    import lombok.experimental.*;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain=true)
    @Builder
    public class Address {
        private String username;
        private Integer addressNo;
        private String nickname;
        private String zipcode;
        private String address1;
        private String address2;
        private Boolean isDefault;
    }

  1. 주문 확인 페이지, 배송지 입력 페이지 (html)
  • src/main/resources - templates - order - view.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>
    </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>
                <div th:text="${order}"></div>
                <div>
                    <button id="add_new_address">새 배송지</button>
                    <div id="address_area">
                    <!-- 등록된 배송지들을 라디오 버튼으로 출력하는 영역 -->
                    </div>
                    <hr>
                    <div id="address_info">
                        <span>배송지 : </span>
                        <!-- radio로 선택한 배송지 정보를 출력하는 영역 -->
                        <span id="forwardingAddress"></span>
                    </div>
                    <hr>
                </div>
                <button id="order" class="btn btn-info">결제</button>
            </section>
        </div>
        <footer th:replace="/fragments/footer.html">
        </footer>
    </div>
    </body>
    </html>
  • src/main/resources - templates - order - new_address.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="/css/main.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="/script/main.js"></script>
    <body>
        <h1>배송주소 추가</h1>
        배송지명 : <input type="text" id="nickname" placeholder="배송지명" autofocus>
        <br>
        <input type="text" id="zipcode" placeholder="우편번호">
        <input type="button" onclick="execDaumPostcode()" value="우편번호 찾기"><br>
        <input type="text" id="roadAddress" placeholder="도로명주소">
        <input type="text" id="jibunAddress" placeholder="지번주소">
        <span id="guide" style="color:#999;display:none"></span>
        <input type="text" id="detailAddress" placeholder="상세주소">
        <br>
        <button id="add">배송지 추가</button>
        <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
        <script>
            //본 예제에서는 도로명 주소 표기 방식에 대한 법령에 따라, 내려오는 데이터를 조합하여 올바른 주소를 구성하는 방법을 설명합니다.
            function execDaumPostcode() {
                new daum.Postcode({
                    oncomplete: function (data) {
                        let roadAddr = data.roadAddress; // 도로명 주소 변수
                        let extraRoadAddr = ''; // 참고 항목 변수
    
                        // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                        // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                        if (data.bname !== '' && /[동|로|가]$/g.test(data.bname)) {
                            extraRoadAddr += data.bname;
                        }
                        // 건물명이 있고, 공동주택일 경우 추가한다.
                        if (data.buildingName !== '' && data.apartment === 'Y') {
                            extraRoadAddr += (extraRoadAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                        }
                        // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                        if (extraRoadAddr !== '') {
                            extraRoadAddr = ' (' + extraRoadAddr + ')';
                        }
    
                        // 우편번호와 주소 정보를 해당 필드에 넣는다.
                        document.getElementById('zipcode').value = data.zonecode;
                        document.getElementById("roadAddress").value = roadAddr;
                        document.getElementById("jibunAddress").value = data.jibunAddress;
                    }
                }).open();
            }
        </script>
    </body>
    
    </html>

  1. 주문 확인 페이지에서 필요한 정보들만 담은 DTO
  • src/main/java - com.example.demo.dto - OrderDto

    package com.example.demo.dto;
    
    import java.util.List;
    
    import com.example.demo.entity.OrderItem;
    
    import lombok.*;
    
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class OrderDto {
        @Data
        @AllArgsConstructor
        public static class View {
            private Integer totalprice;
            private List<OrderItem> orderItems;
        }
    }

  1. 주문 확인 페이지 컨트롤러
  • src/main/java - com.example.demo.controller.mvc - OrderController

    package com.example.demo.controller.mvc;
    
    @Secured("ROLE_USER")
    @AllArgsConstructor
    @Controller
    public class OrderController {
        @GetMapping("/order/new_address")
        public void newAddress() {
        }
    
        @PostMapping("/order/view")
        public ModelAndView orderView(Integer pno, Integer count, Principal principal, HttpSession session) {
            OrderDto.View dto = service.orderOne(pno, count, principal.getName());
            session.setAttribute("dto", dto);
            return new ModelAndView("order/view").addObject("order", dto);
        }

    📝 주문 확인 페이지까지 왔다가 다시 뒤로 가기를 하는 경우가 생길 수도 있다. 그렇기 때문에 여기서 담기는 정보는 일단 session에 담아 준다.


  1. address_areaaddress_info 블록에 주소를 출력할 코드 작성 (자바스크립트)
  • src/main/resources - templates - order - view.html
        function printAddresses() {
            $.ajax("/address/all").done(result => {
                if (result == "") {
                    $('#forwardingAddress').text("등록된 배송지가 없습니다. 배송지를 등록해 주세요.");
                    $('#order').prop("disabled", true);
                } else {
                    $("#address_area").empty();
                    $.each(result, function(idx, address) {
                        const $radio = $("<input type='radio' name='address' class='address'>").attr("data-addressNo", address.addressNo).appendTo($('#address_area'));
                        $("<span>").text(address.nickname).appendTo($("#address_area"));
                        if (address.isDefault == true) {
                            $radio.prop("checked", true);
                            $('#forwardingAddress').text("(" + address.zipcode + ")" + address.address1 + " " + address.address2);
                        }
                    })
                }
            })
        }
        $(document).ready(function() {
            printAddresses();
        }

  1. 라디오 버튼 선택에 따라 address_info 다르게 출력하는 코드 작성 (자바스크립트)
  • src/main/resources - templates - order - view.html
        function changeAddress() {
            const $addressNo = $(this).attr("data-addressNo");
            $.ajax("/address/read/" + $addressNo).done(address => {
                $('#forwardingAddress').text("(" + address.zipcode + ")" + address.address1 + " " + address.address2);
            })
        }
        $(document).ready(function() {
            $("#address_area").on("click",".address", changeAddress);
        }

  1. 새 배송지 버튼을 눌렀을 때 새 창을 띄울 코드 작성 (자바스크립트)
  • src/main/resources - templates - order - view.html
        $(document).ready(function() {
            $('#add_new_address').click(function() {
                const popup = window.open('/order/new_address', '_blank', 'toolbar=yes, menubar=yes, width=700, height=500');
                $(popup).on("beforeunload", printAddresses);
            })
        })

    📝 window.open()을 이용해서 새 창을 열 수 있다.


  1. view.html에 출력할 정보를 보낼 컨트롤러와 서비스, DAO
  • src/main/java - com.example.demo.controller.rest - AddressRestController

    package com.example.demo.controller.rest;
    
    @Secured("ROLE_USER")
    @AllArgsConstructor
    @RestController
    public class AddressRestController {
        private AddressService service;
    
        // 새 배송지 저장
        @PostMapping("/address/new")
        public ResponseEntity<Void> add(Address address, Principal principal) {
            Boolean result = service.save(address, principal.getName());
            if (result == true)
                return ResponseEntity.ok(null);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(null);
        }
    
        // 저장된 주소들이 있는지 조회
        @GetMapping("/address/all")
        public ResponseEntity<List<Address>> read(Principal principal) {
            List<Address> list = service.read(principal.getName());
            return ResponseEntity.ok(list);
        }
    
        // 선택된 라디오 버튼의 주소 조회
        @GetMapping("/address/read/{addressNo}")
        public ResponseEntity<Address> read(@PathVariable Integer addressNo, Principal principal) {
            Address address = service.readOne(addressNo, principal.getName());
            return ResponseEntity.ok(address);
        }
    
    }
  • src/main/java - com.example.demo.service - AddressService

    package com.example.demo.service;
    
    @AllArgsConstructor
    @Service
    public class AddressService {
        private AddressDao dao;
    
        public Boolean save(Address address, String loginId) {
            address.setUsername(loginId);
            if (dao.existsByUsername(loginId) == false)
                address.setIsDefault(true);
            return dao.save(address) == 1;
        }
    
        public List<Address> read(String loginId) {
            return dao.findByUsername(loginId);
        }
    
        public Address readOne(Integer addressNo, String username) {
            return dao.findByUsernameAndAddressNo(username, addressNo);
        }
    
    }
  • src/main/java - com.example.demo.dao - AddressDao

    package com.example.demo.dao;
    
    public interface AddressDao {
    
        @Select("select * from address where username=#{username}")
        public List<Address> findByUsername(String loginId);
    
        @SelectKey(statement = "select address_seq.nextval from dual", keyProperty = "addressNo", before = true, resultType = Integer.class)
        @Insert("insert into address values(#{username}, #{addressNo}, #{nickname}, #{zipcode}, #{address1}, #{address2}, #{isDefault})")
        public Integer save(Address address);
    
        @Select("select count(*) from address where username=#{username} and rownum=1")
        public boolean existsByUsername(String loginId);
    
        @Select("select * from address where username=#{username} and addressNo=#{addressNo}")
        public Address findByUsernameAndAddressNo(String username, Integer addressNo);
    
    }
profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글