-
쇼핑몰 프로젝트 --페이지네이션, 유효성검사, 리뷰 정렬(화면구현)쇼핑몰 프로젝트 2024. 6. 28. 02:05
itemList, itemCategoryList, categoryGet, itemReviewGet페이지네이션 추가
itemList페이지네이션 추가
itemRepository
// 아이템 전체 총 개수 조회 (페이징 하기 위해) public int countAll(String keyword) { String queryString = "select count(i) from Item i"; if (keyword != null && !keyword.isEmpty()) { queryString += " where i.itemName like :keyword"; } TypedQuery<Long> query = em.createQuery(queryString, Long.class); if (keyword != null && !keyword.isEmpty()) { query.setParameter("keyword", "%" + keyword + "%"); } return query.getSingleResult().intValue(); }
itemService
//아이템 전체 페이징 개수 조회 public int countAll(String keyword) { return itemRepository.countAll(keyword); }
itemController
//아이템 전체 조회 @GetMapping("/ypjs/item/get") public String getAllItem( @RequestParam(value = "page",defaultValue = "0") int page, @RequestParam(value = "size",defaultValue = "3") int size, @RequestParam(value = "sortBy", defaultValue = "itemId") String sortBy, @RequestParam(value = "keyword", required = false) String keyword, Model model) { Pageable pageable = PageRequest.of(page, size); List<ItemListDto> items = itemService.findAllItem(keyword, pageable, sortBy); //총 페이지 수 계산 int totalPages = Page.totalPages(itemService.countAll(keyword), size); model.addAttribute("items", items); model.addAttribute("sortBy", sortBy); // 정렬 옵션을 다시 모델에 추가 model.addAttribute("keyword", keyword); //검색조건 유지 model.addAttribute("page",page); //페이징 model.addAttribute("size",size); //페이징 model.addAttribute("totalPages", totalPages); //총 페이지 수 return "item/itemList"; }
오늘 서비스, 레파지토리는 페이징 부분만 추가, 나머지 서비스, 레파지토리는 전에 적어논 글에 있음
item/itemList 페이지네이션 부분만 추가 됨
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head th:replace="frame/header :: head"> <title>Zay Shop - Product Listing Page</title> </head> <style> .fixed-size-img { width: 300px; /* 원하는 너비로 설정 */ height: 300px; /* 원하는 높이로 설정 */ object-fit: cover; /* 이미지를 크기에 맞게 조정 */ } </style> <body> <!--header--> <header th:replace="frame/header :: header"></header> <script src="/js/item/itemRatingsGet.js"></script> <!-- Start Content --> <div class="container py-5"> <div class="row"> <div class="container-fluid mt-3"> <div class="row"> <div class="col-md-12"> <ul class="list-inline shop-top-menu pb-3 pt-1"> <li class="list-inline-item"> <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/item/post}">상품 등록</a> </li> <li class="list-inline-item"> <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/category/post}">카테고리 등록</a> </li> <li class="list-inline-item"> <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/category/get}">카테고리 보기</a> </li> </ul> </div> </div> </div> <div class="col-lg-3"> <h1 class="h2 pb-4">Categories</h1> <ul class="list-unstyled templatemo-accordion"> <li class="pb-3"> <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#"> Women <i class="fa fa-fw fa-chevron-circle-down mt-1"></i> </a> <ul class="collapse show list-unstyled pl-3"> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=3)}">Outer</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=4)}">Top</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=5)}">Bottom</a></li> </ul> </li> <li class="pb-3"> <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#"> Man <i class="pull-right fa fa-fw fa-chevron-circle-down mt-1"></i> </a> <ul id="collapseTwo" class="collapse list-unstyled pl-3"> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=6)}">Outer</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=7)}">Top</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=8)}">Bottom</a></li> </ul> </li> </ul> </div> <div class="col-lg-9"> <div class="row"> <div class="col-md-6"> <ul class="list-inline shop-top-menu pb-3 pt-1"> <li class="list-inline-item"> <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/item/get}">All Items</a> </li> </ul> </div> <div class="col-lg-6"> <!-- 검색 폼 및 정렬 폼 통합 --> <form th:action="@{/ypjs/item/get}" method="get" class="input-group"> <input type="text" id="keyword" name="keyword" class="form-control" placeholder="Search by item name" th:value="${keyword}"> <div class="input-group-append"> <button type="submit" class="btn btn-link text-dark"> <i class="fa fa-search"></i> </button> </div> <div class="col-auto"> <select class="form-control" name="sortBy" id="sortBy" onchange="this.form.submit()"> <option value="itemId" th:selected="${sortBy == 'itemId'}">최신순</option> <option value="itemRatings" th:selected="${sortBy == 'itemRatings'}">별점 높은 순</option> <option value="likeCount" th:selected="${sortBy == 'likeCount'}">찜많은순</option> </select> </div> </form> </div> <!-- 반별 추가--> <!-- <div class="container-fluid mt-3">--> <!-- <div class="row">--> <!-- <!– items 리스트의 각 항목을 반복 –>--> <!-- <div th:each="item : ${items}" class="col-md-4">--> <!-- <div class="card mb-4 product-wap rounded-0">--> <!-- <!– 제품 이미지 –>--> <!-- <div class="card rounded-0">--> <!-- <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}">--> <!-- <img class="card-img rounded-0 img-fluid fixed-size-img" th:src="${item.itemFilePath}" alt="product-item">--> <!-- <!– 필요한 경우 오버레이나 추가 요소를 여기에 추가할 수 있습니다 –>--> <!-- <div class="card-img-overlay rounded-0 product-overlay d-flex align-items-center justify-content-center">--> <!-- <!– 추가적인 콘텐츠가 필요하다면 여기에 추가할 수 있습니다 –>--> <!-- <ul class="list-unstyled">--> <!-- <!– 리스트의 다른 요소들 –>--> <!-- </ul>--> <!-- </div>--> <!-- </a>--> <!-- </div>--> <!-- <!– 제품 정보 –>--> <!-- <div class="card-body">--> <!-- <!– 제품 링크 –>--> <!-- <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a>--> <!-- <!– 별점 및 평점 표시 –>--> <!-- <div class="d-flex justify-content-center mb-1 align-items-center">--> <!-- <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${item.itemRatings}">--> <!-- <!– 별 아이콘은 JavaScript로 생성됩니다 –>--> <!-- </ul> --> <!-- <span id="itemRatings" class="font-weight-bold ml-2" th:text="${item.itemRatings}"></span>--> <!-- </div>--> <!-- <!– 가격 표시 –>--> <!-- <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span>--> <!-- <span class="font-weight-bold">원</span>--> <!-- </div>--> <!-- </div>--> <!-- </div>--> <!-- </div>--> <!-- </div>--> <!-- <script>--> <!-- document.addEventListener("DOMContentLoaded", function() {--> <!-- var starRatingContainers = document.querySelectorAll("#starRating");--> <!-- starRatingContainers.forEach(function(starRatingContainer) {--> <!-- var rating = parseFloat(starRatingContainer.getAttribute("data-rating"));--> <!-- starRatingContainer.innerHTML = generateStarRating(rating);--> <!-- });--> <!-- });--> <!-- function generateStarRating(rating) {--> <!-- var starsHTML = '';--> <!-- var fullStars = Math.floor(rating); // 정수 부분의 별 개수--> <!-- var halfStar = (rating % 1 >= 0.1 && rating % 1 <= 0.9) ? 1 : 0; // 반 별 조건--> <!-- // 정수 부분의 별 추가--> <!-- for (var i = 0; i < fullStars; i++) {--> <!-- starsHTML += '<i class="text-warning fa fa-star"></i>';--> <!-- }--> <!-- // 반 별 추가--> <!-- if (halfStar) {--> <!-- starsHTML += '<i class="text-warning fa fa-star-half-alt"></i>';--> <!-- }--> <!-- // 남은 빈 별 추가--> <!-- var emptyStars = 5 - fullStars - halfStar;--> <!-- for (var i = 0; i < emptyStars; i++) {--> <!-- starsHTML += '<i class="text-muted fa fa-star"></i>';--> <!-- }--> <!-- return starsHTML;--> <!-- }--> <!-- </script>--> <!-- 꽉찬 별만--> <div class="container-fluid mt-3"> <div class="row"> <!-- items 리스트의 각 항목을 반복 --> <div th:each="item : ${items}" class="col-md-4"> <div class="card mb-4 product-wap rounded-0"> <!-- 제품 이미지 --> <div class="card rounded-0"> <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}"> <img class="card-img rounded-0 img-fluid fixed-size-img" th:src="${item.itemFilePath}" alt="product-item"> <!-- 필요한 경우 오버레이나 추가 요소를 여기에 추가할 수 있습니다 --> <div class="card-img-overlay rounded-0 product-overlay d-flex align-items-center justify-content-center"> <!-- 추가적인 콘텐츠가 필요하다면 여기에 추가할 수 있습니다 --> <ul class="list-unstyled"> <!-- 리스트의 다른 요소들 --> </ul> </div> </a> </div> <!-- 제품 정보 --> <div class="card-body"> <!-- 제품 링크 --> <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a> <!-- 별점 및 평점 표시 --> <div class="d-flex justify-content-center mb-1 align-items-center"> <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${item.itemRatings}"> <!-- 별 아이콘은 JavaScript로 생성됩니다 --> </ul> <span id="itemRatings" class="font-weight-bold ml-2" th:text="${item.itemRatings}"></span> </div> <!-- 가격 표시 --> <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span> <span class="font-weight-bold">원</span> </div> </div> </div> </div> </div> </div> </div> </div> </div> <!-- 페이지네이션 시작--> <nav th:unless="${#lists.isEmpty(items)}" class="mt-4"> <ul class="pagination justify-content-center"> <li class="page-item" th:classappend="${page == 0} ? 'disabled'"> <a class="page-link" th:href="@{'/ypjs/item/get?page=' + ${page - 1} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">이전</a> </li> <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'"> <a class="page-link" th:href="@{'/ypjs/item/get?page=' + ${i} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}" th:text="${i + 1}">1</a> </li> <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'"> <a class="page-link" th:href="@{'/ypjs/item/get?page=' + ${page + 1} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">다음</a> </li> </ul> </nav> <!--페이지네이션 끝--> <!-- End Content --> <!-- Start Brands --> <section class="bg-light py-5"> <div class="container my-4"> <div class="row text-center py-3"> <div class="col-lg-6 m-auto"> <h1 class="h1">Our Brands</h1> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod Lorem ipsum dolor sit amet. </p> </div> <div class="col-lg-9 m-auto tempaltemo-carousel"> <div class="row d-flex flex-row"> <!--Controls--> <div class="col-1 align-self-center"> <a class="h1" href="#multi-item-example" role="button" data-bs-slide="prev"> <i class="text-light fas fa-chevron-left"></i> </a> </div> <!--End Controls--> <!--Carousel Wrapper--> <div class="col"> <div class="carousel slide carousel-multi-item pt-2 pt-md-0" id="multi-item-example" data-bs-ride="carousel"> <!--Slides--> <div class="carousel-inner product-links-wap" role="listbox"> <!--First slide--> <div class="carousel-item active"> <div class="row"> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a> </div> </div> </div> <!--End First slide--> <!--Second slide--> <div class="carousel-item"> <div class="row"> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a> </div> </div> </div> <!--End Second slide--> <!--Third slide--> <div class="carousel-item"> <div class="row"> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a> </div> </div> </div> <!--End Third slide--> </div> <!--End Slides--> </div> </div> <!--End Carousel Wrapper--> <!--Controls--> <div class="col-1 align-self-center"> <a class="h1" href="#multi-item-example" role="button" data-bs-slide="next"> <i class="text-light fas fa-chevron-right"></i> </a> </div> <!--End Controls--> </div> </div> </div> </div> </section> <!--End Brands--> <footer th:replace="frame/footer :: footer"></footer> </body> </html>
itemCategoryList 페이지네이션
itemRepository
//카테고리 별 아이템 총 개수 조회(페이징) public int countAllCategoryItem(String keyword, Long categoryId) { // 기본 쿼리 설정 String queryString = "select count(distinct i) from Item i join i.category c where i.category.categoryId = :categoryId"; // 검색 조건 추가 if (keyword != null && !keyword.isEmpty()) { queryString += " and i.itemName like :keyword"; } // 쿼리 생성 TypedQuery<Long> query = em.createQuery(queryString, Long.class); query.setParameter("categoryId", categoryId); // 검색 키워드 파라미터 설정 if (keyword != null && !keyword.isEmpty()) { query.setParameter("keyword", "%" + keyword + "%"); } // 결과 반환 return query.getSingleResult().intValue(); }
itemService
//카테고리 별 아이템 페이징 개수 조회 public int countAllCategoryItem(String keyword, Long categoryId) { return itemRepository.countAllCategoryItem(keyword,categoryId); }
itemController
//카테고리당 아이템 조회(정렬,검색,페이징(페이징받아서 페이징)) @GetMapping("/ypjs/categoryItem/get/{categoryId}") public String getAllCategoryItem(@PathVariable("categoryId") Long categoryId, @RequestParam(value = "page",defaultValue = "0") int page, @RequestParam(value = "size",defaultValue = "3") int size, @RequestParam(value = "sortBy", defaultValue = "itemId") String sortBy, @RequestParam(value = "keyword", required = false) String keyword, Model model) { Pageable pageable = PageRequest.of(page, size); Category category = categoryService.findOneCategory(categoryId); List<ItemListDto> items = itemService.finaAllItemPagingSortBy(categoryId, keyword, pageable, sortBy); //총 페이지 수 계산 int totalPages = Page.totalPages(itemService.countAllCategoryItem(keyword, categoryId), size); model.addAttribute("items",items); model.addAttribute("category", category); model.addAttribute("sortBy", sortBy); // 정렬 옵션을 다시 모델에 추가 model.addAttribute("keyword", keyword); //검색조건 유지 model.addAttribute("page",page); //페이징 model.addAttribute("size",size); //페이징 model.addAttribute("totalPages", totalPages); //총 페이지 수 return "item/itemCategoryList"; }
item/itemCategoryList
페이지네이션 부분만 추가
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head th:replace="frame/header :: head"> <title>Zay Shop - Product Listing Page</title> </head> <style> .fixed-size-img { width: 300px; /* 원하는 너비로 설정 */ height: 300px; /* 원하는 높이로 설정 */ object-fit: cover; /* 이미지를 크기에 맞게 조정 */ } </style> <body> <!--header--> <header th:replace="frame/header :: header"></header> <script src="/js/item/itemRatingsGet.js"></script> <!-- Start Content --> <div class="container py-5"> <div class="row"> <div class="col-lg-3"> <h1 class="h2 pb-4">Categories</h1> <ul class="list-unstyled templatemo-accordion"> <li class="pb-3"> <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#"> Women <i class="fa fa-fw fa-chevron-circle-down mt-1"></i> </a> <ul class="collapse show list-unstyled pl-3"> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=3)}">Outer</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=4)}">Top</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=5)}">Bottom</a></li> </ul> </li> <li class="pb-3"> <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#"> Man <i class="pull-right fa fa-fw fa-chevron-circle-down mt-1"></i> </a> <ul id="collapseTwo" class="collapse list-unstyled pl-3"> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=6)}">Outer</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=7)}">Top</a></li> <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=8)}">Bottom</a></li> </ul> </li> </ul> </div> <div class="col-lg-9"> <div class="row"> <div class="col-md-6"> <ul class="list-inline shop-top-menu pb-3 pt-1"> <li class="list-inline-item"> <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/item/get}">All Items</a> </li> </ul> </div> <div class="col-lg-6"> <!-- 검색 폼 및 정렬 폼 통합 --> <form th:action="@{/ypjs/categoryItem/get/{categoryId}(categoryId=${category.categoryId})}" method="get" class="input-group"> <input type="text" id="keyword" name="keyword" class="form-control" placeholder="Search by item name" th:value="${keyword}"> <div class="input-group-append"> <button type="submit" class="btn btn-link text-dark"> <i class="fa fa-search"></i> </button> </div> <div class="col-auto"> <select class="form-control" name="sortBy" id="sortBy" onchange="this.form.submit()"> <option value="itemId" th:selected="${sortBy == 'itemId'}">최신순</option> <option value="itemRatings" th:selected="${sortBy == 'itemRatings'}">별점 높은 순</option> <option value="likeCount" th:selected="${sortBy == 'likeCount'}">찜많은순</option> </select> </div> </form> </div> <div class="container-fluid mt-3"> <div class="row"> <!-- items 리스트의 각 항목을 반복 --> <div th:each="item : ${items}" class="col-md-4"> <div class="card mb-4 product-wap rounded-0"> <!-- 제품 이미지 --> <div class="card rounded-0"> <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}"> <img class="card-img rounded-0 img-fluid fixed-size-img" th:src="${item.itemFilePath}" alt="product-item"> <!-- 필요한 경우 오버레이나 추가 요소를 여기에 추가할 수 있습니다 --> <div class="card-img-overlay rounded-0 product-overlay d-flex align-items-center justify-content-center"> <!-- 추가적인 콘텐츠가 필요하다면 여기에 추가할 수 있습니다 --> <ul class="list-unstyled"> <!-- 리스트의 다른 요소들 --> </ul> </div> </a> </div> <!-- 제품 정보 --> <div class="card-body"> <!-- 제품 링크 --> <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a> <!-- 별점 및 평점 표시 --> <div class="d-flex justify-content-center mb-1 align-items-center"> <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${item.itemRatings}"> <!-- 별 아이콘은 JavaScript로 생성됩니다 --> </ul> <span id="itemRatings" class="font-weight-bold ml-2" th:text="${item.itemRatings}"></span> </div> <!-- 가격 표시 --> <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span> <span class="font-weight-bold">원</span> </div> </div> </div> </div> </div> </div> </div> </div> </div> <!-- 페이지네이션 시작--> <nav th:unless="${#lists.isEmpty(items)}" class="mt-4"> <ul class="pagination justify-content-center"> <li class="page-item" th:classappend="${page == 0} ? 'disabled'"> <a class="page-link" th:href="@{'/ypjs/categoryItem/get/' + ${category.categoryId} + '?page=' + (${page - 1}) + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">이전</a> </li> <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'"> <a class="page-link" th:href="@{'/ypjs/categoryItem/get/' + ${category.categoryId} + '?page=' + ${i} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}" th:text="${i + 1}">1</a> </li> <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'"> <a class="page-link" th:href="@{'/ypjs/categoryItem/get/' + ${category.categoryId} + '?page=' + (${page + 1}) + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">다음</a> </li> </ul> </nav> <!-- 페이지네이션 끝--> <!-- End Content --> <!-- Start Brands --> <section class="bg-light py-5"> <div class="container my-4"> <div class="row text-center py-3"> <div class="col-lg-6 m-auto"> <h1 class="h1">Our Brands</h1> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod Lorem ipsum dolor sit amet. </p> </div> <div class="col-lg-9 m-auto tempaltemo-carousel"> <div class="row d-flex flex-row"> <!--Controls--> <div class="col-1 align-self-center"> <a class="h1" href="#multi-item-example" role="button" data-bs-slide="prev"> <i class="text-light fas fa-chevron-left"></i> </a> </div> <!--End Controls--> <!--Carousel Wrapper--> <div class="col"> <div class="carousel slide carousel-multi-item pt-2 pt-md-0" id="multi-item-example" data-bs-ride="carousel"> <!--Slides--> <div class="carousel-inner product-links-wap" role="listbox"> <!--First slide--> <div class="carousel-item active"> <div class="row"> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a> </div> </div> </div> <!--End First slide--> <!--Second slide--> <div class="carousel-item"> <div class="row"> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a> </div> </div> </div> <!--End Second slide--> <!--Third slide--> <div class="carousel-item"> <div class="row"> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a> </div> <div class="col-3 p-md-5"> <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a> </div> </div> </div> <!--End Third slide--> </div> <!--End Slides--> </div> </div> <!--End Carousel Wrapper--> <!--Controls--> <div class="col-1 align-self-center"> <a class="h1" href="#multi-item-example" role="button" data-bs-slide="next"> <i class="text-light fas fa-chevron-right"></i> </a> </div> <!--End Controls--> </div> </div> </div> </div> </section> <!--End Brands--> <footer th:replace="frame/footer :: footer"></footer> </body> </html>
사진은 itemList와 동일
categoryList부분 검색, 페이징 없앰
categoryRepository
//category전체 조회 public List<Category> findAll() { return em.createQuery( "select distinct c from Category c", Category.class) .getResultList(); }
categoryService
public List<CategoryListDto> findAllCategory() { List<Category> categories = categoryRepository.findAll(); List<CategoryListDto> result = categories.stream() .map(c -> new CategoryListDto(c)) .collect(Collectors.toList()); return result; }
categoryController
//카테고리 리스트 보기 @GetMapping("/ypjs/category/get") public String getCategoryList(Model model) { List<CategoryListDto> categories = categoryService.findAllCategory(); model.addAttribute("categories", categories); return "category/categoryList"; }
화면은 전에 올린 글에 있음
categoryGet에 검색, 페이지네이션 추가
itemRepository, service
위에 카테고리 별 아이템 조회 그대로 씀
categoryController
//category 1개 조회 @GetMapping("/ypjs/category/get/{categoryId}") public String getOneCategory (@PathVariable("categoryId") Long categoryId, Model model, @RequestParam(value = "page", defaultValue = "0") int page, @RequestParam(value = "size", defaultValue = "3") int size, @RequestParam(value = "keyword", required = false) String keyword) { Pageable pageable = PageRequest.of(page, size); Category findCategory = categoryService.findOneCategory(categoryId); List<ItemListDto> items = itemService.findAllCategoryItem(categoryId, pageable, keyword); //총 페이지 수 계산 int totalPages = Page.totalPages(itemService.countAllCategoryItem(keyword, categoryId), size); model.addAttribute("category", findCategory); model.addAttribute("items",items); model.addAttribute("keyword", keyword); //검색조건 유지 model.addAttribute("page",page); //페이징 model.addAttribute("size",size); //페이징 model.addAttribute("totalPages", totalPages); //총 페이지 수 return "category/categoryGet"; }
category/categoryGet 검색, 페이지네이션, 아이템 이름에 링크 추가
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head th:replace="frame/header :: head"> <title>Zay Shop - Product Listing Page</title> </head> <body> <!--header--> <header th:replace="frame/header :: header"></header> <!-- Start Content --> <div class="container py-5"> 카테고리 번호 당 아이템 리스트 </div> <!-- End Content --> <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- JS 파일 포함 --> <script src="/js/jquery-1.11.0.min.js"></script> <script src="/js/summernote-lite.js"></script> <script src="/js/summernote-ko-KR.js"></script> <script src="/js/item/category.js"></script> <script src="/js/item/item.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote.min.js"></script> <!-- Start Contact --> <div class="container py-5"> <div class="row py-5"> <!-- 폼 요소들 --> <div class="row"> <style> .category-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; max-width: 1000px; /* 최대 너비 설정 */ margin-left: auto; margin-right: auto; } .category-label { font-weight: bold; } .item-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; max-width: 1000px; /* 최대 너비 설정 */ margin-left: auto; margin-right: auto; } .item-info { /* Optional: Add additional styles for item information */ } .category-label { font-weight: bold; } .h3-indent { margin-left: 100px; /* 왼쪽 여백을 줌으로써 텍스트를 오른쪽으로 이동 */ } </style> <div class="category-box"> <div> <span class="category-label">카테고리 번호:</span> <span>:</span> <span th:text="${category.categoryId}"></span> </div> <div> <span class="category-label">카테고리 부모 번호:</span> <span>:</span> <span th:text="${category.categoryParent.categoryId}"></span> </div> <div> <span class="category-label">카테고리 이름:</span> <span>:</span> <span th:text="${category.categoryName}"></span> </div> <br> <div class="container"> <div class="row justify-content-end"> <div class="col-auto"> <a th:href="@{/ypjs/category/update/{categoryId}(categoryId=${category.categoryId})}" class="btn btn-warning">수정하기</a> </div> <input type="hidden" id="categoryId" th:value="${category.categoryId}"> <div class="col-auto"> <button type="button" id="btn-categoryDelete" class="btn btn-danger">삭제하기</button> </div> </div> </div> </div> <div class="row"> <div class="col-lg-9"> <div class="row justify-content-start"> <div class="col-md-6 offset-md-9"> <br><br> <!-- 검색 폼 --> <form th:action="@{/ypjs/category/get/{categoryId}(categoryId=${category.categoryId})}" method="get" class="form-inline w-100"> <div class="input-group mb-3"> <input type="text" id="keyword" name="keyword" class="form-control" aria-label="Recipient's username" aria-describedby="button-addon2" placeholder="Search by item name" th:value="${keyword}"> <button type="submit" class="btn btn-link text-dark mb-2 ml-2" id="button-addon2"> <i class="fa fa-search"></i> </button> </div> </form> </div> </div> </div> </div> <div th:if="${not #lists.isEmpty(items)}"> <br><br><br> <h3 class="h3-indent">아이템 목록</h3> <br> <div th:each="item : ${items}" class="item-box"> <div class="item-info"> <div> <span class="category-label">아이템 이름:</span> <span>:</span> <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a> </div> <div> <span class="category-label">아이템 내용:</span> <span>:</span> <span th:utext="${item.itemContent}"></span> </div> <div> <span class="category-label">아이템 가격:</span> <span>:</span> <span th:text="${item.itemPrice}"></span> </div> <div> <span class="category-label">아이템 수량:</span> <span>:</span> <span th:text="${item.itemStock}"></span> </div> <!-- 필요한 다른 아이템 정보들을 추가 --> <div class="container"> <div class="row justify-content-end"> <div class="col-auto"> <a th:href="@{/ypjs/item/update/{itemId}(itemId=${item.itemId})}" class="btn btn-warning" style="padding: 2px 10px; font-size: 12px; width: 90px; height: 40px; text-align: center; display: inline-block; line-height: 40px;"> 수정하기 </a> </div> <button type="button" class="btn btn-danger btn-sm btn-delete-item" th:attr="data-itemId=${item.itemId}" style="padding: 2px 10px; font-size: 16px; width: 90px; height: 40px;"> 삭제하기 </button> </div> </div> </div> </div> </div> </div> </div> </div> <!-- 페이지네이션 시작--> <nav th:unless="${#lists.isEmpty(items)}" class="mt-4"> <ul class="pagination justify-content-center"> <li class="page-item" th:classappend="${page == 0} ? 'disabled'"> <a class="page-link" th:href="@{'/ypjs/category/get/' + ${category.categoryId} + '?page=' + (${page - 1}) + '&size=' + ${size} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">이전</a> </li> <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'"> <a class="page-link" th:href="@{'/ypjs/category/get/' + ${category.categoryId} + '?page=' + ${i} + '&size=' + ${size} + '&keyword=' + ${#strings.defaultString(keyword, '')}}" th:text="${i + 1}">1</a> </li> <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'"> <a class="page-link" th:href="@{'/ypjs/category/get/' + ${category.categoryId} + '?page=' + (${page + 1}) + '&size=' + ${size} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">다음</a> </li> </ul> </nav> <!-- 페이지네이션 끝--> <!-- End Contact --> <footer th:replace="frame/footer :: footer"></footer> </body> </html>
검색
페이징
itemReview페이징
itemReviewRepository
//아이템리뷰 총 개수 public int countAllItemReview(Long itemId){ return em.createQuery("select count(distinct ir) from ItemReview ir join ir.item i where i.itemId = :itemId", Long.class) .setParameter("itemId", itemId) .getSingleResult() .intValue(); }
itemReviewService
//아이템리뷰 페이징 개수 조회 public int countAllItemReview(Long itemId){ return itemReviewRepository.countAllItemReview(itemId); }
itmReviewController
//아이템 당 리뷰조회 @GetMapping("/ypjs/itemReview/get/{itemId}") public String getAllItemReview(@PathVariable(name = "itemId") Long itemId, Model model, @RequestParam(value = "page",defaultValue = "0") int page, @RequestParam(value = "size",defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); itemService.findOneItem(itemId); List<ItemReviewListDto> itemReviews = itemReviewService.findAllItemReview(itemId, pageable); //총 페이지 수 계산 int totalPages = Page.totalPages(itemReviewService.countAllItemReview(itemId), size); model.addAttribute("itemReviews", itemReviews); model.addAttribute("page",page); //페이징 model.addAttribute("size",size); //페이징 model.addAttribute("totalPages", totalPages); //총 페이지 수 return "itemreview/itemReviewGet"; }
itemreview/itemReviewGet 페이지네이션 추가
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head th:replace="frame/header :: head"> <title>Zay Shop - Product Listing Page</title> </head> <script src="/js/item/itemRatingsGet.js"></script> <body> <!--header--> <header th:replace="frame/header :: header"></header> <style> .category-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } .category-label { font-weight: bold; } .item-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } .item-info { /* Optional: Add additional styles for item information */ } .category-label { font-weight: bold; } #reviewContent img { display: block; /* 이미지가 한 줄에 나타나도록 설정 */ margin: auto; /* 가운데 정렬 */ max-width: 100%; /* 최대 너비 설정 */ height: auto; /* 높이 자동으로 조정 */ margin-bottom: 10px; /* 이미지 아래 여백 추가 */ } </style> <!-- Start Content --> <div class="container py-5"> 아이템 리뷰 </div> <!-- End Content --> <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- JS 파일 포함 --> <script src="/js/jquery-1.11.0.min.js"></script> <script src="/js/summernote-lite.js"></script> <script src="/js/summernote-ko-KR.js"></script> <script src="/js/item/itemReview.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote.min.js"></script> <!-- Start Contact --> <div class="container py-5"> <div class="row py-5"> <form class="col-md-9 m-auto" method="post"> <!-- 폼 요소들 --> <div class="row"> <div th:if="${not #lists.isEmpty(itemReviews)}"> <br><br><br> <h3>리뷰 목록</h3> <br> <div th:each="itemReview : ${itemReviews}" class="item-box"> <div class="item-info" style="text-align: right;"> <div> <span style="font-weight: bold;">작성일 : </span> <span id="itemReviewCreateDate" th:text="${#temporals.format(itemReview.itemReviewCreateDate, 'yyyy-MM-dd')}"></span> </div> </div> <div class="item-info"> <div> <span class="category-label">제목 : </span> <span th:text="${itemReview.itemReviewName}"></span> </div> <!-- 별점 및 평점 표시 --> <div class="d-flex justify-content-left mb-1 align-items-left"> <span class="category-label">별점 : </span> <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${itemReview.itemScore}"> <!-- 별 아이콘은 JavaScript로 생성됩니다 --> </ul> </div> <div> <span class="category-label">리뷰 내용 : </span> <div style="text-align: left;"> <!-- 썸머노트에서 입력한 리뷰 내용 --> <div id="reviewContent" th:utext="${itemReview.itemReviewContent}"> <!-- 썸머노트에서 삽입된 이미지의 스타일 --> </div> </div> </div> <input type="hidden" id="itemId" th:value="${itemReview.itemId}"> <!-- 수정 및 삭제 버튼 --> <div class="container"> <div class="row justify-content-end"> <div class="col-auto"> <a th:href="@{/ypjs/itemReview/update/{itemReviewId}(itemReviewId=${itemReview.itemReviewId})}" class="btn btn-warning">수정하기</a> </div> <div class="col-auto"> <button type="button" class="btn btn-danger btn-sm btn-delete-itemReview" th:data-itemReviewId="${itemReview.itemReviewId}" style="padding: 2px 10px; font-size: 16px; width: 90px; height: 40px;"> 삭제하기 </button> </div> </div> </div> </div> </div> </div> <!-- 페이지네이션 시작 --> <nav th:unless="${#lists.isEmpty(itemReviews)}" class="mt-4"> <ul class="pagination justify-content-center"> <li class="page-item" th:classappend="${page == 0} ? 'disabled'"> <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${page - 1}, size=${size})}">이전</a> </li> <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'"> <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${i}, size=${size})}" th:text="${i + 1}"></a> </li> <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'"> <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${page + 1}, size=${size})}">다음</a> </li> </ul> </nav> <!--페이지네이션 끝--> <!-- End Contact --> </div> </form> </div> </div> <footer th:replace="frame/footer :: footer"></footer> </body> </html>
itemReview js에 유효성 검사
itemReview.js 유효성 검사 부분 추가, summernote도 itemReviewPost.html에서 여기로 옮김
let itemBoardObject = { init: function() { let _this = this; $("#btn-itemReviewPost").on("click", function() { _this.insert(); }), $("#btn-itemReviewUpdate").on("click", function() { _this.update(); }), $(document).on("click", ".btn-delete-itemReview", function() { // 클릭된 버튼의 데이터 속성에서 itemReviewId를 가져옴 let itemReviewId = $(this).data("itemreviewid"); // 아이템 리뷰 삭제 함수 호출 _this.deleteItemReview(itemReviewId); }); // Summernote 초기화 $("#itemReviewContent").summernote({ height: 300, placeholder: '내용을 입력하세요...' }); }, insert: function() { let itemId = $("#itemId").val(); // URL에 사용할 itemId를 가져옴 let itemScore = $("#itemScore").val(); // 별점 가져오기 let itemReviewName = $("#itemReviewName").val(); // 제목 가져오기 let itemReviewContent = $("#itemReviewContent").summernote('code'); //썸머노트 내용 가져오기 // 별점을 선택하지 않은 경우 처리 if (itemScore === "0") { alert("별점을 선택하세요."); return; // 리뷰 등록 중단 } // 제목이 비어 있는지 확인 if (!itemReviewName || itemReviewName.trim().length === 0) { alert("제목을 입력하세요."); return; // 리뷰 등록 중단 } // 썸머노트에서 가져온 HTML 태그 제거 및 내용 검사 let plainTextContent = stripTags(itemReviewContent).trim(); if (!plainTextContent || plainTextContent.length === 0) { alert("내용을 입력하세요."); return; } let data = { itemId: itemId, // itemId를 데이터에 포함 memberId: $("#memberId").val(), // 멤버 ID 추가 itemReviewName: $("#itemReviewName").val(), itemScore: $("#itemScore").val(), itemReviewContent: $("#itemReviewContent").val(), }; $.ajax({ type: "POST", url: "/ypjs/itemReview/post/" + itemId, // URL에 itemId를 추가 data: JSON.stringify(data), contentType: "application/json; charset=utf-8", success: function(response) { alert("리뷰가 등록되었습니다."); window.location.href = "/ypjs/itemReview/get/" + itemId; // 성공 후 리디렉션 }, error: function(error) { alert("에러 발생: " + JSON.stringify(error)); } }); }, update: function() { let itemReviewId = $("#itemReviewId").val(); let itemScore = $("#itemScore").val(); // 별점 가져오기 let itemReviewName = $("#itemReviewName").val(); // 제목 가져오기 let itemReviewContent = $("#itemReviewContent").summernote('code'); //썸머노트 내용 가져오기 // 별점을 선택하지 않은 경우 처리 if (itemScore === "0") { alert("별점을 선택하세요."); return; // 리뷰 등록 중단 } // 제목이 비어 있는지 확인 if (!itemReviewName || itemReviewName.trim().length === 0) { alert("제목을 입력하세요."); return; // 리뷰 등록 중단 } // 썸머노트에서 가져온 HTML 태그 제거 및 내용 검사 let plainTextContent = stripTags(itemReviewContent).trim(); if (!plainTextContent || plainTextContent.length === 0) { alert("내용을 입력하세요."); return; } let updateData = { itemReviewName: $("#itemReviewName").val(), itemScore: $("#itemScore").val(), itemReviewContent: $("#itemReviewContent").val(), }; $.ajax({ type: "PUT", url: "/ypjs/itemReview/update/" + itemReviewId, data: JSON.stringify(updateData), contentType: "application/json; charset=utf-8", success: function(response) { alert("리뷰가 수정되었습니다."); window.location.href = "/ypjs/itemReview/get/" + response ; }, error: function(error) { alert("에러 발생: " + JSON.stringify(error)); // 에러 발생 시 적절히 처리하도록 수정이 필요할 수 있습니다. } }); }, deleteItemReview: function(itemReviewId) { let itemId = $("#itemId").val(); $.ajax({ type: "DELETE", url: "/ypjs/itemReview/delete/" + itemReviewId, success: function(response) { alert("리뷰가 삭제되었습니다."); window.location.href = "/ypjs/itemReview/get/" + itemId; }, error: function(xhr, status, error) { alert("에러 발생: " + error); } }); } }; $(document).ready(function() { itemBoardObject.init(); }); // HTML 태그 제거 함수 function stripTags(html) { let tmp = document.createElement("DIV"); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ""; }
카테고리 유효성 검사
category.js에 유효성 검사 추가
let categoryBoardObject = { init: function() { let _this = this; $("#btn-categoryPost").on("click", function() { _this.insert(); }), $("#btn-categoryUpdate").on("click", function() { _this.update(); }), $("#btn-categoryDelete").on("click", function() { _this.delete(); // 수정된 부분: delete 함수 호출 }); }, insert: function() { let categoryParent = $("#categoryParent").val(); let categoryName = $("#categoryName").val(); if (!categoryParent || categoryParent.trim().length === 0) { alert("카테고리 부모 번호를 입력하세요"); return; } if(!categoryName || categoryName.trim().length === 0){ alert("카테고리 이름을 입력하세요"); return; } let data = { categoryParent: $("#categoryParent").val(), categoryName: $("#categoryName").val(), }; $.ajax({ type: "POST", url: "/ypjs/category/post", data: JSON.stringify(data), contentType: "application/json; charset=utf-8", success: function(response) { alert("카테고리 저장되었습니다."); window.location.href = "/ypjs/category/get"; }, error: function(error) { window.location.href = "/ypjs/category/get"; } }); }, update: function() { let categoryId = $("#categoryId").val(); let categoryParent = $("#categoryParent").val(); let categoryName = $("#categoryName").val(); if (!categoryId || categoryId.trim().length === 0) { alert("카테고리 번호를 입력하세요"); return; } if (!categoryParent || categoryParent.trim().length === 0) { alert("카테고리 부모 번호를 입력하세요"); return; } if(!categoryName || categoryName.trim().length === 0){ alert("카테고리 이름을 입력하세요"); return; } let updateData = { categoryId: categoryId, // 수정된 부분: categoryId 추가 categoryParent: $("#categoryParent").val(), categoryName: $("#categoryName").val(), }; $.ajax({ type: "PUT", url: "/ypjs/category/update/" + categoryId, data: JSON.stringify(updateData), contentType: "application/json; charset=utf-8", success: function(response) { alert("카테고리가 수정되었습니다."); window.location.href = "/ypjs/category/get"; }, error: function(error) { alert("에러 발생: " + JSON.stringify(error)); } }); }, delete: function() { let categoryId = $("#categoryId").val(); // 삭제할 categoryId 가져오기 $.ajax({ type: "DELETE", url: "/ypjs/category/delete/" + categoryId, success: function(response) { alert("카테고리가 삭제되었습니다."); window.location.href = "/ypjs/category/get"; // 삭제 후 페이지 이동 }, error: function(xhr, status, error) { alert("에러 발생: " + error); } }); } }; $(document).ready(function() { categoryBoardObject.init(); });
아이템 유효성 검사
itemPost.html, itemUpdate.html은 form으로 넘겨서 따로 itemValidation.js만들어서 유효성 검사,
썸머노트, 썸네일부분 script도 여기로 옮김
$(document).ready(function () { // Summernote 초기화 $("#itemContent").summernote({ height: 500, placeholder: '상품 내용을 입력하세요...', callbacks: { onChange: function (contents, $editable) { // 내용이 변경될 때 추가 로직 } } }); // 파일명 표시 함수 function displayFileName(input) { if (input.files.length > 0) { var fileName = input.files[0].name; document.getElementById('file-name-display').innerText = '선택된 파일: ' + fileName; } else { document.getElementById('file-name-display').innerText = ''; } } // 폼 제출 전 유효성 검사 $('#itemForm').submit(function (event) { var categoryId = $("#categoryId").val(); var itemName = $("#itemName").val(); var itemPrice = $("#itemPrice").val(); var itemStock = $("#itemStock").val(); var itemContent = $("#itemContent").summernote('code'); if (!categoryId || categoryId.trim().length === 0) { alert('카테고리 번호를 입력하세요.'); event.preventDefault(); // 폼 제출 방지 return; } if (!itemName || itemName.trim().length === 0) { alert('상품명을 입력하세요.'); event.preventDefault(); // 폼 제출 방지 return; } if (!itemPrice || itemPrice.trim().length === 0) { alert('상품가격을 입력하세요.'); event.preventDefault(); // 폼 제출 방지 return; } if (!itemStock || itemStock.trim().length === 0) { alert('상품수량을 입력하세요.'); event.preventDefault(); // 폼 제출 방지 return; } // 썸머노트에서 가져온 HTML 태그 제거 및 내용 검사 let plainTextContent = stripTags(itemContent).trim(); if (!plainTextContent || plainTextContent.trim().length === 0) { alert('상품내용을 입력하세요.'); event.preventDefault(); // 폼 제출 방지 return; } var fileInput = document.getElementById('file'); if (fileInput.files.length === 0) { alert('썸네일 파일을 선택하세요.'); event.preventDefault(); // 폼 제출 방지 } }); }); // HTML 태그 제거 함수 function stripTags(html) { let tmp = document.createElement("DIV"); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ""; }
itemList.html, itemCategoryList.html, itemGet.html에 있던 별 script
itemRatingsGet.js따로 만듦
document.addEventListener("DOMContentLoaded", function() { var starRatingContainers = document.querySelectorAll("#starRating"); starRatingContainers.forEach(function(starRatingContainer) { var rating = parseFloat(starRatingContainer.getAttribute("data-rating")); starRatingContainer.innerHTML = generateStarRating(rating); }); // 별점을 생성하여 HTML 문자열 반환하는 함수 (순수 JavaScript) function generateStarRating(rating) { var starsHTML = ''; var numStars; if (rating >= 0 && rating < 1) { numStars = 0; } else if (rating >= 1 && rating < 2) { numStars = 1; } else if (rating >= 2 && rating < 3) { numStars = 2; } else if (rating >= 3 && rating < 4) { numStars = 3; } else if (rating >= 4 && rating < 5) { numStars = 4; } else if (rating == 5) { numStars = 5; } for (var i = 0; i < numStars; i++) { starsHTML += '<i class="text-warning fa fa-star"></i>'; } for (var i = numStars; i < 5; i++) { starsHTML += '<i class="text-muted fa fa-star"></i>'; } return starsHTML; } });
itemReviewPost.html, itemReviewUpdate.html에 있던 별 js파일 따로 만듦
itemRatingsPost.js
$(document).ready(function() { var initialScore = $('#itemScore').val(); // 기존의 itemScore 값 가져오기 $('#starRating').html(generateStarRating(initialScore)); // 초기화: 기존의 별점으로 별 아이콘 표시 // 별 아이콘 클릭 이벤트 리스너 추가 $('#starRating').on('click', '.fa-star', function() { var score = $(this).data('score'); $('#itemScore').val(score); // hidden 필드에 새로운 점수 설정 $('#starRating').html(generateStarRating(score)); // 별 아이콘 업데이트 }); // 별 아이콘을 생성하여 HTML 문자열 반환하는 함수 function generateStarRating(score) { var starsHTML = ''; for (var i = 1; i <= 5; i++) { if (i <= score) { starsHTML += '<i class="fas fa-star rating-stars filled-star" data-score="' + i + '"></i>'; } else { starsHTML += '<i class="fas fa-star rating-stars" data-score="' + i + '"></i>'; } } return starsHTML; } });
둘이 합치고 싶었지만 그러면 둘 중 하나만 적용 됨
리뷰 갯수 추가
//item1개 조회 @GetMapping("/ypjs/item/get/{itemId}") public String getOneItem (@PathVariable("itemId") Long itemId, Model model) { Item findItem = itemService.findOneItem(itemId); ItemOneDto item = new ItemOneDto(findItem); model.addAttribute("item", item); //조회수 itemService.increaseItemCnt(itemId); //리뷰 갯수 int reviewCount = itemReviewService.countAllItemReview(itemId); model.addAttribute("reviewCount", reviewCount); return "item/itemGet"; }
서비스, 레파지토리는 위에 있음
item/itemGet
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head th:replace="frame/header :: head"> <title>Zay Shop - Product Listing Page</title> </head> <script src="/js/item/itemRatings.js"></script> <body> <!--header--> <header th:replace="frame/header :: header"></header> <script src="/js/item/itemRatingsGet.js"></script> <script src="/js/item/item.js"></script> <style> .bg-light { background-color: transparent !important; /* 배경색을 투명하게 설정 */ } /* 요소의 우선순위를 높여서 Bootstrap의 font-weight-bold 클래스가 적용될 수 있도록 함 */ h1.font-weight-bold, span.font-weight-bold { font-weight: bold !important; } .custom-mt { margin-top: -20px; /* 원하는 간격 값으로 설정 */ } <!-- 가운데 정렬--> .centered-content { display: flex; flex-direction: column; align-items: center; justify-content: center; } #itemContent { text-align: center; } /* #itemContent를 flexbox로 설정하여 가운데 정렬 */ #itemContent { display: flex; flex-direction: column; /* 내부 요소들을 세로로 정렬하기 위해 */ justify-content: center; /* 수직 가운데 정렬 */ align-items: center; /* 수평 가운데 정렬 */ } /* 썸머노트에서 삽입된 이미지의 스타일 */ #itemContent img { max-width: 100%; /* 최대 너비 설정 */ height: auto; /* 높이 자동으로 조정 */ margin-bottom: 10px; /* 이미지 아래 여백 추가 */ } /* 썸머노트에서 삽입된 텍스트의 스타일 */ #itemContent p { text-align: center; /* 텍스트 가운데 정렬 */ margin-bottom: 10px; /* 문단 아래 여백 추가 */ } </style> <!-- Open Content --> <div class="card-body pt-4 mt-3"> <!-- pt-4를 mt-3으로 변경 --> <div class="row"> <div class="col-lg-7 offset-lg-1"> <h1 class="h2 mb-3 font-weight-bold" th:text="${item.itemName}"></h1> <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span> <span class="font-weight-bold">원</span> <p class="h3 py-2"></p> </div> </div> <!-- 별점 및 평점 표시 --> <div class="row mt-3"> <div class="col-lg-7 offset-lg-1"> <div class="d-flex justify-content-start align-items-center"> <ul class="list-unstyled d-flex mb-0 mr-3 text-left" id="starRating" th:attr="data-rating=${item.itemRatings}"> <!-- 별 아이콘은 JavaScript로 생성됩니다 --> </ul> <span id="itemRatings" th:text="${item.itemRatings}"></span> <div> <a th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${item.itemId})}"> Reviews (<span th:text="${reviewCount}"></span>) </a> </div> </div> </div> </div> <!-- 수정하기 및 삭제하기 버튼 --> <div class="row justify-content-end mt-3"> <div class="col-auto"> <a th:href="@{/ypjs/item/update/{itemId}(itemId=${item.itemId})}" class="btn btn-warning">수정하기</a> </div> <div class="col-auto"> <input type="hidden" id="itemId" th:value="${item.itemId}"> <button type="button" id="btn-itemDelete" class="btn btn-danger">삭제하기</button> </div> </div> <div class="row justify-content-end mt-3"> <div class="col-auto"> <span class="font-weight-bold"> 조회수 : </span> <span id="itemCnt" class="font-weight-bold" th:text="${item.itemCnt}"></span> <br> <span class="font-weight-bold">작성일 : </span> <span id="itemCreateDate" class="font-weight-bold" th:text="${#temporals.format(item.itemCreateDate, 'yyyy-MM-dd')}"></span> </div> <div class="col-auto"> </div> </div> </div> </div> </div> <hr> <section class="bg-light"> <div class="container pb-5"> <div class="row justify-content-center align-items-center"> <div class="col-lg-5 mt-5 text-center"> <div id="itemContent" th:utext="${item.itemContent}"> <!-- 썸머노트에서 입력된 내용 (글과 이미지) --> </div> </div> </div> </div> </section> <div class="row pb-3"> <div class="col-lg-6 offset-lg-5"> <!-- offset-lg-6 클래스 추가하여 오른쪽으로 옮김 --> <ul class="list-inline pb-3 text-right"> <!-- text-right 클래스 추가하여 내용 오른쪽 정렬 --> <li class="list-inline-item"> 수량 <input type="hidden" name="product-quanity" id="product-quanity" value="1"> </li> <li class="list-inline-item"><span class="btn btn-success" id="btn-minus">-</span></li> <li class="list-inline-item"><span class="badge bg-secondary" id="var-value">1</span></li> <li class="list-inline-item"><span class="btn btn-success" id="btn-plus">+</span></li> </ul> </div> </div> <div class="row pb-3"> <div class="col d-grid"> <button type="submit" class="btn btn-success btn-md" name="submit" value="buy">Buy</button> </div> <div class="col d-grid"> <button type="submit" class="btn btn-success btn-md" name="submit" value="addtocard">Add To Cart</button> </div> </div> </form> </div> </div> </div> </div> </div> </section> <!-- Close Content --> <footer th:replace="frame/footer :: footer"></footer> </body> </html>
reviewCount부분 추가
리뷰 갯수 링크에 같이 연결돼서 나옴
리뷰 정렬
기본 최신순, 별점 높은 순, 별점 낮은 순 정렬
itemReviewRepository
//아이템 당 리뷰 조회 public List<ItemReview> findAllItemReview(Long itemId, Pageable pageable, String sortBy) { // 기본 쿼리 문자열 String queryString = "select ir from ItemReview ir join fetch ir.item i where i.itemId = :itemId"; // 정렬 조건 추가 switch (sortBy) { case "highScore": queryString += " order by ir.itemScore desc, ir.itemReviewId desc"; // 'highScore'일 때 itemScore 기준 내림차순, 동일할 경우 itemReviewId 내림차순 정렬 break; case "lowScore": queryString += " order by ir.itemScore asc, ir.itemReviewId desc"; // 'lowScore'일 때 itemScore 기준 오름차순, 동일할 경우 itemReviewId 내림차순 정렬 break; default: queryString += " order by ir.itemReviewId desc"; // 기본 정렬: itemReviewId 기준 내림차순 정렬 (최신순) break; } return em.createQuery(queryString, ItemReview.class) .setParameter("itemId", itemId) .setFirstResult((int) pageable.getOffset()) .setMaxResults(pageable.getPageSize()) .getResultList(); }
itemReviewService
//아이템 당 리뷰조회 public List<ItemReviewListDto> findAllItemReview(Long itemId, Pageable pageable, String sortBy) { List<ItemReview> reviews = itemReviewRepository.findAllItemReview(itemId, pageable, sortBy); List<ItemReviewListDto> result = reviews.stream() .map(ItemReviewListDto::new) .collect(Collectors.toList()); return result; }
itemReviewController
//아이템 당 리뷰조회 @GetMapping("/ypjs/itemReview/get/{itemId}") public String getAllItemReview(@PathVariable(name = "itemId") Long itemId, Model model, @RequestParam(value = "page",defaultValue = "0") int page, @RequestParam(value = "sortBy", defaultValue = "itemReviewId") String sortBy, @RequestParam(value = "size",defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); itemService.findOneItem(itemId); List<ItemReviewListDto> itemReviews = itemReviewService.findAllItemReview(itemId, pageable, sortBy); //총 페이지 수 계산 int totalPages = Page.totalPages(itemReviewService.countAllItemReview(itemId), size); model.addAttribute("itemReviews", itemReviews); model.addAttribute("sortBy", sortBy); // 정렬 옵션을 다시 모델에 추가 model.addAttribute("page",page); //페이징 model.addAttribute("size",size); //페이징 model.addAttribute("totalPages", totalPages); //총 페이지 수 return "itemreview/itemReviewGet"; }
itemreview/itemReviewGet
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head th:replace="frame/header :: head"> <title>Zay Shop - Product Listing Page</title> </head> <script src="/js/item/itemRatingsGet.js"></script> <body> <!--header--> <header th:replace="frame/header :: header"></header> <style> .category-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } .category-label { font-weight: bold; } .item-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; max-width: 1000px; /* 최대 너비 설정 */ margin-left: auto; margin-right: auto; } .item-info { /* Optional: Add additional styles for item information */ } .category-label { font-weight: bold; } .h3-indent { margin-left: 100px; /* 왼쪽 여백을 줌으로써 텍스트를 오른쪽으로 이동 */ } .custom-select-wrapper { border: 1px solid #f0f8ff; /* 테두리 두께와 색상 설정 */ padding: 3px; /* 선택 영역의 여백 설정 */ background-color: #f0f8ff; /* 연한 하늘색 배경색 */ display: inline-block; /* 인라인 요소로 표시 */ border-radius: 10px; /* 테두리 둥글기 설정 */ } .form-control { border: 1px solid #f0f8ff; /* 내부 테두리 설정 */ border-radius: 8px; /* 안쪽 테두리 둥글기 설정 */ } </style> <!-- Start Content --> <div class="container py-5"> 아이템 리뷰 </div> <!-- End Content --> <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="/js/item/itemReview.js"></script> <!-- Start Contact --> <!--정렬 추가--> <form th:action="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0]?.itemId})}" method="get" class="row"> <div class="offset-sm-9 col-sm-1 mb-2 custom-select-wrapper" style="width: 8%;"> <select class="form-control" name="sortBy" id="sortBy" onchange="this.form.submit()"> <option th:value="itemReviewId" th:selected="${sortBy == 'itemReviewId'}">최신순</option> <option th:value="highScore" th:selected="${sortBy == 'highScore'}">별점 높은 순</option> <option th:value="lowScore" th:selected="${sortBy == 'lowScore'}">별점 낮은 순</option> </select> </div> </form> <div class="container py-2"> <div class="row py-5"> <!-- 폼 요소들 --> <div class="row"> <div th:if="${not #lists.isEmpty(itemReviews)}"> <h3 class="h3-indent">리뷰 목록</h3> <br> <div th:each="itemReview : ${itemReviews}" class="item-box"> <div class="item-info" style="text-align: right;"> <div> <span style="font-weight: bold;">작성일 : </span> <span id="itemReviewCreateDate" th:text="${#temporals.format(itemReview.itemReviewCreateDate, 'yyyy-MM-dd')}"></span> </div> </div> <div class="item-info"> <div> <span class="category-label">제목 : </span> <span th:text="${itemReview.itemReviewName}"></span> </div> <!-- 별점 및 평점 표시 --> <div class="d-flex justify-content-left mb-1 align-items-left"> <span class="category-label">별점 : </span> <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${itemReview.itemScore}"> <!-- 별 아이콘은 JavaScript로 생성됩니다 --> </ul> </div> <div> <span class="category-label">리뷰 내용 : </span> <div style="text-align: left;"> <span th:utext="${itemReview.itemReviewContent}"></span> </div> </div> <input type="hidden" id="itemId" th:value="${itemReview.itemId}"> <!-- 수정 및 삭제 버튼 --> <div class="container"> <div class="row justify-content-end"> <div class="col-auto"> <a th:href="@{/ypjs/itemReview/update/{itemReviewId}(itemReviewId=${itemReview.itemReviewId})}" class="btn btn-warning">수정하기</a> </div> <div class="col-auto"> <button type="button" class="btn btn-danger btn-sm btn-delete-itemReview" th:data-itemReviewId="${itemReview.itemReviewId}" style="padding: 2px 10px; font-size: 16px; width: 90px; height: 40px;"> 삭제하기 </button> </div> </div> </div> </div> </div> </div> <!-- 페이지네이션 시작 --> <nav th:unless="${#lists.isEmpty(itemReviews)}" class="mt-4"> <ul class="pagination justify-content-center"> <li class="page-item" th:classappend="${page == 0} ? 'disabled'"> <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${(page > 0 ? page - 1 : 0)}, size=${size}, sortBy=${sortBy})}">이전</a> </li> <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'"> <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${i}, size=${size}, sortBy=${sortBy})}" th:text="${i + 1}"></a> </li> <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'"> <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${(page < totalPages - 1 ? page + 1 : totalPages - 1)}, size=${size}, sortBy=${sortBy})}">다음</a> </li> </ul> </nav> <!--페이지네이션 끝--> <!-- End Contact --> </div> </div> </div> <footer th:replace="frame/footer :: footer"></footer> </body> </html>
스타일, 정렬부분 추가
'쇼핑몰 프로젝트' 카테고리의 다른 글
쇼핑몰 프로젝트 --카테고리 수정, 디자인변경(화면구현) (0) 2024.07.04 쇼핑몰 프로젝트 --카테고리 링크수정, 컨트롤러분리,유효성검사(화면구현) (0) 2024.07.02 쇼핑몰 프로젝트 --별 기능(화면구현) (0) 2024.06.26 쇼핑몰 프로젝트 --리뷰 수정, 삭제, 아이템 검색, 정렬(화면구현) (0) 2024.06.26 쇼핑몰 프로젝트 -- 카테고리, 리뷰(화면구현) (0) 2024.06.20