상품 보기 - 리뷰

<p th:text="'리뷰 ' + ${reviewPageResponseDTO.avgRate} + ' / 5'"></p>

<div class="reviews">
  <div>
    <p th:text="'총 ' + ${reviewPageResponseDTO.total} + '건'"></p>
    <div>
      <a href="#" class="sort-link" data-sort-type="highestRate">평점 높은 순</a>&nbsp;&nbsp;|&nbsp;&nbsp;
      <a href="#" class="sort-link" data-sort-type="lowestRate">평점 낮은 순</a>&nbsp;&nbsp;|&nbsp;&nbsp;
      <a href="#" class="sort-link" data-sort-type="latest">최신순</a>
    </div>
  </div>
  <div th:each="review : ${reviewPageResponseDTO.dtoList}" class="reviews_content">
    <p th:text="${#strings.substring(review.uid, 0, 4) + '***'}"></p>

    <div class="stars">
      <img src="/images/product/full_star.png"
           th:each="i : ${#numbers.sequence(1, review.rating)}"
           alt="★">
      <p th:text="${review.rating}">5</p>
    </div>

    <p th:text="'판매자 : ' + ${review?.product?.company}">판매자 : 롯데백화점 본점</p>

    <p th:utext="${review.content}">
      리뷰 내용
    </p>

    <img th:if="${review.sNameImage1 != null}" th:src="${review.sNameImage1}" alt="리뷰 이미지">
    <img th:if="${review.sNameImage2 != null}" th:src="${review.sNameImage2}" alt="리뷰 이미지">
  </div>

  <div class="reviews_page">
    <th:block th:if="${reviewPageResponseDTO.prev}">
      <a href="#" class="prev">&lt;</a>
    </th:block>
    <th:block th:each="num : ${#numbers.sequence(reviewPageResponseDTO.start, reviewPageResponseDTO.end)}">
      <a href="#" th:class="${num == reviewPageResponseDTO.pg} ? 'current' : 'num'" th:data-page="${num}">
        [[${num}]]
      </a>
    </th:block>
    <th:block th:if="${reviewPageResponseDTO.next}">
      <a href="#" class="next">&gt;</a>
    </th:block>
  </div>
</div>

기술 설명

리뷰 목록 AJAX 처리 및 페이징·정렬 기능 (view.js)

const reviewsContainer = document.querySelector('.reviews');
let currentSortType = null;

// AJAX 응답 데이터로 리뷰 리스트와 페이징 생성
function updateReviewListUI(reviewData) {
    // 기존 리뷰 내용 제거
    const existingReviewsContent = reviewsContainer.querySelectorAll('.reviews_content');
    existingReviewsContent.forEach(review => review.remove());

    // 리뷰 항목 생성
    reviewData.dtoList.forEach(review => {
        const reviewContent = document.createElement('div');
        reviewContent.classList.add('reviews_content');

        let starsHTML = '';
        for (let i = 0; i < Math.floor(review.rating); i++) {
            starsHTML += '<span>⭐</span>';
        }

        reviewContent.innerHTML = `
            <p>${review.uid.substring(0, 4)}***</p>
            <div class="stars">
                ${starsHTML}
                <p>${review.rating}</p>
            </div>
            <p>판매자 : ${review?.product?.company || '정보 없음'}</p>
            <p>${review.content.replace(/\\\\n/g, '<br>')}</p>
            ${review.snameImage1 ? `<img src="/upload/review/${review.snameImage1}" alt="리뷰 이미지 1">` : ''}
            ${review.snameImage2 ? `<img src="/upload/review/${review.snameImage2}" alt="리뷰 이미지 2">` : ''}
        `;
        reviewsContainer.appendChild(reviewContent);
    });

    // 페이징 컨테이너 준비
    let reviewsPage = reviewsContainer.querySelector('.reviews_page');
    if (!reviewsPage) {
        reviewsPage = document.createElement('div');
        reviewsPage.classList.add('reviews_page');
    }
    reviewsPage.innerHTML = '';

    // 이전 페이지 링크
    if (reviewData.prev) {
        reviewsPage.innerHTML += `<a href="#" class="prev" data-page="${reviewData.start - 1}">&lt;</a>`;
    }

    // 페이지 번호 링크
    for (let i = reviewData.start; i <= reviewData.end; i++) {
        const currentClass = i === reviewData.pg ? 'current' : 'num';
        reviewsPage.innerHTML += `<a href="#" class="${currentClass}" data-page="${i}">${i}</a>`;
    }

    // 다음 페이지 링크
    if (reviewData.next) {
        reviewsPage.innerHTML += `<a href="#" class="next" data-page="${reviewData.end + 1}">&gt;</a>`;
    }

    reviewsContainer.appendChild(reviewsPage);
}

// 리뷰 리스트를 AJAX로 요청
function fetchReviews(page, prodNo, sortType) {
    let requestUrl = `/product/reviewList?pg=${page}&prodNo=${prodNo}`;
    if (sortType) {
        requestUrl += `&sortType=${sortType}`;
    }
    fetch(requestUrl)
        .then(response => response.json())
        .then(data => {
            updateReviewListUI(data);
        })
        .catch(error => {
            console.error('리뷰 목록 로드 실패:', error);
        });
}

// 페이지 번호 클릭 이벤트 처리
function handlePaginationClick(event) {
    if (event.target.matches('.reviews_page a')) {
        event.preventDefault();
        const pageNumber = parseInt(event.target.getAttribute('data-page'));
        if (!isNaN(pageNumber) && pageNumber > 0) {
            fetchReviews(pageNumber, document.getElementById('prodNo').value, currentSortType);
        }
    }
}

// 정렬 링크 클릭 이벤트 처리
function handleSortClick(event) {
    if (event.target.matches('.sort-link')) {
        event.preventDefault();
        const sortType = event.target.getAttribute('data-sort-type');
        currentSortType = sortType; // 선택된 정렬 타입 저장
        fetchReviews(1, document.getElementById('prodNo').value, currentSortType);
    }
}

// 초기 리뷰 요청 및 이벤트 바인딩
fetchReviews(1, document.getElementById('prodNo').value, currentSortType);
reviewsContainer.addEventListener('click', handlePaginationClick);
reviewsContainer.addEventListener('click', handleSortClick);

기능 설명