merge master into develop - CscListRForm.jsp FAQ 아코디언 버그 수정

This commit is contained in:
jilee4unity
2026-05-28 23:34:06 +09:00
7250 changed files with 570509 additions and 25 deletions

View File

@@ -0,0 +1,214 @@
/**
* Cpslmkn.js
*
* 캡슐 메이킹 게임 기능을 관리하는 모듈
* 사용자가 제품을 선택하고 게임을 진행할 수 있는 인터페이스 제공
*/
$(function() {
// 초기 컨텐츠 숨김 - 인트로 모달이 먼저 표시되어야 함
$('.content-wrap').hide();
// 인트로 모달 초기화 및 표시 - 게임 시작 전 소개 화면
initIntroModal();
// 제품 선택 이벤트 바인딩 - 사용자가 게임을 진행할 제품 선택
bindProductSelection();
// 시작하기 버튼 이벤트 바인딩 - 제품 선택 후 게임 시작
bindStartButton();
/**
* 제품 선택 시 시각적 피드백 및 상태 업데이트
* 사용자가 게임 제품을 클릭하면 해당 항목 강조 표시 및 선택 처리
*/
$('.gameProduct').on('click', function() {
// 모든 gameProduct에서 selected 클래스 제거 - 기존 선택 초기화
$('.gameProduct').removeClass('selected');
// 클릭된 gameProduct에 selected 클래스 추가 - 현재 선택 항목 강조
$(this).addClass('selected');
// 해당 radio 버튼 선택 - UI와 실제 form 값 동기화
$(this).find('input[type="radio"]').prop('checked', true);
// 시작 버튼 활성화 - 제품 선택 완료 시 다음 단계 진행 가능
$('.bottomBtnControls .btn').prop('disabled', false);
});
});
/**
* 인트로 모달 초기화 및 표시 함수
* 게임 시작 전 소개 슬라이드를 표시하고 관련 이벤트 설정
*/
function initIntroModal() {
// 슬라이드 및 페이징 요소 참조
const $slides = $('.gameIntroWrap'); // 각 소개 슬라이드
const $paging = $('.gamePaging'); // 페이징 도트 컨테이너
let currentSlide = 0; // 현재 표시 중인 슬라이드 인덱스
// 터치 이벤트 관련 변수
let touchStartX = 0;
let touchStartY = 0;
let touchEndX = 0;
let touchEndY = 0;
let isSwipe = false;
// 페이징 도트 동적 생성 - 슬라이드 개수만큼 도트 추가
$slides.each(function() {
$paging.append('<span></span>');
});
// 초기 슬라이드 표시 - 첫 번째 슬라이드(인덱스 0) 표시
showSlide(0);
// 터치 시작 이벤트
$('.gameIntroPaging').on('touchstart', function(e) {
touchStartX = e.originalEvent.touches[0].clientX;
touchStartY = e.originalEvent.touches[0].clientY;
isSwipe = false;
});
// 터치 이동 이벤트
$('.gameIntroPaging').on('touchmove', function(e) {
e.preventDefault(); // 스크롤 방지
touchEndX = e.originalEvent.touches[0].clientX;
touchEndY = e.originalEvent.touches[0].clientY;
// 움직임이 있으면 스와이프로 판단
const deltaX = Math.abs(touchEndX - touchStartX);
const deltaY = Math.abs(touchEndY - touchStartY);
if (deltaX > 10 || deltaY > 10) {
isSwipe = true;
}
});
// 터치 종료 이벤트
$('.gameIntroPaging').on('touchend', function(e) {
if (!isSwipe) return; // 스와이프가 아니면 무시
const deltaX = touchEndX - touchStartX;
const deltaY = touchEndY - touchStartY;
const minSwipeDistance = 50; // 최소 스와이프 거리
// 좌우 스와이프만 처리 (상하 스와이프는 무시)
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > minSwipeDistance) {
if (deltaX > 0) {
// 오른쪽 스와이프 - 이전 슬라이드
const prevIndex = currentSlide === 0 ? $slides.length - 1 : currentSlide - 1;
showSlide(prevIndex);
} else {
// 왼쪽 스와이프 - 다음 슬라이드
const nextIndex = (currentSlide + 1) % $slides.length;
showSlide(nextIndex);
}
}
});
// 기존 클릭 이벤트 (터치와 충돌하지 않도록 수정)
$('.gameIntroPaging').on('click', function(e) {
// 스와이프가 발생했으면 클릭 이벤트 무시
if (isSwipe) {
isSwipe = false;
return;
}
// 다음 슬라이드로 순환 이동 (마지막 슬라이드에서는 처음으로)
showSlide((currentSlide + 1) % $slides.length);
});
// 페이징 도트 클릭 시 해당 슬라이드로 직접 이동
$paging.on('click', 'span', function() {
showSlide($(this).index());
});
/**
* 특정 슬라이드 표시 함수
* 지정된 인덱스의 슬라이드를 표시하고 페이징 상태 업데이트
* @param {number} index - 표시할 슬라이드 인덱스
*/
function showSlide(index) {
// 모든 슬라이드 숨김 후 해당 인덱스의 슬라이드만 표시
$slides.hide().eq(index).show();
// 페이징 도트 상태 업데이트 - 현재 슬라이드에 해당하는 도트만 활성화
$paging.find('span').removeClass('active').eq(index).addClass('active');
// 현재 슬라이드 인덱스 업데이트
currentSlide = index;
}
// 시작하기 버튼 클릭 이벤트 - 인트로 종료 및 메인 컨텐츠 표시
$('#goCapsuleMaking').on('click', function() {
// 인트로 모달 닫기
MicroModal.close('intro-modal');
// 메인 컨텐츠 표시
$('.content-wrap').show();
});
// 인트로 모달 표시 - 페이지 로드 시 자동 표시
MicroModal.show('intro-modal', {
disableScroll: true, // 모달 표시 시 배경 스크롤 방지
awaitCloseAnimation: true // 닫기 애니메이션 완료 대기
});
}
/**
* 제품 선택 이벤트 설정함수
* 라디오 버튼으로 제품 선택 시 시작 버튼 활성화
*/
function bindProductSelection() {
// 제품 라디오 버튼 변경 시 시작 버튼 활성화
$('input[name="gameproduct"]').on('change', function() {
$('.bottomBtnControls .btn').prop('disabled', false);
});
}
/**
* 시작하기 버튼 이벤트 설정 함수
* 제품 선택 후 게임 시작 버튼 클릭 시 확인 모달 표시 및 게임 생성 요청
*/
function bindStartButton() {
// 초기에 시작 버튼 비활성화 상태로 설정하고 클릭 이벤트 바인딩
$('.bottomBtnControls .btn').prop('disabled', true).on('click', function() {
// 선택된 제품 라디오 버튼 확인
const selectedProduct = $('input[name="gameproduct"]:checked');
// 제품이 선택된 경우에만 처리
if (selectedProduct.length > 0) {
// 선택된 제품명을 확인 모달에 표시
const productName = selectedProduct.closest('.gameProduct').find('.gpTit .tit').text();
$('#selectedProductName').text(productName);
// 확인 모달 표시 - 사용자에게 최종 확인 요청
MicroModal.show('modal-1', {
disableScroll: true,
awaitCloseAnimation: true
});
// 모달의 '예' 버튼 클릭 이벤트 - 게임 생성 요청 처리
$('.microModal__btn-primary').off('click').on('click', function() {
// 선택된 제품의 혜택 고유번호 가져오기
const bnfInnb = selectedProduct.val();
// AJAX 대신 폼 제출 방식으로 서버에 게임 생성 요청
const form = $('<form>', {
'method': 'post',
'action': '/sopmal/bnft/createGame'
}).append($('<input>', {
'type': 'hidden',
'name': 'bnfInnb',
'value': bnfInnb
}));
const sopmalCd = $('#sopmalCd').val();
form.append($('<input>', {
'type': 'hidden',
'name': 'sopmalCd',
'value': sopmalCd
}));
// 동적으로 생성한 폼을 body에 추가하고 제출
$('body').append(form);
form.submit();
});
}
});
}

View File

@@ -0,0 +1,174 @@
/**
* CpslmknCollectList.js
*
* 캡슐 메이킹 게임의 특별 이벤트 슬라이더 기능 구현
* 사용자에게 특별 이벤트 정보를 슬라이드 형식으로 제공
*/
/**
* 특별 이벤트 슬라이더 초기화 및 동작 구현 함수
* 슬라이드 자동 전환, 페이지네이션, 터치 이벤트 등 모든 슬라이더 관련 기능 설정
*/
function initializeSpecialEventSlider() {
// 주요 변수 초기화
const $slides = $('.specialEventSlider .innerIcon'); // 슬라이드 요소들(각 이벤트 아이콘)
const slideCount = $slides.length; // 전체 슬라이드 개수
let currentIndex = 0; // 현재 표시 중인 슬라이드 인덱스
let interval; // 자동 슬라이드 전환용 타이머 ID
// 슬라이드가 1개 이하인 경우 첫 번째 슬라이드만 표시하고 함수 종료
if (slideCount <= 1) {
if (slideCount === 1) {
$slides.eq(0).show(); // 첫 번째 슬라이드만 표시
}
return; // 추가 기능 없이 함수 종료
}
/**
* 특정 인덱스의 슬라이드 표시 함수
* 지정된 인덱스의 슬라이드만 표시하고 페이지네이션 상태 업데이트
* @param {number} index - 표시할 슬라이드 인덱스
*/
function showSlide(index) {
$slides.hide(); // 모든 슬라이드 숨김
$slides.eq(index).fadeIn(); // 선택한 슬라이드만 페이드인 효과로 표시
updatePagination(index); // 페이지네이션 업데이트
}
/**
* 페이지네이션 버튼 생성 함수
* 슬라이드 개수만큼 페이지네이션 버튼을 동적으로 생성
*/
function createPagination() {
const $pagination = $('.pagination'); // 페이지네이션 컨테이너
$pagination.empty(); // 기존 버튼 제거
// 슬라이드가 1개 이하인 경우 페이지네이션 숨김
if (slideCount <= 1) {
$pagination.hide();
return;
}
// 슬라이드 개수만큼 버튼 생성
for (let i = 0; i < slideCount; i++) {
$pagination.append(`<button data-index="${i}"></button>`); // 각 버튼에 인덱스 데이터 속성 추가
}
$pagination.show(); // 페이지네이션 표시
updatePagination(0); // 초기 페이지네이션 상태 설정 (첫 번째 버튼 활성화)
}
/**
* 페이지네이션 상태 업데이트 함수
* 현재 표시 중인 슬라이드에 해당하는 페이지네이션 버튼만 활성화
* @param {number} activeIndex - 활성화할 페이지네이션 버튼 인덱스
*/
function updatePagination(activeIndex) {
$('.pagination button').removeClass('active'); // 모든 버튼에서 활성 클래스 제거
$('.pagination button').eq(activeIndex).addClass('active'); // 현재 슬라이드 인덱스에 해당하는 버튼만 활성화
}
/**
* 다음 슬라이드 표시 함수
* 현재 슬라이드의 다음 슬라이드로 이동 (마지막 슬라이드에서는 처음으로 순환)
*/
function nextSlide() {
// 순환 구조로 인덱스 계산 (마지막 슬라이드에서 다음은 첫 번째)
currentIndex = (currentIndex + 1) % slideCount;
showSlide(currentIndex); // 계산된 인덱스의 슬라이드 표시
}
/**
* 이전 슬라이드 표시 함수
* 현재 슬라이드의 이전 슬라이드로 이동 (첫 번째 슬라이드에서는 마지막으로 순환)
*/
function previousSlide() {
// 순환 구조로 인덱스 계산 (첫 번째 슬라이드에서 이전은 마지막)
// slideCount를 더해 음수가 되는 것 방지
currentIndex = (currentIndex - 1 + slideCount) % slideCount;
showSlide(currentIndex); // 계산된 인덱스의 슬라이드 표시
}
/**
* 자동 슬라이드 시작 함수
* 일정 시간 간격으로 다음 슬라이드로 자동 전환
*/
function startSliding() {
interval = setInterval(nextSlide, 3000); // 3초마다 다음 슬라이드로 이동
}
/**
* 자동 슬라이드 정지 함수
* 자동 슬라이드 타이머 정지
*/
function stopSliding() {
clearInterval(interval); // 설정된 타이머 중지
}
// 슬라이더 초기화 - 페이지네이션 생성, 첫 슬라이드 표시, 자동 슬라이딩 시작
createPagination(); // 페이지네이션 버튼 생성
showSlide(currentIndex); // 첫 번째 슬라이드 표시
startSliding(); // 자동 슬라이드 시작
// 이벤트 핸들러 설정 - 사용자 상호작용
// 마우스 호버 시 자동 슬라이드 제어 (마우스 오버: 정지, 마우스 아웃: 재시작)
$('.capsuleCollectTop').hover(stopSliding, startSliding);
// 페이지네이션 버튼 클릭 이벤트 - 해당 인덱스 슬라이드로 직접 이동
$('.pagination').on('click', 'button', function() {
currentIndex = $(this).data('index'); // 클릭된 버튼의 인덱스 가져오기
showSlide(currentIndex); // 해당 인덱스 슬라이드 표시
});
// 슬라이더 영역 클릭 시 다음 슬라이드로 이동 (슬라이드가 2개 이상일 때만)
$('.capsuleCollectTop').on('click', function() {
if (slideCount > 1) {
nextSlide(); // 다음 슬라이드 표시
}
});
// 모바일 터치 이벤트 지원 - 스와이프 동작
// 터치 위치 추적 변수
let touchStartX = 0; // 터치 시작 X 좌표
let touchEndX = 0; // 터치 종료 X 좌표
// 터치 시작 위치 기록
$('.capsuleCollectTop').on('touchstart', function(e) {
if (slideCount > 1) {
touchStartX = e.originalEvent.touches[0].clientX; // 첫 번째 터치 포인트의 X 좌표
}
});
// 터치 종료 시 스와이프 처리
$('.capsuleCollectTop').on('touchend', function(e) {
if (slideCount > 1) {
touchEndX = e.originalEvent.changedTouches[0].clientX; // 터치 종료 시점의 X 좌표
handleSwipe(); // 스와이프 처리 함수 호출
}
});
/**
* 스와이프 처리 함수
* 터치 시작과 종료 지점의 차이를 계산하여 스와이프 방향 결정
*/
function handleSwipe() {
// 왼쪽으로 50px 이상 스와이프: 다음 슬라이드
if (touchStartX - touchEndX > 50) {
nextSlide(); // 다음 슬라이드로 이동
}
// 오른쪽으로 50px 이상 스와이프: 이전 슬라이드
else if (touchEndX - touchStartX > 50) {
previousSlide(); // 이전 슬라이드로 이동
}
// 50px 미만의 움직임은 무시 (우연한 터치 방지)
}
}
/**
* 문서 로드 완료 시 실행
* DOM이 완전히 로드된 후 슬라이더 초기화
*/
$(document).ready(function() {
initializeSpecialEventSlider(); // 특별 이벤트 슬라이더 초기화
});

View File

@@ -0,0 +1,818 @@
/**
* CpslmknCreateGame.js
*
* 캡슐 메이킹 게임의 게임 생성 및 진행 기능을 관리하는 모듈
* 사용자가 잎/도토리를 모아 캡슐을 생성하고 단계별로 완성해 나가는 기능 구현
*/
/**
* 문서 로드 완료 시 초기화 및 이벤트 바인딩
* 페이지 로드 시 UI 초기화 및 모든 이벤트 리스너 설정
*/
$(document).ready(function() {
// MicroModal 기본 초기화 (여기서 한 번만 수행)
MicroModal.init({
disableScroll: true,
awaitCloseAnimation: true
// onShow, onClose 등 콜백은 여기서 정의 X
});
// 기존 헤더 버튼 비활성화 및 새 버튼 추가
initializeHeaderButtons();
// 버블 효과 생성
// 화면에 움직이는 버블 애니메이션 요소를 동적으로 추가
createBubbles();
// 플라스크 애니메이션 적용
// 각 플라스크 요소에 애니메이션 효과 적용, 각 요소마다 다른 시간 간격으로 이미지 전환
$('.flask1, .flask2, .flask3, .flask4, .flask5, .line1, .line2, .line3').each(function(index) {
animateFlask($(this), [300, 1000, 200, 300, 500, 500, 300, 300][index]);
});
// 초록잎/도토리 넣기 버튼 이벤트 리스너
$('#cntGameItem').on('click', useLeaf);
// 초록잎/도토리 모으기 버튼 이벤트 리스너
$('.btn.sm.outline.theme2').on('click', goToCollectPage);
// 클립 버튼 이벤트 리스너
$(document).off('click', '#currentProgressButton').on('click', '#currentProgressButton', function() {
const bnfInnb = $('#bnfInnb').val() || $('input[name="bnfInnb"]').val();
if (!bnfInnb) {
console.error('bnfInnb 값을 찾을 수 없습니다.');
alert('게임 정보를 불러올 수 없습니다.');
return;
}
$.ajax({
url: '/sopmal/bnft/capsuleGameInfo',
type: 'POST',
data: {
bnfInnb: bnfInnb
},
dataType: 'json',
success: function(response) {
if (response && response.resBody) {
try {
const result = JSON.parse(response.resBody.result);
console.log("서버에서 받은 원본 데이터:", result); // 디버깅용
// 모달 데이터 구성
// 게이지 바 계산에 필요한 데이터 추가
const modalData = {
progressInfo: {
progrsStep: result.currentProgressStep,
totUseLeafCnt: result.totalUsedLeaf || result.totUseLeafCnt || 0,
requiredLeaf: result.requiredLeaf || 100,
stepProgressPercentage: result.stepProgressPercentage || 0
},
productName: result.rewardProductName,
productDesc: result.rewardProductDesc || result.productDescription || '',
thumbnailPath: result.productImagePath,
cdnurl: $('#cdnurl').val() || '',
sopmalCd: $('#sopmalCd').val(),
importantNotice: {
notice1: result.notice1 || $('#importantNotice').val() || '',
notice2: result.notice2 || '',
notice3: result.notice3 || ''
}
};
console.log("모달에 전달할 데이터:", modalData); // 디버깅용
// 먼저 모달 표시
MicroModal.show('modal-1');
// 그 다음에 모달 내용 업데이트 (모달이 DOM에 추가된 후 업데이트해야 함)
setTimeout(function() {
if (typeof updateModalContent === 'function') {
updateModalContent(modalData);
} else {
console.error('updateModalContent 함수를 찾을 수 없습니다.');
alert('화면을 업데이트 할 수 없습니다.');
}
}, 100);
} catch (e) {
console.error('JSON 파싱 오류:', e, response.resBody.result);
alert('진행 상태 데이터를 처리하는 중 오류가 발생했습니다.');
}
} else {
const errorMessage = (response && response.resBody && response.resBody.message) ? response.resBody.message : '서버 응답 오류 또는 데이터 없음';
console.error('데이터 로드 실패:', errorMessage, response);
alert(`진행 상태를 불러오는데 실패했습니다. (${errorMessage})`);
}
},
error: function(xhr, status, error) {
console.error('AJAX 요청 실패:', status, error);
alert('진행 상태를 불러오는 중 오류가 발생했습니다.');
}
});
});
// 설정 버튼 이벤트 리스너
$('#CapsuleettingsButton').on('click', function(e) {
e.preventDefault();
goToSettingsPage();
});
// "다른 선물로 다시 시작" 버튼 이벤트 리스너 (modal-1 내부에 있음)
// data-micromodal-trigger="modal-2" 속성으로 처리되므로 별도 핸들러 불필요할 수 있음
// 단, CpslmknCreateGameModal.js 에서 #modal-2의 '처음부터 다시시작' 버튼 핸들러는 필요
$(document).on('click', '#modal-2 .microModal__btn:first-child', function() {
// resetCapsule 함수는 CpslmknCreateGameModal.js 에 정의됨
if (typeof resetCapsule === 'function') {
resetCapsule();
} else {
console.error('resetCapsule 함수를 찾을 수 없습니다.');
}
});
// 페이지 로드 시 해시 체크
// URL에 해시가 있으면 해당 ID의 모달 열기
if (window.location.hash) {
var modalId = window.location.hash.replace('#', '');
openModal(modalId);
}
// 페이지 로드 시 초기 상태 설정
// 게임 진행 상태를 서버에서 가져오거나 세션스토리지에서 복원
initializeGameStatus();
// 모달 닫기 버튼 이벤트 리스너 추가
// 모달 닫기 버튼 클릭 시 해당 모달 닫기
$('.microModal__close2').on('click', function() {
const modalId = $(this).closest('.microModal').attr('id');
MicroModal.close(modalId);
});
});
/**
* 버블 애니메이션 요소를 생성하여 화면에 추가하는 함수
* 다양한 크기와 위치, 애니메이션 속도를 가진 버블을 생성
*/
function createBubbles() {
// 버블 설정 배열 - 각 버블의 크기, 위치, 지연시간, 애니메이션 지속시간 정의
const bubbles = [
{ size: 12, left: '20%', bottom: '-10%', delay: 0, duration: 3 },
{ size: 10, left: '60%', bottom: '10%', delay: 0.6, duration: 2.8 },
{ size: 8, left: '30%', bottom: '30%', delay: 0.4, duration: 2.6 },
{ size: 6, left: '70%', bottom: '50%', delay: 1, duration: 2.4 },
{ size: 4, left: '40%', bottom: '70%', delay: 0.8, duration: 2.2 },
{ size: 2, left: '50%', bottom: '90%', delay: 1.2, duration: 2 }
];
// 각 버블 설정에 따라 DOM 요소 생성 및 스타일 적용
$.each(bubbles, function(index, bubble) {
$('<div>')
.addClass('bubble')
.css({
width: bubble.size,
height: bubble.size,
left: bubble.left,
bottom: bubble.bottom,
animationDuration: bubble.duration + 's',
animationDelay: bubble.delay + 's'
})
.appendTo('#bubbleContainer');
});
}
/**
* 플라스크 요소에 애니메이션 효과를 적용하는 함수
* @param {jQuery} $flaskElement - 애니메이션을 적용할 플라스크 요소
* @param {number} interval - 이미지 전환 간격(밀리초)
*/
function animateFlask($flaskElement, interval) {
// 플라스크 내부의 모든 이미지 요소 선택
const $images = $flaskElement.find('.svgContainer img');
let currentIndex = 0;
// 다음 이미지를 표시하는 내부 함수
function showNextImage() {
$images.eq(currentIndex).hide(); // 현재 이미지 숨기기
currentIndex = (currentIndex + 1) % $images.length; // 다음 이미지 인덱스 계산(순환)
$images.eq(currentIndex).show(); // 다음 이미지 표시
}
// 초기 상태 설정: 모든 이미지 숨기고 첫 번째 이미지만 표시
$images.hide().eq(0).show();
// 지정된 간격으로 이미지 전환 타이머 설정
setInterval(showNextImage, interval);
}
/**
* 완료 모달의 진행 상태 표시를 업데이트하는 함수
* @param {number} currentStep - 현재 진행 단계(1~5)
* @param {number} progressPercent - 현재 단계의 진행률(0~100)
*/
function updateCompleteModalProgress(currentStep, progressPercent) {
// 모달 내부 진행바 요소 선택
const $modalGameStatusBar = $('#modal-1 .gameStatusBar');
// 모든 단계 표시에서 .on 클래스 제거 (초기화)
$modalGameStatusBar.find('.step01, .step02, .step03, .step04, .last').removeClass('on');
// 현재 단계까지의 모든 단계에 .on 클래스 부여 (활성화)
// switch문의 fall-through 특성을 활용하여 현재 단계까지 모든 단계 활성화
switch (currentStep) {
case 5:
$modalGameStatusBar.find('.last').addClass('on'); // 5단계
/* falls through */
case 4:
$modalGameStatusBar.find('.step04').addClass('on'); // 4단계
/* falls through */
case 3:
$modalGameStatusBar.find('.step03').addClass('on'); // 3단계
/* falls through */
case 2:
$modalGameStatusBar.find('.step02').addClass('on'); // 2단계
/* falls through */
case 1:
$modalGameStatusBar.find('.step01').addClass('on'); // 1단계
break;
default:
// 정상 단계가 아닐 경우 처리 X
break;
}
// 진행 바의 너비를 진행률에 맞게 조정
$modalGameStatusBar.find('.per').css('width', `${progressPercent}%`);
}
/**
* 게임 상태를 초기화하는 함수
* 새로고침 여부에 관계없이 JSP hidden 값을 사용하여 초기화
*/
function initializeGameStatus() {
// JSP에서 전달받은 hidden 값들로 게임 상태 구성
const gameStatusFromHidden = {
ownedLeafCount: parseInt($('#leafCount').val()) || 0,
currentStep: parseInt($('#currentStep').val()) || parseInt($('#progressStep').val()) || 1,
stepProgressPercentage: parseFloat($('#stepProgressPercentage').val()) || 0,
isStepCompleted: false
};
console.log('Hidden 값으로부터 구성된 게임 상태:', gameStatusFromHidden);
// UI 업데이트 및 세션스토리지에 상태 저장
updateGameUI(gameStatusFromHidden);
sessionStorage.setItem('gameStatus', JSON.stringify(gameStatusFromHidden));
}
/**
* 게임 UI를 업데이트하는 함수
* @param {Object} data - 게임 상태 데이터 객체
* @param {number} data.ownedLeafCount - 보유 아이템 수
* @param {number} data.currentStep - 현재 진행 단계(1~5)
* @param {number} data.stepProgressPercentage - 현재 단계의 진행률(0~100)
* @param {boolean} data.isStepCompleted - 현재 단계 완료 여부
*/
function updateGameUI(data) {
// 데이터 null 체크 - 데이터가 없으면 함수 종료
if (!data) {
console.error('데이터가 없습니다');
return;
}
// 안전한 값 할당 - 기본값 설정으로 undefined/null 방지
const safeData = {
ownedLeafCount: data.ownedLeafCount || 0,
currentStep: data.currentStep || 1,
stepProgressPercentage: data.stepProgressPercentage || 0,
isStepCompleted: data.isStepCompleted || false
};
// 아이템 개수 UI 업데이트 - 아이콘 유지하면서 개수만 변경
const itemIcon = $('#ContingItem i').attr('class') || 'ico_leaf';
$('#ContingItem').html(`<i class="${itemIcon}"></i>${safeData.ownedLeafCount}개`);
// 진행률 표시 업데이트
const displayPercentage = data.stepProgressPercentage;
// 진행 바 너비 및 텍스트 업데이트
$('.percentage-bar').css('width', `${displayPercentage}%`);
$('.per-txt').text(`${Math.floor(displayPercentage)}% 채웠어요.`);
$('.per-flask span').text(`${safeData.currentStep}단계`);
// 플라스크 이미지 클래스 업데이트 - 현재 단계에 맞는 이미지 표시
$('.bar-container')
.removeClass((index, className) => {
// 정규식으로 flask0숫자 패턴의 모든 클래스 찾아 제거
return (className.match(/(^|\s)flask0\d+/g) || []).join(' ');
})
.addClass(`flask0${safeData.currentStep}`);
// 세션스토리지 업데이트 - 최신 상태 저장
sessionStorage.setItem('gameStatus', JSON.stringify(safeData));
// 모달 진행 상황 업데이트
updateCompleteModalProgress(safeData.currentStep, displayPercentage);
// 5단계 완성 여부 확인 및 버튼 비활성화 처리
// 최종 단계(5단계)가 100% 완료되면 버튼 비활성화 및 텍스트 변경
if (safeData.currentStep === 5 && safeData.stepProgressPercentage >= 100) {
// 버튼 비활성화
$('#cntGameItem')
.prop('disabled', true)
.addClass('disabled')
.text('캡슐 제작 완료!');
// 자동으로 완료 모달 표시 (기존 코드 수정)
setTimeout(() => {
// 현재 혜택 정보로 모달 데이터 구성
const bnfInnb = $('#bnfInnb').val() || $('input[name="bnfInnb"]').val();
if (!bnfInnb) {
console.error('bnfInnb 값을 찾을 수 없습니다.');
return;
}
// 서버에서 최신 완료 정보 가져와서 모달 표시
$.ajax({
url: '/sopmal/bnft/capsuleGameInfo',
type: 'POST',
data: {
bnfInnb: bnfInnb
},
dataType: 'json',
success: function(response) {
if (response && response.resBody) {
try {
const result = JSON.parse(response.resBody.result);
// 완료 상태 모달 데이터 구성
const modalData = {
progressInfo: {
progrsStep: 5, // 완료 단계
totUseLeafCnt: result.totalUsedLeaf || result.totUseLeafCnt || 0,
requiredLeaf: result.requiredLeaf || 100,
stepProgressPercentage: 100 // 완료 상태
},
productName: result.rewardProductName,
productDesc: result.rewardProductDesc || result.productDescription || '',
thumbnailPath: result.productImagePath,
cdnurl: $('#cdnurl').val() || '',
sopmalCd: $('#sopmalCd').val(),
importantNotice: {
notice1: result.notice1 || $('#importantNotice').val() || '',
notice2: result.notice2 || '',
notice3: result.notice3 || ''
}
};
// 완료 모달 자동 표시
MicroModal.show('modal-1');
// 모달 내용 업데이트
setTimeout(function() {
if (typeof updateModalContent === 'function') {
updateModalContent(modalData);
} else {
console.error('updateModalContent 함수를 찾을 수 없습니다.');
}
}, 100);
} catch (e) {
console.error('JSON 파싱 오류:', e);
}
}
},
error: function(xhr, status, error) {
console.error('완료 정보 로드 실패:', error);
}
});
}, 1000); // 1초 후 자동 표시
}
}
/**
* 아이템(잎/도토리)을 사용하여 게임을 진행하는 함수
* 서버에 사용 요청을 보내고 결과에 따라 UI 업데이트
*/
function useLeaf() {
// 현재 보유한 잎 개수 확인 - 텍스트에서 숫자만 추출
const currentLeafCount = parseInt($('#ContingItem').text().replace(/[^0-9]/g, ''));
// 잎이 없는 경우 토스트 메시지 표시 후 함수 종료
if (currentLeafCount <= 0) {
// 수정: CSS .show 클래스를 이용한 토스트 표시
$('#acorn_toast').addClass('show');
setTimeout(() => {
$('#acorn_toast').removeClass('show');
}, 2000);
return;
}
// 사용할 잎 개수 계산 (10개 이상이면 10개, 미만이면 1개)
// 한 번에 최대 10개까지 사용 가능
const useLeafCnt = currentLeafCount >= 10 ? 10 : 1;
// 서버 요청 데이터 구성
const requestData = {
bnfInnb: $('#bnfInnb').val(), // 혜택 고유번호
bnfSttusInnb: $('#bnfSttusInnb').val(), // 혜택 상태 고유번호
useLeafCnt: useLeafCnt // 사용할 아이템 개수
};
// 서버에 아이템 사용 요청
$.ajax({
url: '/sopmal/bnft/useLeaf',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(requestData),
success: function(response) {
// 서버 응답 데이터 파싱
const resultData = JSON.parse(response.resBody.result);
// 초록잎 부족 오류 처리
if (resultData.leafUsageSuccess === false) {
if (resultData.errorCode === 'INSUFFICIENT_LEAF') {
// 초록잎 부족 시 보유량 업데이트 및 알림 표시
const ownedLeafCount = resultData.ownedLeafCount || 0;
// UI에 정확한 보유량 반영
const itemIcon = $('#ContingItem i').attr('class') || 'ico_leaf';
$('#ContingItem').html(`<i class="${itemIcon}"></i>${ownedLeafCount}개`);
// 수정: CSS .show 클래스를 이용한 토스트 표시
$('#acorn_toast').addClass('show');
setTimeout(() => {
$('#acorn_toast').removeClass('show');
}, 2000);
} else {
// 기타 오류 처리
const errorMessage = resultData.errorMessage || '잎 사용에 실패했습니다.';
showCustomAlert(errorMessage);
}
return;
}
// 수정: 성공 조건을 gameCompleted 또는 currentStep으로 변경
if (resultData.gameCompleted || resultData.currentStep) {
// 현재 단계 데이터 구성
let currentStepData = {
...resultData,
currentStep: resultData.currentStep,
ownedLeafCount: resultData.ownedLeafCount // 서버에서 받은 최신 보유 개수 사용
};
// 수정: 게임 완료 조건 체크
if (resultData.gameCompleted || (resultData.currentStep === 5 && resultData.stepProgressPercentage >= 100)) {
// 게임 완료 시 데이터 업데이트
currentStepData.isStepCompleted = true;
currentStepData.stepProgressPercentage = 100;
currentStepData.currentStep = 5; // 최종 단계로 설정
// UI 업데이트
updateGameUI(currentStepData);
// 수정: 완료 모달 표시 로직 개선
setTimeout(() => {
const bnfInnb = $('#bnfInnb').val() || $('input[name="bnfInnb"]').val();
if (!bnfInnb) {
console.error('bnfInnb 값을 찾을 수 없습니다.');
return;
}
// 완료 상태 모달 데이터 구성 (서버에서 받은 데이터 사용)
const modalData = {
progressInfo: {
progrsStep: 5, // 완료 단계
totUseLeafCnt: resultData.totalLeafUsed || 0,
requiredLeaf: resultData.requiredLeaf || resultData.totalLeafNeeded || 1000,
stepProgressPercentage: 100 // 완료 상태
},
productName: resultData.productName,
productDesc: resultData.productDesc,
thumbnailPath: resultData.imageUrl,
cdnurl: $('#cdnurl').val() || '',
sopmalCd: $('#sopmalCd').val(),
importantNotice: {
notice1: resultData.notice || $('#importantNotice').val() || '',
notice2: '',
notice3: ''
}
};
// 완료 모달 자동 표시
MicroModal.show('modal-1');
// 모달 내용 업데이트
setTimeout(function() {
if (typeof updateModalContent === 'function') {
updateModalContent(modalData);
} else {
console.error('updateModalContent 함수를 찾을 수 없습니다.');
}
}, 100);
}, 1000); // 1초 후 자동 표시
return;
}
// 단계 완료 여부 확인 및 처리 (5단계 미만인 경우)
if (resultData.stepProgressPercentage >= 100) {
// 단계 완료 시 데이터 업데이트
currentStepData.isStepCompleted = true;
currentStepData.stepProgressPercentage = 100;
// UI 업데이트
updateGameUI(currentStepData);
// 중간 단계 완료 모달 처리
const stepNames = ['첫번째', '두번째', '세번째', '네번째', '다섯번째'];
const currentStepName = stepNames[currentStepData.currentStep - 1];
// 완료 메시지 업데이트
$('#completionStepText').text(`${currentStepName} 유리병이 가득 찼어요!`);
$('#completionFlask')
.removeClass()
.addClass(`ico_flask flask0${currentStepData.currentStep}`)
.text(`${currentStepName} 유리병`);
// 남은 단계 수 계산 및 표시
const remainingFlasks = 5 - currentStepData.currentStep;
$('#remainingFlasks').text(remainingFlasks);
// 모달 표시 전에 기존 이벤트 핸들러 제거 (중복 방지)
$('#modal-2 .microModal__btn-primary').off('click');
// 단계 완료 모달 표시
MicroModal.show('modal-2');
// 단계 완료 모달의 "계속하기" 버튼에만 다음 단계 진행 이벤트 바인딩
$('#modal-2 .microModal__btn-primary').one('click', function() {
// 모달 닫기
MicroModal.close('modal-2');
if (!resultData.gameCompleted) {
// 모달이 완전히 닫힌 후에 다음 단계로 진행
setTimeout(() => {
// 다음 단계 데이터 구성
const nextStepData = {
...currentStepData,
currentStep: currentStepData.currentStep + 1,
stepProgressPercentage: 0,
isStepCompleted: false
};
// UI 업데이트
updateGameUI(nextStepData);
}, 300);
}
});
// "다시 시작" 버튼은 별도로 처리 (첫 번째 버튼)
$('#modal-2 .microModal__btn:first-child').one('click', function() {
// 다시 시작 확인 모달 표시
showRestartConfirmModal();
});
} else {
// 단계가 완료되지 않은 경우 일반 UI 업데이트
updateGameUI(currentStepData);
}
} else {
// 아이템 사용 실패 시 알림 표시
const errorMessage = resultData.errorMessage || '잎 사용에 실패했습니다. 다시 시도해주세요.';
showCustomAlert(errorMessage);
}
},
error: function(xhr, status, error) {
// 서버 통신 오류 시 알림 표시
showCustomAlert('서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
}
});
}
/**
* 아이템 수집 페이지로 이동하는 함수
* 현재 게임 상태를 세션스토리지에 저장 후 이동
*/
function goToCollectPage() {
// 현재 진행 상태 저장 - 수집 페이지에서 돌아올 때 복원하기 위함
const currentGameStatus = sessionStorage.getItem('gameStatus');
if (currentGameStatus) {
sessionStorage.setItem('previousGameStatus', currentGameStatus);
}
// 모으기 페이지로 이동
window.location.href = '/sopmal/bnft/cpslmknCollectList';
}
// 닫기 버튼 이벤트 리스너
// 모달 닫기 버튼 클릭 시 해당 모달 닫기
$('.microModal .microModal__close2').on('click', function () {
var modalId = $(this).closest('.microModal').attr('id');
closeModal(modalId);
});
// 뒤로가기 처리
// 브라우저 뒤로가기 버튼 클릭 시 모달 닫기 처리
window.addEventListener('popstate', function (event) {
var modalId = window.location.hash.replace('#', '');
if (modalId) {
closeModal(modalId);
} else {
// 해시가 없으면 모든 모달 닫기
$('.microModal').each(function () {
MicroModal.close($(this).attr('id'));
});
}
// 본문 스크롤 복원
document.body.style.overflow = '';
});
/**
* 헤더 버튼을 초기화하는 함수
* 기존 헤더의 검색/장바구니 버튼을 제거하고 게임용 버튼(진행상태/설정)으로 대체
*/
function initializeHeaderButtons() {
// 기존 헤더 버튼 중 검색 및 장바구니 아이콘 삭제
$('.header__btn').empty();
// 새로운 버튼 추가
const $headerBtns = $('.header__btn').parent();
const newButtons = `
<button class="header__btn" id="currentProgressButton">
<i class="icon icon_nav-capsule"></i>
<span class="visually-hidden">진행상태보기</span>
</button>
<button class="header__btn" id="CapsuleettingsButton">
<i class="icon icon_nav-set"></i>
<span class="visually-hidden">설정</span>
</button>
`;
$headerBtns.append(newButtons);
}
/**
* 설정 페이지로 이동하는 함수
* 현재 혜택 내부번호를 유지하며 설정 페이지로 이동
*/
function goToSettingsPage() {
const bnfInnb = $('#bnfInnb').val(); // 혜택 내부번호 가져오기
const sopmalCd = $('#sopmalCd').val(); // 쇼핑몰 코드 가져오기
window.location.href = `/sopmal/bnft/settings?bnfInnb=${bnfInnb}`; // 설정 페이지로 이동
}
/**
* 진행 상태 바 생성 함수
* 게임 진행 단계와 진행률을 시각적으로 표시하는 상태 바 생성
* @param {number} progressStep - 현재 진행 단계 (1~5)
* @param {number} progressPercent - 현재 진행률 (0~100)
* @returns {jQuery} 생성된 상태 바 jQuery 객체
*/
function createStatusBar(progressStep, progressPercent) {
// 진행률을 정수로 변환하고 소수점 제거
const displayPercent = Math.floor(progressPercent);
// 상태 바 DOM 구조 생성 및 반환
return $('<div>').addClass('gameStatusBarWrap')
.append($('<div>').addClass('gameStatusBar')
.append($('<div>').addClass('per').css('width', `${displayPercent}%`)) // 진행률 표시 바
.append($('<span>').addClass('first').text('시작')) // 시작 지점
.append(createStepSpans(progressStep)) // 중간 단계 표시
.append($('<span>').addClass(`last ${progressStep >= 5 ? 'on' : ''}`).text('5단계'))); // 마지막 단계
}
/**
* 단계 표시 span 생성 함수
* 진행 상태 바에 표시할 각 단계별 표시 요소 생성
* @param {number} currentStep - 현재 진행 단계 (1~5)
* @returns {Array} 생성된 단계 표시 span jQuery 객체 배열
*/
function createStepSpans(currentStep) {
const spans = [];
// 1~4단계 표시 생성 (5단계는 별도로 'last' 클래스로 처리)
for (let i = 1; i <= 4; i++) {
spans.push(
$('<span>')
.addClass(`step0${i} ${currentStep >= i ? 'on' : ''}`) // 현재 단계 이하는 'on' 클래스 추가
.text(`${i}단계`)
);
}
return spans;
}
/**
* 다른선물로 다시 시작 확인 모달을 표시하는 함수
* 모달을 표시하고 버튼에 적절한 이벤트 리스너 연결
*/
function showRestartConfirmModal() {
// 기존 이벤트 리스너 제거 (중복 방지)
$('.microModal__btn').off('click');
// 모달 표시
MicroModal.show('modal-2', {
onShow: function() {
// URL 해시 추가 (브라우저 뒤로가기 처리용)
if (window.location.hash !== '#modal-2') {
window.history.pushState({ modal: 'open' }, '', '#modal-2');
}
}
});
// "처음부터 다시시작" 버튼에 이벤트 리스너 연결
$('#modal-2 .microModal__btn').first().on('click', function() {
console.log("처음부터 다시시작 버튼 클릭됨");
MicroModal.close('modal-2');
// 사용자 선택 확정을 위한 두 번째 확인 모달 표시
showFinalConfirmModal();
});
// "그대로 계속할게요" 버튼에 이벤트 리스너 연결
$('#modal-2 .microModal__btn-primary').on('click', function() {
console.log("그대로 계속하기 버튼 클릭됨");
MicroModal.close('modal-2');
// URL 해시 제거
window.history.replaceState({}, '', window.location.pathname);
});
}
/**
* 최종 확인 모달을 표시하는 함수
* 정말로 다시 시작할지 확인하는 두 번째 모달 표시
*/
function showFinalConfirmModal() {
// 최종 확인 모달이 없으면 동적으로 생성
if ($('#modal-3').length === 0) {
$('body').append(`
<div class="microModal microAlert" id="modal-3" aria-hidden="true">
<div class="microModal__overlay" tabindex="-1">
<div class="microModal__container" role="dialog" aria-modal="true">
<div class="microModal__body">
<div class="microModal__top">
<h2 class="microModal__title">
정말 처음부터 다시 시작하시겠습니까?
</h2>
</div>
<div class="microModal__content">
<p>
지금까지 진행한 내용이 모두 삭제됩니다.
</p>
</div>
</div>
<div class="microModal__btnContainer">
<button class="microModal__btn" data-micromodal-close aria-label="창 닫기">취소</button>
<button class="microModal__btn microModal__btn-primary" id="restartFromBeginning">다시 시작하기</button>
</div>
</div>
</div>
</div>
`);
}
// 모달 표시
MicroModal.show('modal-3', {
onShow: function() {
// URL 해시 추가
window.history.pushState({ modal: 'open' }, '', '#modal-3');
}
});
// 이벤트 리스너가 중복되지 않도록 먼저 제거
$('#restartFromBeginning').off('click');
// "다시 시작하기" 버튼에 이벤트 리스너 연결
$('#restartFromBeginning').on('click', function() {
console.log("최종 다시 시작하기 버튼 클릭됨");
MicroModal.close('modal-3');
// 캡슐 선택 페이지로 이동
restartCapsule();
});
// "취소" 버튼에 이벤트 리스너 연결
$('#modal-3 .microModal__btn').first().on('click', function() {
MicroModal.close('modal-3');
// URL 해시 제거
window.history.replaceState({}, '', window.location.pathname);
});
}
/**
* 캡슐 제작을 처음부터 다시 시작하는 함수
* 서버에 재시작 요청을 보내고 캡슐 선택 페이지로 이동
*/
function restartCapsule() {
// 현재 혜택 고유번호 가져오기
const bnfInnb = $('#bnfInnb').val();
// 서버에 재시작 요청 보내기
$.ajax({
url: '/sopmal/bnft/resetCapsule',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ bnfInnb: bnfInnb }),
success: function(response) {
// 성공 시 캡슐 선택 페이지로 이동
window.location.href = '/sopmal/bnft/selectCapsule';
},
error: function(xhr, status, error) {
// 오류 발생 시 토스트 메시지 표시
console.error('재시작 요청 실패:', error);
alert('재시작 요청 중 오류가 발생했습니다. 다시 시도해주세요.');
}
});
}

View File

@@ -0,0 +1,411 @@
// 쿠폰함 페이지로 이동하는 함수
function goToCouponPage() {
// 쿠폰함 페이지 URL로 이동
window.location.href = '/sopmal/coupon/list';
}
// 새로운 캡슐 만들기 시작하는 함수
function startNewCapsule() {
// 제품 선택 페이지로 이동
window.location.href = '/sopmal/bnft/selectCapsule';
}
// 다른 선물로 다시 시작하는 함수 (리셋 확인 모달에서 호출)
function resetCapsule() {
// Ajax 요청으로 현재 진행 상태 초기화
$.ajax({
url: '/sopmal/bnft/resetCapsule',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: $('#bnfInnb').val(),
bnfSttusInnb: $('#bnfSttusInnb').val()
}),
success: function(response) {
// 응답 구조 변경에 맞게 처리
const result = JSON.parse(response.resBody.result);
if (result.success) {
// 제품 선택 페이지로 이동
window.location.href = '/sopmal/bnft/selectCapsule';
} else {
alert('초기화 중 오류가 발생했습니다. 다시 시도해주세요.');
}
},
error: function() {
alert('서버 오류가 발생했습니다. 다시 시도해주세요.');
}
});
}
// 모달 초기화 함수
function initModals() {
// modal-2(다시 시작 확인) 버튼 이벤트 등록
$('#modal-2 .microModal__btn:first-child').on('click', function() {
resetCapsule();
});
// 애니메이션 효과 초기화
// initCompletionAnimation();
}
// 모달 데이터 로드 및 초기화
$(document).ready(function() {
// 모달 초기화: onShow 콜백과 다른 옵션들을 함께 설정
MicroModal.init({
disableScroll: true,
awaitCloseAnimation: true,
onShow: function(modal) {
console.log(`Modal shown: ${modal.id}`);
if (modal.id === 'modal-1') {
console.log('Loading data for modal-1...');
loadModalData();
}
}
});
// 🔥 문제 부분: 이 코드를 제거하거나 수정해야 함
// 모달2(다시 시작 확인) 버튼 이벤트 등록을 제거
// $(document).off('click', '#modal-2 .microModal__btn:first-child').on('click', '#modal-2 .microModal__btn:first-child', function() {
// resetCapsule();
// });
// 대신 "다른선물로 다시 시작" 버튼에만 resetCapsule을 바인딩
$(document).off('click', '#restartGameButton').on('click', '#restartGameButton', function(e) {
e.preventDefault();
showRestartConfirmModal();
});
});
// 모달 데이터 로드 함수
function loadModalData() {
const bnfInnb = $('#bnfInnb').val() || $('input[name="bnfInnb"]').val();
if (!bnfInnb) {
console.error('bnfInnb 값을 찾을 수 없습니다.');
return;
}
// AJAX로 최신 진행 상태 데이터 요청
$.ajax({
url: '/sopmal/bnft/getProgressStatus',
type: 'GET',
data: {
bnfInnb: bnfInnb
},
success: function(response) {
if (response.resultCode === '0000') {
// 응답 데이터로 모달 내용 업데이트
updateModalContent(response.content);
} else {
console.error('데이터 로드 실패:', response.message);
}
},
error: function(xhr, status, error) {
console.error('AJAX 요청 실패:', error);
}
});
}
// 모달 내용 업데이트 함수
function updateModalContent(data) {
console.log("updateModalContent 호출됨:", data); // 디버깅용
const progressInfo = data.progressInfo;
if (!progressInfo) {
console.error("progressInfo가 없습니다:", data);
return;
}
// progressStep이 유효한지 확인
if (!progressInfo.progrsStep || isNaN(parseInt(progressInfo.progrsStep))) {
console.error("유효하지 않은 progrsStep:", progressInfo.progrsStep);
progressInfo.progrsStep = 1; // 기본값 설정
} else {
// 숫자로 변환
progressInfo.progrsStep = parseInt(progressInfo.progrsStep);
}
console.log("현재 단계:", progressInfo.progrsStep);
// 쇼핑몰 코드에 따른 텍스트 설정
const brandName = (data.sopmalCd === 'NC') ? '뉴트리' : '닥터린';
// 모달 컨테이너 참조
const $modalContent = $('#modal-1 .microModal__content');
if ($modalContent.length === 0) {
console.error("모달 컨테이너를 찾을 수 없습니다");
return;
}
// 모달 컨텐츠 초기화
$modalContent.empty();
// 게임 상태 요소 생성
const $gameStatus = $('<div class="gameStatus"></div>');
$modalContent.append($gameStatus);
// 진행 상태에 따라 다른 UI 표시
// 수정: 5단계 완성 여부를 stepProgressPercentage로 판단
const isGameCompleted = progressInfo.progrsStep === 5 && progressInfo.stepProgressPercentage >= 100;
if (!isGameCompleted) {
// 진행 중 UI (5단계 진행 중 포함)
$gameStatus.append(`<p class="tit">${brandName} 캡슐 만들기<br><b>${progressInfo.progrsStep}단계</b> 진행중!!</p>`);
// 진행 바 추가 - 실제 진행률 전달
$gameStatus.append(createStatusBar(progressInfo.progrsStep, progressInfo.stepProgressPercentage || 0));
// 선물 정보 추가
const $gameGift = $('<div class="gameGift"></div>');
$gameGift.append(`<p class="tit">${brandName} 캡슐을 완성하면 받는 선물이예요.</p>`);
const $gameGiftInbox = $('<div class="gameGiftInbox"></div>');
$gameGiftInbox.append(`<p class="img"><img src="${data.cdnurl}${data.thumbnailPath}" alt="상품 이미지"></p>`);
const $txtDiv = $('<div class="txt"></div>');
$txtDiv.append(`<p class="txt1">${data.productDesc || ''}</p>`);
$txtDiv.append(`<p class="txt2">${data.productName || ''}</p>`);
$gameGiftInbox.append($txtDiv);
$gameGift.append($gameGiftInbox);
// 다른 선물로 다시 시작 버튼 추가
$gameGift.append(`<div class="alignR">
<a href="#" class="btn giftReset" id="restartGameButton">
<i class="ico_giftReset"></i>다른선물로 다시 시작
</a>
</div>`);
$gameStatus.append($gameGift);
} else {
// 완료 상태 UI 구성 (5단계 100% 완성 시에만)
$gameStatus.append(`
<p class="tit">${brandName} 캡슐 만들기 최종 완료!</p>
<p class="tit2">진짜 영양제 선물받고<br>새로운 캡슐 만들기 시작하세요.</p>
`);
// 진행 바 추가 - 5단계 완료 (100% 전달)
$gameStatus.append(createStatusBar(5, 100));
// 쿠폰 영역 추가
const $gameCoupon = $('<div class="gameCoupon"></div>');
$gameCoupon.append(`
<p class="tit">
<a href="javascript:goToCouponPage()"><i class="ico_present"></i>쿠폰함에 교환권이 전달되었습니다.</a>
</p>
<div class="gameCouponInbox">
<div class="barcode">
<img src="/mobile/images/bnft/ico_barcode.svg" width="80" height="80" alt="바코드">
</div>
<p class="img"><img src="${data.cdnurl}${data.thumbnailPath}" alt="상품 이미지"></p>
<div class="txt">
<p class="txt1">${data.productName || ''}</p>
<p class="txt2">${data.productDesc || ''}</p>
</div>
</div>
`);
$gameStatus.append($gameCoupon);
// 새로운 캡슐 만들기 시작하기 버튼 추가
$gameStatus.append(`
<div class="btnBox alignC" style="margin-top: 20px;">
<button type="button" class="btn lg theme newStartButton" onclick="startNewCapsule()">
새로운 캡슐 만들기 시작하기
</button>
</div>
`);
}
// 알림 박스 추가 - notice2 사용 (디버깅 로그 추가)
console.log("importantNotice 데이터:", data.importantNotice);
console.log("notice2 내용:", data.importantNotice?.notice2);
const $commentBox = $('<div class="comment-box"></div>');
const notice2Content = data.importantNotice?.notice2?.replace(/<[^>]*>/g, '') || '';
$commentBox.html(`
<p style="font-size:14px; font-weight:bold; margin-bottom:5px;">참여방법안내</p>
<p style="color:rgba(0,0,0,0.6); font-size:14px;">${notice2Content}</p>
`);
// CSS 문제 해결을 위한 강제 스타일 적용
$commentBox.css({
'background': '#fff',
'padding': '16px',
'padding-bottom': '16px', // 정상적인 패딩으로 수정
'margin-bottom': '0', // 음수 마진 제거
'min-height': 'auto',
'display': 'block'
});
$modalContent.append($commentBox);
// 렌더링 후 확인용 로그
console.log("comment-box 렌더링 완료:", $commentBox);
}
// 진행 상태 바 생성 함수 - 단계별로 색상 변경
function createStatusBar(progressStep, progressPercent) {
const $statusBarWrap = $('<div class="gameStatusBarWrap"></div>');
const $statusBar = $('<div class="gameStatusBar"></div>');
// 디버깅 로그 추가
console.log(`createStatusBar 호출: progressStep=${progressStep}, progressPercent=${progressPercent}`);
// 수정: 게이지 바 너비 계산 (6개 구간으로 나누기)
// 시작(0%) → 1단계(16.67%) → 2단계(33.33%) → 3단계(50%) → 4단계(66.67%) → 5단계(83.33%) → 완료(100%)
let widthPercent;
if (progressStep < 5) {
// 1~4단계: 각 단계당 16.67%씩 할당
widthPercent = progressStep * 16.67;
} else if (progressStep === 5) {
// 5단계: stepProgressPercentage에 따라 83.33%~100% 사이
const basePercent = 83.33; // 5단계 시작점
const additionalPercent = (progressPercent || 0) * 0.1667; // 5단계 내 진행률 (최대 16.67%)
widthPercent = basePercent + additionalPercent;
} else {
// 완료 상태
widthPercent = 100;
}
console.log(`게이지 바 너비: ${widthPercent}%`);
// 진행률 표시 바 - 단계에 맞게 너비 조정
$statusBar.append(`<div class="per" style="width:${widthPercent}%;"></div>`);
// 시작 지점
$statusBar.append('<span class="first">시작</span>');
// 각 단계 표시
for (let i = 1; i <= 4; i++) {
// 현재 단계 이상인 경우 'on' 클래스 추가
const isActive = progressStep >= i;
console.log(`단계 ${i}: ${isActive ? '활성화' : '비활성화'}`);
$statusBar.append(`<span class="step0${i} ${isActive ? 'on' : ''}">${i}단계</span>`);
}
// 마지막 단계 - 5단계 완료(100%)일 때만 활성화
const isLastActive = progressStep === 5 && (progressPercent >= 100);
console.log(`최종 단계: ${isLastActive ? '활성화' : '비활성화'}`);
$statusBar.append(`<span class="last ${isLastActive ? 'on' : ''}">5단계</span>`);
$statusBarWrap.append($statusBar);
return $statusBarWrap;
}
// 다른선물로 다시 시작 확인 모달을 표시하는 함수
function showRestartConfirmModal() {
console.log("다시 시작 확인 모달 표시 시도");
// 모달 HTML 정의
const restartModalHtml = `
<div class="microModal microAlert" id="restart-modal" aria-hidden="true">
<div class="microModal__overlay" tabindex="-1">
<div class="microModal__container" role="dialog" aria-modal="true">
<div class="microModal__body">
<div class="microModal__top">
<h2 class="microModal__title">
새로운 선물로 변경하여<br>만들기를 처음부터 다시 시작할까요?
</h2>
</div>
<div class="microModal__content">
<p>
현재 보유중인 초록잎은 유지되지만<br>
진행한 내용은 모두 삭제되며<br>
다시 복구할 수 없습니다.
</p>
</div>
</div>
<div class="microModal__btnContainer">
<button class="microModal__btn" id="restartConfirmBtn">처음부터 다시시작</button>
<button class="microModal__btn microModal__btn-primary" id="restartCancelBtn">그대로 계속할게요</button>
</div>
</div>
</div>
</div>
`;
// 이미 존재하는 restart-modal 제거 (중복 방지)
$('#restart-modal').remove();
// 모달 HTML을 body에 추가
$('body').append(restartModalHtml);
// 모달 표시
MicroModal.show('restart-modal', {
onShow: function() {
console.log("다시 시작 확인 모달이 표시됨");
}
});
// 버튼 이벤트 핸들러 연결
$('#restartConfirmBtn').off('click').on('click', function() {
console.log("처음부터 다시시작 버튼 클릭됨");
MicroModal.close('restart-modal');
// 사용자 선택 확정을 위한 두 번째 확인 모달 표시
showFinalConfirmModal();
});
$('#restartCancelBtn').off('click').on('click', function() {
console.log("그대로 계속하기 버튼 클릭됨");
MicroModal.close('restart-modal');
});
}
// 최종 확인 모달을 표시하는 함수
function showFinalConfirmModal() {
// 모달 HTML 정의
const finalConfirmModalHtml = `
<div class="microModal microAlert" id="final-confirm-modal" aria-hidden="true">
<div class="microModal__overlay" tabindex="-1">
<div class="microModal__container" role="dialog" aria-modal="true">
<div class="microModal__body">
<div class="microModal__top">
<h2 class="microModal__title">
정말 처음부터 다시 시작하시겠습니까?
</h2>
</div>
<div class="microModal__content">
<p>
지금까지 진행한 내용이 모두 삭제됩니다.
</p>
</div>
</div>
<div class="microModal__btnContainer">
<button class="microModal__btn" id="finalCancelBtn">취소</button>
<button class="microModal__btn microModal__btn-primary" id="finalConfirmBtn">다시 시작하기</button>
</div>
</div>
</div>
</div>
`;
// 이미 존재하는 final-confirm-modal 제거 (중복 방지)
$('#final-confirm-modal').remove();
// 모달 HTML을 body에 추가
$('body').append(finalConfirmModalHtml);
// 모달 표시
MicroModal.show('final-confirm-modal');
// 버튼 이벤트 핸들러 연결
$('#finalConfirmBtn').off('click').on('click', function() {
console.log("최종 다시 시작하기 버튼 클릭됨");
MicroModal.close('final-confirm-modal');
// 캡슐 선택 페이지로 이동
resetCapsule();
});
$('#finalCancelBtn').off('click').on('click', function() {
console.log("최종 취소 버튼 클릭됨");
MicroModal.close('final-confirm-modal');
});
}

View File

@@ -0,0 +1,204 @@
$(document).ready(function() {
let page = 1;
let isLoading = false;
let hasMoreData = true;
const itemsPerPage = 20;
// 디버깅: 초기 상태 확인
console.log('페이지 로드 완료. 초기 page:', page);
console.log('초기 데이터 개수:', $('#leafHistoryList li').length);
console.log('초기 hasMoreData:', hasMoreData);
// 초기 데이터 개수 확인하여 hasMoreData 설정
const initialItemCount = $('#leafHistoryList li').length;
if (initialItemCount < itemsPerPage) {
console.log('초기 데이터가 ' + itemsPerPage + '개 미만입니다. hasMoreData를 false로 설정합니다.');
hasMoreData = false;
}
// 하지만 초기 데이터가 정확히 20개라면 더 있을 가능성이 있으므로 true 유지
if (initialItemCount === itemsPerPage) {
console.log('초기 데이터가 정확히 ' + itemsPerPage + '개입니다. 더 많은 데이터가 있을 수 있습니다.');
hasMoreData = true;
}
// 초기 데이터가 0개인 경우도 체크
if (initialItemCount === 0) {
console.log('초기 데이터가 없습니다.');
hasMoreData = false;
}
console.log('최종 hasMoreData:', hasMoreData);
// 무한 스크롤 이벤트 처리
$(window).scroll(function() {
console.log('스크롤 이벤트 발생');
console.log('isLoading:', isLoading, 'hasMoreData:', hasMoreData);
if (isLoading || !hasMoreData) {
console.log('스크롤 처리 중단됨');
return;
}
const scrollHeight = $(document).height();
const scrollTop = $(window).scrollTop();
const windowHeight = $(window).height();
console.log('스크롤 정보:', {
scrollHeight: scrollHeight,
scrollTop: scrollTop,
windowHeight: windowHeight,
trigger: scrollTop + windowHeight,
threshold: scrollHeight - 200
});
// 스크롤이 페이지 하단에 도달했는지 확인
if (scrollTop + windowHeight >= scrollHeight - 200) {
console.log('스크롤 임계점 도달, loadMoreItems 호출');
loadMoreItems();
}
});
function loadMoreItems() {
console.log('loadMoreItems 시작, 요청할 페이지:', page + 1);
isLoading = true;
$('#loading').show();
// 다음 페이지 데이터 요청
$.ajax({
url: `/sopmal/bnft/leafHistory/more?page=${page + 1}&size=${itemsPerPage}`,
type: 'GET',
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
beforeSend: function() {
console.log('AJAX 요청 시작:', `/sopmal/bnft/leafHistory/more?page=${page + 1}&size=${itemsPerPage}`);
},
success: function(response) {
console.log('AJAX 응답 받음:', response);
try {
let data = null;
// 응답 구조에 맞게 데이터 추출
if (response && response.resBody && response.resBody.result) {
// result가 문자열인 경우 JSON 파싱
if (typeof response.resBody.result === 'string') {
data = JSON.parse(response.resBody.result);
console.log('JSON 파싱 성공:', data);
} else {
data = response.resBody.result;
}
} else if (response && response.data) {
data = response.data;
} else {
console.log('예상치 못한 응답 구조:', response);
}
if (data && data.history && data.history.length > 0) {
console.log('응답 데이터 개수:', data.history.length);
appendItems(data.history);
page++;
console.log('페이지 증가됨. 현재 page:', page);
// 마지막 페이지인지 확인
if (data.history.length < itemsPerPage) {
hasMoreData = false;
console.log('마지막 페이지 도달');
}
} else {
hasMoreData = false;
console.log('데이터 없음. 더 이상 로드 안함');
}
} catch (e) {
console.error('JSON 파싱 오류:', e);
hasMoreData = false;
}
},
error: function(xhr, status, error) {
console.error('AJAX 요청 실패:', {
status: xhr.status,
statusText: xhr.statusText,
error: error,
responseText: xhr.responseText
});
hasMoreData = false;
},
complete: function() {
console.log('AJAX 요청 완료');
isLoading = false;
$('#loading').hide();
}
});
}
function appendItems(items) {
console.log('appendItems 호출, 추가할 아이템 수:', items.length);
const $historyList = $('#leafHistoryList');
$.each(items, function(index, item) {
const $li = $('<li>');
// 타이틀 부분 생성
const $titleSpan = $('<span>', {
class: 'tit',
text: item.eventName
});
// USE 타입이면 아이콘 추가
if (item.actionType === 'USE') {
const gameStage = item.gameStage || '1';
$('<i>', {
class: `ico_flask0${gameStage}`
}).appendTo($titleSpan);
}
// 날짜 부분 생성
const dateTime = new Date(item.actionDatetime);
const formattedDate = dateTime.getFullYear().toString().slice(2) + '-' +
(dateTime.getMonth() + 1).toString().padStart(2, '0') + '-' +
dateTime.getDate().toString().padStart(2, '0') + ' ' +
dateTime.getHours().toString().padStart(2, '0') + ':' +
dateTime.getMinutes().toString().padStart(2, '0');
const $dateSpan = $('<span>', {
class: 'dayTime',
text: formattedDate
});
// 개수 부분 생성
const countValue = Math.abs(parseFloat(item.leafCount)).toString().replace('.0', '');
const countPrefix = item.leafCount > 0 ? '+' : '-';
const $countSpan = $('<span>', {
class: item.leafCount < 0 ? 'cnt minus' : 'cnt',
text: countPrefix + countValue
});
// 요소들 추가
$li.append($titleSpan).append($dateSpan).append($countSpan);
$historyList.append($li);
});
console.log('아이템 추가 완료. 총 아이템 수:', $('#leafHistoryList li').length);
}
// Top 버튼 이벤트
$('.quick-menu__to-top button').click(function() {
$('html, body').animate({
scrollTop: 0
}, 300);
});
// 페이지 로드 시 스크롤 가능 여부 확인
setTimeout(function() {
const isScrollable = $(document).height() > $(window).height();
console.log('스크롤 가능 여부:', isScrollable);
console.log('문서 높이:', $(document).height(), '윈도우 높이:', $(window).height());
if (!isScrollable) {
console.log('스크롤바가 없어서 무한 스크롤이 동작하지 않습니다.');
}
}, 100);
});

View File

@@ -0,0 +1,126 @@
/**
* CpslmknSettings.js
*
* 캡슐 메이킹 게임 설정 페이지 관련 기능 구현
* 초록잎/도토리 보유 내역 보기, 알림 설정 등 사용자 설정 기능 제공
*/
/**
* 토스트 메시지 표시 함수
* @param {string} message - 표시할 메시지
*/
function showToast(message) {
// 기존에 토스트 요소가 있으면 제거
$('.toastPop').remove();
// 토스트 요소 생성 및 추가
const $toast = $('<div class="toastPop">' + message + '</div>');
$('body').append($toast);
// 토스트 표시 및 자동 제거
setTimeout(function() {
$toast.addClass('show');
setTimeout(function() {
$toast.removeClass('show');
// 애니메이션 완료 후 요소 제거
setTimeout(function() {
$toast.remove();
}, 300);
}, 2000);
}, 100);
}
/**
* 초록잎/도토리 보유 내역 페이지로 이동하는 함수
* 사용자가 현재까지 모은 아이템 내역을 확인할 수 있는 페이지로 이동
*/
function goToLeafHistory() {
const sopmalCd = $('#sopmalCd').val();
window.location.href = `/sopmal/bnft/leafHistory?sopmalCd=${sopmalCd}`;
}
/**
* 알림 설정 토글 함수
* 사용자의 알림 수신 동의 상태를 변경하고 서버에 업데이트
* 스위치 토글 시 호출되어 서버에 알림 설정 변경 요청
*/
function toggleNotification() {
// 토글 스위치 요소 및 현재 선택 상태 가져오기
const $switch = $('input[role="switch"]'); // 토글 스위치 요소
const isChecked = $switch.prop('checked'); // 현재 스위치 상태 (체크됨/안됨)
const agreAt = isChecked ? 'Y' : 'N'; // 동의 여부 값 (Y: 동의, N: 미동의)
// 서버에 알림 설정 변경 요청
$.ajax({
url: '/sopmal/bnft/notification',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
// bnfDvCd, bnfAgreDvCd는 백엔드에서 고정값 사용 또는 자동 인식 가능 시 제거 가능
bnfDvCd: '050',
bnfAgreDvCd: '010',
agreAt: agreAt // 동의 여부 (Y/N) - 필수 전송
}),
// 요청 성공 시 처리
success: function(response) {
// 응답 결과 파싱 (WnbtResponse 구조 가정)
try {
// responseHandler가 이미 파싱해줄 수 있으나, 안전하게 처리
let resultData = {};
if (typeof response.resBody?.result === 'string') {
resultData = JSON.parse(response.resBody.result);
} else {
resultData = response.resBody?.result || {};
}
if (resultData.success === true) {
// 설정 변경 성공 메시지 (기존 alert 대신 다른 UI 고려 가능)
showToast(resultData.message || '알림 설정이 변경되었습니다.');
} else {
// 설정 변경 실패 메시지
showToast(resultData.message || '알림 설정 변경에 실패했습니다.');
// 서버 처리 실패 시 UI 상태 복원 (토글 원상태로)
$switch.prop('checked', !isChecked);
}
} catch (e) {
console.error("알림 설정 응답 처리 오류:", e);
showToast('알림 설정 응답 처리 중 오류가 발생했습니다.');
$switch.prop('checked', !isChecked); // 오류 시 UI 복원
}
},
// 요청 실패 시 처리
error: function(jqXHR, textStatus, errorThrown) {
// 통신 오류 메시지
console.error("알림 설정 API 호출 실패:", textStatus, errorThrown);
showToast('서버와의 통신 중 오류가 발생했습니다.');
// 통신 실패 시 UI 상태 복원 (토글 원상태로)
$switch.prop('checked', !isChecked);
}
});
}
/**
* 페이지 로드 시 실행되는 초기화 함수
* 페이지 UI 설정, 이벤트 리스너 등록, 초기 상태 설정
*/
$(document).ready(function() {
// 초록잎/도토리 보유 내역 링크 클릭 이벤트 리스너 추가
$('.productManagement li:first-child a').on('click', function(e) {
e.preventDefault(); // 기본 링크 동작 방지
goToLeafHistory(); // 보유 내역 페이지로 이동 함수 호출
});
// 알림 설정 스위치 변경 이벤트 리스너 추가
$('input[role="switch"]').on('change', toggleNotification);
// 페이지 로드 시 서버에서 받아온 사용자의 현재 알림 설정 상태에 따라 스위치 초기화
// notificationStatus 변수는 JSP에서 전역으로 선언되어 있어야 함 ('Y' 또는 'N')
if (typeof notificationStatus !== 'undefined') {
$('input[role="switch"]').prop('checked', notificationStatus === 'Y');
}
});