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,578 @@
/**
* Atend.js
*
* 출석체크 관련 기능을 관리하는 모듈
* 사용자가 매일 출석체크를 하고 이에 따른 보상을 받을 수 있는 기능 구현
*/
const Atend = (function() {
// 전역 변수로 관리
let attendanceDays = [];
// 현재 날짜 정보 초기화
const currentDate = new Date(); // 현재 날짜 객체
const currentMonth = currentDate.getMonth(); // 현재 월 (0-11)
const currentYear = currentDate.getFullYear(); // 현재 연도
const currentDay = currentDate.getDate(); // 현재 일자
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); // 현재 월의 총 일수
const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay(); // 현재 월의 첫 날 요일 (0: 일요일, 6: 토요일)
// DOM 요소 참조
const daysContainer = document.querySelector(".days"); // 달력 일자를 표시할 컨테이너
const checkAttendanceButton = document.getElementById("check-attendance"); // 출석체크 버튼
const checkNotificationButton = document.getElementById("check-notification"); // 알림 신청/취소 버튼
// 월 이름 배열 - 한글로 표시할 월 이름
const monthNames = ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"];
// 출석 일자 배열 - 서버에서 받아온 데이터에서 공백과 대괄호 등 불필요한 문자 제거
const trimmedAttendanceDays = trimArrayElements(attendanceDays);
// 1. attendanceDays 원본 데이터 확인
console.log('attendanceDays:', attendanceDays);
// 2. 처리된 데이터 확인
console.log('trimmedAttendanceDays:', trimmedAttendanceDays);
// 3. 현재 날짜 형식 확인
const today = new Date();
const formattedToday = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;
console.log('Today formatted:', formattedToday);
/**
* 초기화 함수
* 페이지 로드 시 호출되어 달력과 관련 기능을 초기화
*/
function init() {
updateMonthTitle(); // 월 제목 업데이트
generateCalendar(); // 달력 생성
addEventListeners(); // 이벤트 리스너 등록
getNotificationStatus(); // 알림 상태 조회
getLatestAttendanceInfo(); // 여기서 출석 데이터 로드 및 달력 업데이트
}
/**
* 월 제목 업데이트 함수
* 현재 월을 한글로 화면에 표시
*/
function updateMonthTitle() {
document.querySelector(".month-tit").textContent = monthNames[currentMonth];
}
/**
* 달력 생성 함수
* 빈 셀과 일자를 생성하여 달력 완성
*/
function generateCalendar() {
generateEmptyCells(); // 달력 시작 전 빈 셀 생성
generateDays(); // 일자 셀 생성
}
/**
* 달력 앞부분 빈 셀 생성 함수
* 월의 첫 날 요일에 맞게 앞부분에 빈 셀 생성
*/
function generateEmptyCells() {
for (let i = 0; i < firstDayOfMonth; i++) {
const emptyCell = document.createElement("div"); // 빈 div 요소 생성
daysContainer.appendChild(emptyCell); // 달력 컨테이너에 추가
}
}
/**
* 달력 일자 생성 함수
* 현재 월의 모든 일자를 생성하고 출석 상태에 따라 클래스 추가
*/
function generateDays() {
for (let day = 1; day <= daysInMonth; day++) {
const calendarDay = document.createElement("div"); // 날짜 셀 생성
calendarDay.textContent = day; // 날짜 텍스트 설정
// 날짜 형식 포맷팅 (YYYY-MM-DD)
const formattedDay = formatDate(currentYear, currentMonth + 1, day);
// 출석한 날인 경우 success 클래스 추가
if (trimmedAttendanceDays.includes(formattedDay)) {
calendarDay.classList.add("success");
}
// 오늘 날짜인 경우 today 클래스 추가
if (day === currentDay) {
calendarDay.classList.add("today");
}
// 달력 컨테이너에 날짜 셀 추가
daysContainer.appendChild(calendarDay);
}
}
/**
* 이벤트 리스너 등록 함수
* 출석체크 및 알림 버튼에 이벤트 핸들러 연결
*/
function addEventListeners() {
checkAttendanceButton.addEventListener("click", handleAttendanceCheck); // 출석체크 버튼 클릭 이벤트
checkNotificationButton.addEventListener("click", handleNotification); // 알림 신청/취소 버튼 클릭 이벤트
}
/**
* 출석체크 처리 함수
* 사용자가 출석체크 버튼 클릭 시 실행되는 핸들러
*/
function handleAttendanceCheck() {
console.log('handleAttendanceCheck');
// 오늘 날짜를 YYYY-MM-DD 형식으로 포맷팅
const formattedToday = formatDate(currentYear, currentMonth + 1, currentDay);
// 오늘 출석체크를 아직 하지 않은 경우
if (!trimmedAttendanceDays.includes(formattedToday)) {
// 출석 횟수 계산 (현재 출석일 수 + 1)
const attendanceCount = trimmedAttendanceDays.length + 1;
// 보상 코드 결정 (10일, 20일, 월말에 따라 다른 보상)
const bnfRwdCateDvCd = getBenefitRewardCode(attendanceCount);
// 출석 정보 저장 요청
saveAttendance(formattedToday, bnfRwdCateDvCd);
} else {
// 이미 출석체크한 경우 모달 표시
openModal('modal-already-checked');
}
}
/**
* 출석 정보 저장 함수
* 서버에 출석 정보 저장 요청
* @param {string} formattedToday - YYYY-MM-DD 형식의 날짜
* @param {string} bnfRwdCateDvCd - 보상 카테고리 코드
*/
function saveAttendance(formattedToday, bnfRwdCateDvCd) {
$.ajax({
url: '/sopmal/bnft/participateAttendanceCheck', // 출석체크 API 엔드포인트
method: 'POST', // HTTP 메서드
contentType: 'application/json; charset=UTF-8', // 요청 콘텐츠 타입
data: JSON.stringify({
bnfInnb: screenInfo.bnfInnb,
bnfPrtDt: formattedToday, // 출석일자
bnfRwdCateDvCd: bnfRwdCateDvCd, // 보상 카테고리 코드
bnfRwdGodsDvCd: '010' // 보상 상품 코드 (010: 마일리지)
}),
success: function(response) {
var data = JSON.parse(response.resBody.result);
//console.log(data);
if (data.success) {
// 출석체크 성공 처리
handleAttendanceSuccess(formattedToday)(data);
} else {
// 실패 메시지 표시 - alert를 showCustomAlert로 변경
showCustomAlert(data.message || '출석체크에 실패했습니다.');
}
},
error: handleAttendanceError // 에러 핸들러
});
}
/**
* 출석 저장 성공 처리 함수
* 출석체크 성공 시 UI 업데이트 및 모달 표시
* @param {string} formattedToday - YYYY-MM-DD 형식의 날짜
* @returns {Function} - 실제 성공 처리를 수행할 콜백 함수
*/
function handleAttendanceSuccess(formattedToday) {
return function(data) {
// 출석일 배열에 오늘 날짜 추가
trimmedAttendanceDays.push(formattedToday);
// 달력 UI 업데이트
updateCalendarUI();
// 출석 완료 모달 표시 (응답 데이터 전달)
showAttendanceModal(data);
// 최신 출석 정보 조회 (출석 횟수, 적립 마일리지 등)
getLatestAttendanceInfo();
};
}
/**
* 알림용 모달 표시 함수
* alert 대신 모달을 사용하여 메시지 표시
* @param {string} message - 표시할 메시지
*/
function showCustomAlert(message) {
$('#modal-alert .microModal__content p').text(message);
// 기존 이벤트 모두 제거
$('#modal-alert .microModal__btn-primary').off('click');
$('#modal-alert .microModal__overlay').off('click');
// 확인 버튼 클릭 이벤트
$('#modal-alert .microModal__btn-primary').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log("모달 확인 버튼 클릭");
MicroModal.close('modal-alert');
});
// 오버레이 클릭 시 닫기
$('#modal-alert .microModal__overlay').on('click', function(e) {
if (e.target === this) {
console.log("모달 오버레이 클릭");
MicroModal.close('modal-alert');
}
});
// ESC 키로 닫기
$(document).off('keydown.modalAlert').on('keydown.modalAlert', function(e) {
if (e.keyCode === 27) { // ESC 키
console.log("ESC 키로 모달 닫기");
MicroModal.close('modal-alert');
$(document).off('keydown.modalAlert');
}
});
console.log("모달 표시:", message);
MicroModal.show('modal-alert');
}
/**
* 출석 저장 실패 처리 함수
* 출석체크 저장 실패 시 에러 메시지 표시
* @param {Object} xhr - XMLHttpRequest 객체
* @param {string} status - 상태 텍스트
* @param {string} error - 에러 메시지
*/
function handleAttendanceError(xhr, status, error) {
console.error('Failed to save attendance:', error);
// alert를 showCustomAlert로 변경
showCustomAlert('출석체크를 저장하는 데 실패했습니다. 다시 시도해주세요.');
}
/**
* 달력 UI 업데이트 함수
* 출석체크 성공 후 오늘 날짜 셀에 성공 표시 추가
*/
function updateCalendarUI() {
// 오늘 날짜에 해당하는 셀 찾기
const todayElement = Array.from(daysContainer.children).find(child => child.textContent == currentDay);
if (todayElement) {
// 출석 성공 클래스 추가
todayElement.classList.add("success");
}
}
/**
* 출석 모달 표시 함수
* 출석 횟수에 따라 다른 보상 모달 표시
* @param {Object} data - 서버 응답 데이터 (receivedRewards 포함)
*/
function showAttendanceModal(data) {
const attendanceCount = data.currentAttendanceCount || trimmedAttendanceDays.length;
// 10일 출석 달성
if (attendanceCount === 10) {
updateModalWithRewardInfo('modal-2', data.receivedRewards);
openModal('modal-2');
}
// 20일 출석 달성
else if (attendanceCount === 20) {
updateModalWithRewardInfo('modal-3', data.receivedRewards);
openModal('modal-3');
}
// 한 달 모두 출석 달성
else if (attendanceCount === daysInMonth) {
updateModalWithRewardInfo('modal-4', data.receivedRewards);
openModal('modal-4');
}
// 일반 출석
else {
openModal('modal-1');
}
}
/**
* 모달에 보상 정보 업데이트 함수
* 서버에서 받은 보상 정보로 모달 내용을 동적으로 업데이트
* @param {string} modalId - 모달 ID
* @param {Array} receivedRewards - 받은 보상 목록
*/
function updateModalWithRewardInfo(modalId, receivedRewards) {
if (!receivedRewards || receivedRewards.length === 0) {
return;
}
const modal = document.getElementById(modalId);
if (!modal) {
return;
}
const subTitleElement = modal.querySelector('.sub_tit');
if (!subTitleElement) {
return;
}
// 쿠폰 보상이 있는 경우 모달 내용 업데이트
const couponReward = receivedRewards.find(reward => reward.couponName);
if (couponReward) {
if (modalId === 'modal-2' || modalId === 'modal-3') {
// 10회, 20회 출석 시 쿠폰 이름 동적 표시
subTitleElement.textContent = `${couponReward.couponName}이 발급되었습니다.`;
}
}
// 100% 출석 보상의 경우 (modal-4)
if (modalId === 'modal-4') {
const mileageReward = receivedRewards.find(reward => reward.mileageAmount);
let rewardText = '';
if (mileageReward) {
rewardText += `축하마일리지 ${mileageReward.mileageAmount}원 적립완료`;
} else {
rewardText += '축하마일리지 적립완료';
}
rewardText += '\n특별경품 자동응모 완료';
// 쿠폰도 있는 경우 추가
if (couponReward) {
rewardText += `\n${couponReward.couponName} 발급완료`;
}
subTitleElement.innerHTML = rewardText.replace(/\n/g, '<br>');
}
}
/**
* 최신 출석 정보 조회 함수
* 서버에서 최신 출석 횟수와 적립 마일리지 정보 조회
*/
function getLatestAttendanceInfo() {
$.ajax({
url: '/sopmal/bnft/getLatestAttendanceInfo',
method: 'POST',
data: JSON.stringify({
bnfInnb: screenInfo.bnfInnb
}),
contentType: 'application/json; charset=utf-8',
success: function(response) {
// 출석 정보 UI 업데이트
updateAttendanceInfo(response.attendanceCount, response.totalAttendanceMileage);
// 출석 날짜로 달력 업데이트
if (response.attendanceDays) {
updateCalendarWithAttendanceDays(response.attendanceDays);
}
},
error: function(xhr, status, error) {
console.error('Failed to fetch latest attendance info:', error);
}
});
}
/**
* 출석 정보 UI 업데이트 함수
* 화면에 출석 횟수와 누적 마일리지 표시
* @param {number} attendanceCount - 출석 횟수
* @param {number} totalAttendanceMileage - 누적 마일리지
*/
function updateAttendanceInfo(attendanceCount, totalAttendanceMileage) {
document.querySelector(".cnt1").textContent = `${attendanceCount}회`; // 출석 횟수 표시
document.querySelector(".cnt2").textContent = `${totalAttendanceMileage}원`; // 누적 마일리지 표시
}
/**
* 배열 요소 공백 제거 함수
* 배열의 각 요소에서 대괄호와 공백 제거
* @param {Array} arr - 처리할 배열
* @returns {Array} - 처리된 배열
*/
function trimArrayElements(arr) {
return arr.map(element => element.replace(/[\[\]\s]/g, ''));
}
/**
* 날짜 포맷팅 함수
* 연, 월, 일을 YYYY-MM-DD 형식으로 변환
* @param {number} year - 연도
* @param {number} month - 월 (1-12)
* @param {number} day - 일
* @returns {string} - YYYY-MM-DD 형식의 날짜 문자열
*/
function formatDate(year, month, day) {
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
}
/**
* 보상 코드 결정 함수
* 출석 횟수에 따라 다른 보상 코드 반환
* @param {number} attendanceCount - 출석 횟수
* @returns {string} - 보상 카테고리 코드
*/
function getBenefitRewardCode(attendanceCount) {
if (attendanceCount === 10) return '01020'; // 10일 출석 보상
if (attendanceCount === 20) return '01030'; // 20일 출석 보상
if (attendanceCount === daysInMonth) return '01040'; // 한 달 모두 출석 보상
return '01010'; // 일반 출석 보상
}
/**
* 알림 상태 조회 함수
* 서버에서 사용자의 알림 신청 상태 조회
*/
function getNotificationStatus() {
$.ajax({
url: '/sopmal/bnft/getNotificationStatus', // 알림 상태 조회 API
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ bnfInnb: screenInfo.bnfInnb }), // 혜택 고유번호
success: function(response) {
// 알림 버튼 UI 업데이트
updateNotificationButton(response.notificationStatus);
},
error: function(xhr, status, error) {
console.error('알림 상태 조회 실패:', error);
}
});
}
/**
* 알림 버튼 업데이트 함수
* 알림 상태에 따라 버튼 텍스트와 데이터 속성 변경
* @param {boolean} isNotificationOn - 알림 신청 여부
*/
function updateNotificationButton(isNotificationOn) {
const $notificationBtn = $('#check-notification');
const $iconElement = $notificationBtn.find('i.ico_alarm');
// 알림 상태에 따라 버튼 텍스트 변경
if (isNotificationOn) {
// 알림 활성화 상태: '알림 신청 완료' 표시 및 취소 액션으로 설정
$notificationBtn.contents().filter(function() {
return this.nodeType === 3; // 텍스트 노드만 선택
}).replaceWith('알림 신청 완료');
$notificationBtn.data('action', 'cancel');
} else {
// 알림 비활성화 상태: '알림 신청' 표시 및 신청 액션으로 설정
$notificationBtn.contents().filter(function() {
return this.nodeType === 3; // 텍스트 노드만 선택
}).replaceWith('알림 신청');
$notificationBtn.data('action', 'apply');
}
// 알림 아이콘이 없으면 추가
if ($iconElement.length === 0) {
$notificationBtn.prepend('<i class="ico_alarm"></i>');
}
}
/**
* 알림 신청/취소 처리 함수
* 사용자가 알림 버튼 클릭 시 신청 또는 취소 요청 처리
*/
function handleNotification() {
// 현재 버튼의 액션 상태 확인 (신청 또는 취소)
var action = $('#check-notification').data('action');
// 액션에 따른 API URL 결정
const url = action === 'apply' ? '/sopmal/bnft/applyNotification' : '/sopmal/bnft/cancelNotification';
$.ajax({
url: url,
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ bnfInnb: screenInfo.bnfInnb }), // 혜택 고유번호
success: function(response) {
if (response.success) {
// 성공 메시지 표시 (신청 또는 취소에 따라 다름)
$('#modal-alert .microModal__content p').text(action === 'apply' ? '알림 신청이 완료되었습니다.' : '알림 취소가 완료되었습니다.');
MicroModal.show('modal-alert');
// 알림 상태 다시 조회하여 UI 업데이트
getNotificationStatus();
} else {
// 실패 메시지 표시
$('#modal-alert .microModal__content p').text(response.message || (action === 'apply' ? '알림 신청에 실패했습니다.' : '알림 취소에 실패했습니다.'));
MicroModal.show('modal-alert');
}
},
error: function(xhr, status, error) {
console.error(action === 'apply' ? '알림 신청 실패:' : '알림 취소 실패:', error);
$('#modal-alert .microModal__content p').text(action === 'apply' ? '알림 신청 중 오류가 발생했습니다.' : '알림 취소 중 오류가 발생했습니다.');
MicroModal.show('modal-alert');
}
});
}
/**
* 문서 로드 완료 이벤트 핸들러
* 페이지 로드 시 초기화 진행 (로그인 확인 로직 제거됨)
*/
document.addEventListener("DOMContentLoaded", function() {
// 로그인 확인 로직 없이 바로 초기화 함수 호출
init();
// getNotificationStatus(); // init() 내부에서 호출됨
// getLatestAttendanceInfo(); // init() 내부에서 호출되도록 수정했으므로 제거
});
/**
* 달력 업데이트 함수 (모듈 내부로 이동)
*/
function updateCalendarWithAttendanceDays(attendanceDaysArray) {
console.log('Updating calendar with attendance days:', attendanceDaysArray);
// 전역 attendanceDays 업데이트
attendanceDays = attendanceDaysArray;
// 기존 success 클래스 제거
document.querySelectorAll('.days div').forEach(element => {
element.classList.remove('success');
});
// 새로운 출석 날짜에 success 클래스 추가
attendanceDaysArray.forEach(dateStr => {
const day = new Date(dateStr).getDate();
const dayElements = Array.from(daysContainer.children);
// 해당 날짜 요소 찾기 (빈 셀 제외)
const dayElement = dayElements.find(child => {
return child.textContent == day && child.textContent !== '';
});
if (dayElement) {
dayElement.classList.add('success');
console.log(`Added success class to day ${day}`);
}
});
}
// 공개 메서드
return {
init: init, // 초기화 함수
getLatestAttendanceInfo: getLatestAttendanceInfo, // 최신 출석 정보 조회
getNotificationStatus: getNotificationStatus, // 알림 상태 조회
handleNotification: handleNotification, // 알림 신청/취소 처리
updateCalendarWithAttendanceDays: updateCalendarWithAttendanceDays // 외부 접근용
};
})();
/**
* 브라우저 뒤로가기 이벤트 핸들러
* 뒤로가기 시 열려있는 모달 닫기
*/
window.addEventListener('popstate', function(event) {
var modalId = window.location.hash.replace('#', '');
if (modalId) {
// 해시에 모달 ID가 있으면 해당 모달 닫기
closeModal(modalId);
} else {
// 해시가 없으면 모든 모달 닫기
document.querySelectorAll('.microModal').forEach(modal => {
MicroModal.close(modal.id);
});
}
// 본문 스크롤 복원
document.body.style.overflow = '';
});
/**
* 페이지 로드 시 해시 체크
* URL에 해시가 있으면 해당 모달 열기
*/
if (window.location.hash) {
var modalId = window.location.hash.replace('#', '');
openModal(modalId);
}

View File

@@ -0,0 +1,343 @@
/**
* 혜택 메인 페이지 관련 자바스크립트
*/
// 전역 변수로 디바이스 권한 정보 저장
var globalDevicePermission = {
phacAgreAt: null // 신체활동접근 권한 여부
};
$(document).ready(function() {
// 배너 슬라이드 초기화
initBannerSlide();
// 리스트 애니메이션 효과
initListAnimation();
// 상단 이동 버튼 이벤트
initTopButton();
// 혜택 메뉴 클릭 이벤트 초기화
initBenefitMenuClick();
// 모달 초기화
initModal();
// 마케팅동의 팝업 표시
mbrMarktClauseAgreePopup();
var needDeviceIdCheck = $('#needDeviceIdCheck').val(); // deviceId가 없었는지 플래그
// 1차 요청 결과 확인
if (needDeviceIdCheck || !bnftMainData || bnftMainData.error) {
console.log('deviceId가 없거나 데이터 조회 실패, 2차 요청 시작');
// window.app.get_device_info()로 deviceId 조회
var json_str = window.app.get_device_info();
var result = JSON.parse(json_str);
var deviceId = result.deviceId; // 디바이스 고유ID(IME)
if (deviceId) {
// 2차 AJAX 요청
$.ajax({
url: '/sopmal/bnft/devicePermission',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
deviceId: deviceId
}),
success: function(response) {
console.log('2차 요청 성공:', response);
if (response.statusCode === 0 && response.resBody && response.resBody.result) {
var devicePermissionData = JSON.parse(response.resBody.result);
console.log('디바이스 권한 정보:', devicePermissionData);
// 전역 변수에 권한 정보 저장
globalDevicePermission.phacAgreAt = devicePermissionData.phacAgreAt;
// 디바이스 권한 정보를 기반으로 혜택 데이터 재조회 또는 처리
if (devicePermissionData.phacAgreAt === 'Y') {
console.log('신체활동접근 권한이 허용되었습니다.');
} else {
console.log('신체활동접근 권한이 거부되었습니다.');
}
} else {
console.error('응답 형식이 올바르지 않습니다:', response);
showErrorMessage('디바이스 권한 정보 조회에 실패했습니다.');
}
},
error: function(xhr, status, error) {
console.error('2차 요청 실패:', error);
showErrorMessage('혜택 정보 조회 중 오류가 발생했습니다.');
}
});
} else {
console.error('deviceId 조회 실패');
showErrorMessage('디바이스 정보를 확인할 수 없습니다.');
}
} else {
console.log('1차 요청으로 데이터 조회 완료');
// 1차 요청이 성공한 경우 기존 데이터 사용
renderBnftMainData(bnftMainData);
}
// URL 파라미터 읽어서 표시
const urlParams = new URLSearchParams(window.location.search);
const strInnb = urlParams.get('strInnb');
const sopmalCd = urlParams.get('sopmalCd');
if (strInnb || sopmalCd) {
$('#qr-info-display').show();
$('#store-id-value').text(strInnb || '-');
$('#sopmal-cd-value').text(sopmalCd || '-');
}
});
/**
* 배너 슬라이드 초기화 함수
*/
function initBannerSlide() {
// 배너가 있을 경우만 슬라이드 초기화
if ($('.benefit_main_bannerWrap').length > 0) {
const $slides = $('.benefit_bn');
const slideCount = $slides.length;
// 슬라이드가 1개 이상인 경우에만 실행
if (slideCount > 0) {
let currentIndex = 0;
let interval;
// 페이지네이션 버튼 생성
createPagination();
// 첫 번째 슬라이드 표시
showSlide(currentIndex);
// 자동 슬라이드 시작
startSliding();
// 슬라이드 영역에 마우스 진입/이탈 시 자동 슬라이드 제어
$('.benefit_main_bannerWrap').hover(stopSliding, startSliding);
// 페이지네이션 버튼 클릭 이벤트
$('.pagination').on('click', 'button', function() {
currentIndex = $(this).data('index');
showSlide(currentIndex);
});
// 슬라이드 영역 클릭 시 다음 슬라이드로 이동
$('.benefit_main_banner').on('click', function() {
nextSlide();
});
/**
* 특정 인덱스의 슬라이드 표시
*/
function showSlide(index) {
$slides.hide();
$slides.eq(index).fadeIn();
$('.pagination button').removeClass('active');
$('.pagination button').eq(index).addClass('active');
}
/**
* 페이지네이션 버튼 생성
*/
function createPagination() {
const $pagination = $('.pagination');
$pagination.empty();
for (let i = 0; i < slideCount; i++) {
$pagination.append(`<button data-index="${i}"></button>`);
}
}
/**
* 다음 슬라이드 표시
*/
function nextSlide() {
currentIndex = (currentIndex + 1) % slideCount;
showSlide(currentIndex);
}
/**
* 이전 슬라이드 표시
*/
function previousSlide() {
currentIndex = (currentIndex - 1 + slideCount) % slideCount;
showSlide(currentIndex);
}
/**
* 자동 슬라이드 시작
*/
function startSliding() {
stopSliding(); // 기존 인터벌 제거
interval = setInterval(nextSlide, 3000); // 3초마다 슬라이드 전환
}
/**
* 자동 슬라이드 중지
*/
function stopSliding() {
if (interval) {
clearInterval(interval);
}
}
}
}
}
/**
* 리스트 애니메이션 효과 초기화
*/
function initListAnimation() {
$('.item_li li').each(function(index) {
$(this).delay(index * 50).queue(function(next) {
$(this).css({
opacity: 1,
transform: 'translateY(0)'
});
next();
});
});
}
/**
* 상단 이동 버튼 이벤트 초기화
*/
function initTopButton() {
$('.quick-menu__to-top button').on('click', function() {
$('html, body').animate({
scrollTop: 0
}, 300);
});
}
/**
* 모달 초기화 함수
*/
function initModal() {
MicroModal.init({
disableScroll: true,
awaitCloseAnimation: true
});
}
function mbrMarktClauseAgreePopup() {
console.log('마케팅동의 팝업 표시');
const marktAgreeAt = $('#mbrMarktAgreeAt').val();
console.log('marktAgreeAt:', marktAgreeAt);
// 마케팅동의 체크 (메인 페이지와 동일한 방식)
if (marktAgreeAt === 'N') {
console.log('마케팅동의 필요');
// 마케팅동의 팝업 표시
const stplatDvCd = '05';
$.ajax({
type: 'post',
url: "/sopmal/mbr/selectMbrMarktAgreePopup",
data: {stplatDvCd: stplatDvCd},
success: function (data) {
$('#mbrMarktClauseAgreeDiv').html(data);
openModal('mbrMarktClauseAgreePopup');
}
});
e.preventDefault();
return;
}
}
/**
* 혜택 메뉴 클릭 이벤트 초기화
*/
function initBenefitMenuClick() {
$('.bnft-menu-item').on('click', function(e) {
const avalAt = $(this).data('aval-at');
const bnfUrl = $(this).data('bnf-url');
const bnfDvCd = String($(this).data('bnf-dv-cd'));
const isParticipated = $(this).find('.check_done').length > 0;
const ntkChlAt = $(this).data('ntk-chl-at');
const ntkBnfInnb = $(this).data('ntk-bnf-innb');
const cpslProgrsAt = $(this).data('cpsl-progrs-at');
const cpslBnfInnb = $(this).data('cpsl-bnf-innb');
const intrstAvalAt = $(this).data('intrst-aval-at');
// 디버깅 로그 추가
console.log('=== 혜택 메뉴 클릭 디버깅 ===');
console.log('avalAt:', avalAt, '(타입:', typeof avalAt, ')');
console.log('intrstAvalAt:', intrstAvalAt, '(타입:', typeof intrstAvalAt, ')');
console.log('bnfDvCd:', bnfDvCd);
console.log('bnfUrl:', bnfUrl);
console.log('isParticipated:', isParticipated);
console.log('globalDevicePermission.phacAgreAt:', globalDevicePermission.phacAgreAt);
console.log('=============================');
// 만보기(020) 메뉴 클릭 시 권한 체크
if (bnfDvCd === '020' && (avalAt === 'N' || globalDevicePermission.phacAgreAt === 'N')) {
console.log('만보기 신체활동 권한 미허용으로 차단됨');
MicroModal.show('modal-stepcnt-permission');
e.preventDefault();
return;
}
// 관심건강분야 설정(120) 메뉴에서만 관심분야 설정 여부 체크
if (bnfDvCd === '120' && intrstAvalAt === 'N') {
console.log('관심분야 미설정으로 차단됨');
MicroModal.show('modal-bnft-unavailable');
e.preventDefault();
return;
}
// 행운복권(030)이고 참여완료 상태인 경우
if (bnfDvCd === '030' && isParticipated) {
// 행운복권 참여완료 모달 표시
MicroModal.show('modal-lucky-participated');
e.preventDefault();
}
// OX 퀴즈(130)이고 참여완료 상태인 경우
else if (bnfDvCd === '130' && isParticipated) {
// OX 퀴즈 참여완료 모달 표시
MicroModal.show('modal-oxquiz-participated');
e.preventDefault();
}
// 캡슐만들기(050)인 경우 진행 중인 게임 여부에 따라 분기
else if (bnfDvCd === '050' && avalAt === 'Y') {
if (cpslProgrsAt === 'Y' && cpslBnfInnb) {
// 진행 중인 게임이 있는 경우 - createGame으로 bnfInnb와 함께 이동
window.location.href = '/sopmal/bnft/createGame?bnfInnb=' + cpslBnfInnb;
} else {
// 진행 중인 게임이 없는 경우 - selectCapsule로 이동
window.location.href = '/sopmal/bnft/selectCapsule';
}
e.preventDefault();
}
// 섭취챌린지(100)인 경우 참여 여부에 따라 분기
else if (bnfDvCd === '100' && avalAt === 'Y') {
if (ntkChlAt === 'Y' && ntkBnfInnb) {
// 참여 중인 섭취챌린지가 있는 경우
window.location.href = '/sopmal/bnft/intakchlDailyRecordMain?bnfInnb=' + ntkBnfInnb;
} else {
// 참여 중인 섭취챌린지가 없는 경우
window.location.href = bnfUrl;
}
e.preventDefault();
}
else if (avalAt === 'Y') {
console.log('정상 이동:', bnfUrl);
window.location.href = bnfUrl;
} else {
console.log('avalAt 값이 Y가 아니어서 차단됨. 실제값:', avalAt);
MicroModal.show('modal-bnft-unavailable');
e.preventDefault();
}
});
}

View File

@@ -0,0 +1,344 @@
/*******************************************
Web-App 연동 스크립트
*******************************************/
// 하단탭 메뉴제어 - 앱접근시 혜택링크 세팅
$(document).ready(function(){
if(window.app) {
const $tabBar = $("#tabBarWishOrBnft");
if ($tabBar.length > 0) {
var sopmalcd = $tabBar.data('sopmalcd');
if(sopmalcd == 'NC' || sopmalcd == 'DT') {
$tabBar.attr("href", "/sopmal/bnft/bnftMain");
$tabBar.find("i").removeClass("icon_tab-bar-wishlist").addClass("icon_tab-bar-benefit");
$tabBar.contents().filter(function() {
return this.nodeType === 3 && $.trim(this.nodeValue) !== "";
}).each(function() {
this.nodeValue = "혜택";
});
}
}
}
// 건강 데이터 이용 안내 모달 표시 (앱 진입시)
setTimeout(function() {
fn_show_health_data_agreement_modal();
}, 500); // 0.5초 지연 후 실행 (페이지 로딩 완료 대기)
});
// 앱접속후 로그인전 디바이스정보 입력
function fn_save_device_info() {
if(window.app) {
var json_str = window.app.get_device_info();
var result = JSON.parse(json_str);
var tokenKey = result.tokenKey; // push key
var deviceId = result.deviceId; // 디바이스 고유ID(IME)
var deviceOsNm = result.deviceOsNm; // 운영체제이름
var deviceOsVer = result.deviceOsVer; // 운영체제버전
var deviceAppVer = result.deviceAppVer; // 앱버전
var pushAgreAt = result.pushAgreAt; // 알림 동의 여부
var phacAgreAt = result.phacAgreAt; // 신체활동접근 동의 여부
// 현재 세션에서 저장여부
var sessDvcId = sessionStorage.getItem("sessDvcId");
console.log('sessDvcId : ' + sessDvcId);
// 앱디바이스 정보 저장
if(typeof deviceId !== 'undefined' && deviceId !== '' && !sessDvcId) {
$.ajax({
url: '/sopmal/app/insertAppDeviceInfo',
type: 'post',
contentType: 'application/json; charset=UTF-8',
data : JSON.stringify({
fcmToken : tokenKey,
pushAgreAt : pushAgreAt,
phacAgreAt : phacAgreAt,
deviceId : deviceId,
deviceOsNm : deviceOsNm,
deviceOsVer : deviceOsVer,
deviceAppVer : deviceAppVer
}),
success: function (result) {
const data = JSON.parse(result.resBody.result);
if(data.result == 'success') {
sessionStorage.setItem("sessDvcId", data.deviceId);
console.log('디바이스 정보 저장');
}
},
error: function() {
console.log('디바이스 정보 저장 실패');
}
});
}
} else {
console.log('[Skipped] Not in app context or already processed.');
}
}
// 엡에서 로그인후 회원-디바이스정보 한번에 입력 or 수정
function fn_save_mbrdvc_info(isAuth) {
console.log('[Called] fn_save_mbrdvc_info()')
// 로그인상태가 아니라면 리턴
if(!isAuth) return;
// 저장후 sessionStorage.sessMbrId 값을 설정함으로 해당값이 없는경우에만 저장
var sessMbrId = sessionStorage.getItem("sessMbrId");
if(window.app && !sessMbrId) {
var json_str = window.app.get_device_info();
var result = JSON.parse(json_str);
//console.log(json_str);
var tokenKey = result.tokenKey; // push key
var deviceId = result.deviceId; // 디바이스 고유ID(IME)
var deviceOsNm = result.deviceOsNm; // 운영체제이름
var deviceOsVer = result.deviceOsVer; // 운영체제버전
var deviceAppVer = result.deviceAppVer; // 앱버전
var pushAgreAt = result.pushAgreAt; // 알림 동의 여부
var phacAgreAt = result.phacAgreAt; // 신체활동접근 동의 여부
// 앱디바이스 정보 저장
if(typeof deviceId !== 'undefined' && deviceId !== '' && !sessMbrId) {
$.ajax({
url: '/sopmal/app/insertAppMbrDvcInfo',
type: 'post',
contentType: 'application/json; charset=UTF-8',
data : JSON.stringify({
fcmToken : tokenKey,
pushAgreAt : pushAgreAt,
phacAgreAt : phacAgreAt,
deviceId : deviceId,
deviceOsNm : deviceOsNm,
deviceOsVer : deviceOsVer,
deviceAppVer : deviceAppVer
}),
success: function (result) {
const data = JSON.parse(result.resBody.result);
if(data.result == 'success') {
sessionStorage.setItem("sessDvcId", data.deviceId);
sessionStorage.setItem("sessMbrId", data.mbrId);
console.log('[저장] 회원-디바이스정보 정보 저장');
}
},
error: function() {
console.log('[오류] 회원-디바이스정보 저장 실패');
}
});
}
} else {
console.log('[Skipped] Not in app context or already processed.');
}
}
// 푸시알림 터치 웹 콜백 - pushType : 푸시타입, data : 전달받은 data
function callback_push_receive_process (pushType , data){
if(window.app) {
console.log("[Called] callback_push_receive_process()");
console.log(data);
try {
// JSON 문자열 객체 파싱
const parsedData = (typeof data === 'string') ? JSON.parse(data) : data;
if (pushType !== '') {
const dataUrl = parsedData.url || '';
const dataKey = parsedData.key || '';
const dataExt = parsedData.ext || '';
if(dataUrl !== '') {
const hasQuery = dataUrl.includes('?');
var linkUrl = '';
linkUrl += dataUrl;
if (dataKey !== '') linkUrl += (hasQuery ? '&' : '?') + 'key=' + dataKey;
if (dataExt !== '') linkUrl += '&ext=' + dataExt;
location.href = linkUrl;
}
}
} catch (e) {
console.error("Invalid JSON data received:", data);
}
}
}
// 푸시 발송 테스트
function fn_push_send() {
if(window.app) {
var json_str = window.app.get_device_info();
var result = JSON.parse(json_str);
var tokenKey = result.tokenKey;
//var tokenKey = "cKqrsjAZSXW1GlefD96MkH:APA91bH37JY5BUQx5m5PMNBdrFsapKtWk0xs8WJNAaFqra3_gj4msjLOTu3e-tRTCrczraL3N5KmRbCcSvPgOfDiQJKlc7LqmCR0WpDwFanLeZi-t-AsGwc";
var pushSj = "테스트 발송";
var pushCn = "테스트 발송입니다.";
var pushType = "notice";
var imgFilCours = "https://img.soju1sake1.com/NC/goods/1/2017/06/116_tmp_2a6add67ad603d452d32743dac0410112327view.jpg";
var dataUrl = "/sopmal/goods/selectGoodsDtl?goodsInnb=857";
var dataKey = "";
var dataExt = "";
var arrFcmToken = [tokenKey];
if(typeof tokenKey !== 'undefined' && tokenKey !== '') {
$.ajax({
url: '/sopmal/app/procTestPushNotification',
type: 'post',
contentType: 'application/json; charset=UTF-8',
data : JSON.stringify({
fcmToken : tokenKey,
arrFcmToken : arrFcmToken,
pushSj : pushSj,
pushCn : pushCn,
pushType : pushType,
imgFilCours : imgFilCours,
dataUrl : dataUrl,
dataKey : dataKey,
dataExt : dataExt
}),
success: function (result) {
const data = JSON.parse(result.resBody.result);
if(data.result == 'success') {
console.log('테스트 푸시 발송 성공');
}
},
error: function() {
console.log('테스트 푸시 발송 실패');
}
});
}
}
}
// 딥링크 페이지 이동
function callback_link(shopcd , type , data){
if(window.app) {
// http://dev.nutricore.co.kr/sopmal/app/openApp?linkType=reward&bnfDvCd=150&strInnb=1527
// var strparam = '?type=' + linkType + '&data=' + bnfDvCd + '|' + strInnb;
console.log("[Called] callback_link()");
console.log('callback_link shopcd : ' + shopcd);
console.log('callback_link type : ' + type);
console.log('callback_link data : ' + data);
var arrData = data.split('|');
var bnfDvCd = arrData[0]; // 혜택구분코드
var strInnb = arrData[1]; // 스토어고유번호
// 보상 처리
if(type == 'reward'){
location.href = '/sopmal/bnft/rewardProc?sopmalCd='+ shopcd +'&bnfDvCd='+ bnfDvCd +'&strInnb='+ strInnb;
}
}
}
// 건강 데이터 이용 안내 모달 표시
function fn_show_health_data_agreement_modal() {
console.log('[Called] fn_show_health_data_agreement_modal()');
// 앱 환경에서만 실행
if(!window.app) {
console.log('[Skipped] Not in app context.');
return;
}
// sessionStorage 체크 - 이미 표시한 경우 스킵
var healthDataAgreementShown = sessionStorage.getItem("hdaShown");
if(healthDataAgreementShown === 'Y') {
console.log('[Skipped] Health data agreement modal already shown.');
return;
}
// sopmalCd 가져오기
const $tabBar = $("#tabBarWishOrBnft");
var sopmalCd = '';
if ($tabBar.length > 0) {
sopmalCd = $tabBar.data('sopmalcd');
}
// 개인정보처리방침 링크 설정
var privacyPolicyUrl = '';
if(sopmalCd == 'NC') {
privacyPolicyUrl = 'https://nutricore.co.kr/sopmal/main/selectClause?stplatDvCd=02';
} else if(sopmalCd == 'DT') {
privacyPolicyUrl = 'https://doctorlean.co.kr/sopmal/main/selectClause?stplatDvCd=02';
}
// 모달 HTML 생성
var modalHtml = `
<div id="healthDataAgreementModal" class="microModal microAlert" aria-modal="true">
<div class="microModal__overlay" tabindex="-1">
<div class="microModal__container" role="dialog" aria-modal="true">
<div class="microModal__body">
<div class="microModal__top">
<div class="modal-header__title">건강 데이터 이용 안내</div>
</div>
<div class="microModal__content ck-content">
<p>본 앱은 만보기 서비스를 위해 Google Fit에서 제공하는 걸음 수 데이터를 수집·이용합니다.</p>
<br>
<p>수집된 데이터는 만보기 기능 제공, 이동거리 계산, 마일리지 적립 조건 판단에만 사용되며, 다른 목적으로는 이용되지 않습니다.</p>
<br>
<p>자세한 내용은 <a href="${privacyPolicyUrl}" target="_blank" style="color: #007bff; text-decoration: underline;">개인정보처리방침</a>에서 확인하실 수 있습니다.</p>
</div>
</div>
<div class="microModal__btnContainer">
<button class="microModal__btn microModal__btn-primary" onclick="fn_confirm_health_data_agreement()">확인</button>
</div>
</div>
</div>
</div>
`;
// 기존 모달이 있다면 제거
$('#healthDataAgreementModal').remove();
// 모달 HTML을 body에 추가
$('body').append(modalHtml);
// MicroModal 초기화 (혹시 없다면)
if(typeof MicroModal !== 'undefined') {
MicroModal.init({
disableScroll: true,
awaitCloseAnimation: true
});
}
// 모달 표시
if(typeof openModal === 'function') {
openModal('healthDataAgreementModal');
} else if(typeof MicroModal !== 'undefined') {
MicroModal.show('healthDataAgreementModal');
}
console.log('[Shown] Health data agreement modal displayed.');
}
// 건강 데이터 이용 안내 모달 확인 버튼 클릭
function fn_confirm_health_data_agreement() {
console.log('[Called] fn_confirm_health_data_agreement()');
// sessionStorage에 표시 완료 저장
sessionStorage.setItem("hdaShown", "Y");
// 모달 닫기
fn_close_health_data_agreement_modal();
console.log('[Confirmed] Health data agreement confirmed and saved.');
}
// 건강 데이터 이용 안내 모달 닫기
function fn_close_health_data_agreement_modal() {
console.log('[Called] fn_close_health_data_agreement_modal()');
// 모달 닫기
if(typeof closeModal === 'function') {
closeModal('healthDataAgreementModal');
} else if(typeof MicroModal !== 'undefined') {
MicroModal.close('healthDataAgreementModal');
}
// DOM에서 모달 제거
setTimeout(function() {
$('#healthDataAgreementModal').remove();
}, 300); // 애니메이션 완료 후 제거
}

View File

@@ -0,0 +1,650 @@
/**
* Conrev.js
*
* 리뷰 슬라이더 및 리뷰 보기 보상 기능을 관리하는 모듈
* 사용자가 일정 시간 동안 리뷰를 보면 마일리지를 적립해주는 기능 구현
*/
const Conrev = (function() {
// 모듈 내부 변수 선언
let timer; // 리뷰 시간 카운트다운 타이머
let remainingTime; // 리뷰를 봐야 하는 남은 시간(초)
let currentEventId; // 현재 선택된 리뷰의 고유 ID
let startTime; // 리뷰 보기 시작 시간
let reviewCheckInterval; // 리뷰 팝업이 계속 열려있는지 확인하는 타이머
let currentSlideIndex = 0; // 현재 보고 있는 슬라이드 인덱스
let reviewData = []; // 서버에서 받아온 리뷰 데이터 저장 배열
let lastViewedSlideIndex = 0; // 마지막으로 본 슬라이드 인덱스 (중복 마일리지 적립 방지용)
let reviewLayer; // 리뷰 팝업 레이어 요소
let autoSlideTimer; // 자동 슬라이드 타이머
let mileageAwarded = false; // 마일리지 적립 완료 플래그 추가
/**
* 토스트 메시지 표시 함수
* @param {string} message - 표시할 메시지
*/
function showToast(message) {
// 기존에 토스트 요소가 있으면 제거
$('.toastPop').remove();
// 토스트 요소 생성 및 추가 (z-index 1001로 설정하여 모달 위에 표시)
const $toast = $('<div class="toastPop" style="z-index: 1001; position: fixed;">' + message + '</div>');
$('body').append($toast);
// 토스트 표시 및 자동 제거
setTimeout(function() {
$toast.addClass('show');
setTimeout(function() {
$toast.removeClass('show');
// 애니메이션 완료 후 요소 제거
setTimeout(function() {
$toast.remove();
}, 300);
}, 2000);
}, 100);
}
/**
* 마일리지 보상 정보 가져오기
* 현재 슬라이드의 리뷰에 맞는 보상 정보를 가져옴
* @param {string|number} bnfInnb - 리뷰 혜택 고유 번호 (선택사항)
*/
function fetchRewardInfo(bnfInnb) {
// 현재 슬라이드의 bnfInnb 사용하거나 파라미터로 받은 값 사용
const targetBnfInnb = bnfInnb || (reviewData.length > currentSlideIndex ? reviewData[currentSlideIndex].bnfInnb : '');
if (targetBnfInnb) {
$.ajax({
url: '/sopmal/bnft/getRewardInfo',
method: 'POST',
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify({ bnfInnb: targetBnfInnb }),
success: function(response) {
if (response.success && response.mlgRwdAmt) {
// JSP의 금액 부분을 동적으로 업데이트
$('.topupPoint b').text(response.mlgRwdAmt + '원');
console.log('마일리지 보상 금액:', response.mlgRwdAmt);
} else {
console.error('마일리지 보상 정보를 가져오는데 실패했습니다.');
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('마일리지 보상 정보 요청 중 오류 발생:', textStatus, errorThrown);
}
});
}
}
/**
* 슬라이더 초기화 함수
* 페이지 로드 시 호출되어 리뷰 슬라이더의 모든 기능을 설정
*/
function initializeSlider() {
// hidden input에서 JSON 형태의 리뷰 데이터 가져오기
try {
let rawData = $('#reviewDataInput').val();
// JSON 파싱을 위해 백슬래시를 이중 백슬래시로 변환 (이스케이프 처리)
rawData = rawData.replace(/\\(?!["])/g, "\\\\");
reviewData = JSON.parse(rawData) || [];
} catch (e) {
console.error('리뷰 데이터 파싱 실패:', e);
reviewData = [];
}
// 슬라이더 요소 참조 가져오기
const $slider = $('.reviewSlider ul'); // 슬라이더 컨테이너
const $slides = $('.reviewSlider li'); // 각 슬라이드 항목
const slideCount = $slides.length; // 전체 슬라이드 개수
let currentSlide = 0; // 현재 슬라이드 인덱스
// 페이지네이션 도트 동적 생성 (슬라이드 개수만큼 도트 생성)
const $pagination = $('.pagination');
for (let i = 0; i < slideCount; i++) {
$pagination.append('<div class="dot" data-slide="' + i + '"></div>');
}
// 첫 번째 도트를 활성화 상태로 설정
$pagination.find('.dot').first().addClass('active');
/**
* 슬라이드 이동 함수
* 현재 슬라이드 인덱스에 따라 슬라이더 위치와 페이지네이션 상태 업데이트
*/
function moveSlide() {
// 슬라이더 위치 조정 (CSS margin-left 사용하여 수평 이동)
$slider.css('margin-left', -currentSlide * 100 + '%');
// 현재 슬라이드에 해당하는 도트만 활성화
$pagination.find('.dot').removeClass('active').eq(currentSlide).addClass('active');
// 글로벌 변수 업데이트
currentSlideIndex = currentSlide;
// 리뷰 버튼 업데이트 (현재 슬라이드에 맞는 리뷰 데이터 연결)
updateReviewButton();
// 현재 슬라이드에 맞는 마일리지 보상 정보 업데이트
fetchRewardInfo();
}
// 슬라이드 이미지 로드 함수 호출 - JSP에서 CDN으로 이미 설정되므로 주석 처리
// loadImages();
/**
* 페이지네이션 도트 클릭 이벤트 핸들러
* 사용자가 특정 도트를 클릭하면 해당 슬라이드로 이동
*/
$pagination.on('click', '.dot', function() {
const newSlideIndex = $(this).data('slide');
currentSlide = newSlideIndex;
moveSlide();
// 도트 클릭으로 슬라이드 변경시에는 마일리지 적립하지 않음 (리뷰 보기를 통해서만 적립)
});
/**
* 마우스/터치 드래그 이벤트 처리
* 슬라이더를 드래그하여 다음/이전 슬라이드로 이동할 수 있게 함
*/
let isDragging = false; // 드래그 중인지 상태 플래그
let startPos = 0; // 드래그 시작 위치
let endPos = 0; // 드래그 종료 위치
// 드래그 시작 이벤트 (마우스다운 또는 터치스타트)
// $slider 대신 더 넓은 영역인 .revieBox에 이벤트 적용
$('.revieBox').on('mousedown touchstart', function(e) {
isDragging = true;
// 마우스 또는 터치 이벤트의 시작 위치 저장
startPos = e.pageX || e.originalEvent.touches[0].pageX;
});
// 드래그 종료 이벤트 (마우스업 또는 터치엔드)
$(document).on('mouseup touchend', function(e) {
if (isDragging) {
// 마우스 또는 터치 이벤트의 종료 위치 저장
endPos = e.pageX || e.originalEvent.changedTouches[0].pageX;
const diff = endPos - startPos;
// 드래그 거리가 충분히 클 경우에만 슬라이드 전환 (50px 이상)
if (Math.abs(diff) > 50) {
// 오른쪽으로 드래그: 이전 슬라이드, 왼쪽으로 드래그: 다음 슬라이드
currentSlide += (diff > 0) ? -1 : 1;
// 슬라이드 인덱스 범위 제한 (순환 슬라이더)
if (currentSlide < 0) {
currentSlide = slideCount - 1; // 첫 슬라이드에서 이전으로 가면 마지막으로
} else if (currentSlide >= slideCount) {
currentSlide = 0; // 마지막 슬라이드에서 다음으로 가면 처음으로
}
moveSlide();
}
isDragging = false;
}
});
// 이미지 드래그 방지 (브라우저 기본 동작 차단)
$('.reviewSlider img').on('dragstart', function(event) {
event.preventDefault();
});
// 슬라이드 클릭 시 링크 이동 처리 - 드래그와 구분하여 처리
$slides.on('click', function(e) {
// 더보기 버튼이나 그 하위 요소를 클릭한 경우 제외
if ($(e.target).closest('.reviewText-toggle').length > 0) {
return;
}
// 드래그 중이 아닐 때만 링크 이동
if (!isDragging) {
const link = $(this).data('link');
if (link) {
window.location.href = link;
}
}
});
// 초기 리뷰 버튼 설정
updateReviewButton();
// 첫 번째 슬라이드의 마일리지 보상 정보 가져오기 (페이지 로딩시)
fetchRewardInfo();
/**
* 자동 슬라이드 시작 함수
* 일정 시간마다 자동으로 다음 슬라이드로 이동
*/
function startAutoSlide() {
autoSlideTimer = setInterval(function() {
// 다음 슬라이드로 순환 이동 (마지막 슬라이드에서는 처음으로)
currentSlide = (currentSlide + 1) % slideCount;
moveSlide();
}, 5000); // 5초마다 슬라이드 자동 전환
}
/**
* 자동 슬라이드 정지 함수
*/
function stopAutoSlide() {
clearInterval(autoSlideTimer);
}
// 슬라이더에 마우스 오버/아웃 시 자동 슬라이드 제어
// .reviewSlider 대신 .revieBox에 적용
$('.revieBox').hover(
function() { stopAutoSlide(); }, // 마우스 오버 시 자동 슬라이드 정지
function() { startAutoSlide(); } // 마우스 아웃 시 자동 슬라이드 재시작
);
// 초기 자동 슬라이드 시작
startAutoSlide();
// 더보기 기능 초기화
initializeReadMore();
}
/**
* 리뷰 모달 생성 함수
* 리뷰 버튼 클릭 시 호출되어 모달 창 요소를 생성
*/
function createReviewModal() {
// 기존 모달이 있다면 제거
$('#review-modal').remove();
// 모달 HTML 구조 생성
const modalHTML = `
<div class="reviewModal" id="review-modal" style="display:none;">
<div class="reviewModal__overlay" style="position:fixed; top:0; left:0; right:0; bottom:0; background-color:rgba(0,0,0,0.6); z-index:999;">
<div class="reviewModal__container" style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:90%; max-width:800px; max-height:80vh; background-color:#fff; border-radius:5px; overflow:hidden;">
<div class="reviewModal__header" style="display:flex; justify-content:space-between; align-items:center; padding:15px; border-bottom:1px solid #e0e0e0;">
<h2 class="reviewModal__title" style="margin:0; font-size:18px; font-weight:bold;">리뷰 보기</h2>
<button class="reviewModal__close" style="background:transparent; border:0; width:24px; height:24px; position:relative; cursor:pointer;">
<span style="position:absolute; width:2px; height:16px; background-color:#666; top:4px; left:11px; transform:rotate(45deg);"></span>
<span style="position:absolute; width:2px; height:16px; background-color:#666; top:4px; left:11px; transform:rotate(-45deg);"></span>
</button>
</div>
<div class="reviewModal__body" style="max-height:calc(80vh - 130px); overflow:hidden;">
<div class="reviewModal__content">
<div class="review-iframe-container" style="width:100%; height:calc(80vh - 170px); min-height:300px; overflow:hidden;">
<iframe id="reviewIframe" src="about:blank" frameborder="0" style="width:100%; height:100%; border:none;"></iframe>
</div>
<div class="review-timer" style="padding:10px 15px; text-align:center; background-color:#f8f8f8; border-top:1px solid #e0e0e0; font-weight:bold;">
남은 시간: <span id="popupCountdown">0</span>초
</div>
</div>
</div>
<div class="reviewModal__btnContainer" style="padding:15px; border-top:1px solid #e0e0e0; text-align:right;">
<button id="review-modal-close-btn" style="padding:8px 16px; border-radius:4px; cursor:pointer; border:none; font-weight:bold; background-color:#f1f1f1; color:#333;">닫기</button>
</div>
</div>
</div>
</div>
`;
// 모달을 페이지에 추가
$('body').append(modalHTML);
// 모달 닫기 버튼에 이벤트 핸들러 직접 연결
$('#review-modal-close-btn, .reviewModal__close').on('click', function() {
closeReviewModal();
});
}
/**
* 리뷰 버튼 업데이트 함수
* 현재 슬라이드에 맞는 리뷰 정보로 리뷰 버튼의 이벤트 핸들러 업데이트
*/
function updateReviewButton() {
if (reviewData.length > currentSlideIndex) {
const currentReview = reviewData[currentSlideIndex];
// 기존 이벤트 핸들러 제거 후 새로운 핸들러 등록
$('#reviewButton').off('click').on('click', function() {
fetchReviewTime(currentReview.bnfInnb);
});
// 현재 슬라이드의 더보기 상태 리셋
const $currentSlide = $('.reviewSlider li').eq(currentSlideIndex);
const $reviewText = $currentSlide.find('.reviewText');
const $toggleBtn = $currentSlide.find('.reviewText-toggle');
if ($toggleBtn.length > 0) {
$reviewText.removeClass('expanded').addClass('collapsed');
$toggleBtn.text('더보기');
}
}
}
/**
* 리뷰 시간 정보 가져오기
* 서버에 리뷰 보기 필요 시간을 요청
* @param {string|number} bnfInnb - 리뷰 혜택 고유 번호
*/
function fetchReviewTime(bnfInnb) {
$.ajax({
url: '/sopmal/bnft/getReviewTime',
method: 'POST',
contentType: 'application/json;charset=UTF-8',
data: JSON.stringify({ bnfInnb: bnfInnb }),
success: function(response) {
// 서버에서 정상적으로 시간 정보를 받아왔을 경우
if (response.success && response.tmeSecnd) {
// 모달 생성 먼저
createReviewModal();
// 그 다음 리뷰 보기 시작 (ID, 링크URL, 필요 시간(초))
start(bnfInnb, reviewData[currentSlideIndex].linkUrl, response.tmeSecnd);
} else {
console.error('리뷰 시간 정보를 가져오는데 실패했습니다.');
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('리뷰 시간 정보 요청 중 오류 발생:', textStatus, errorThrown);
}
});
}
/**
* 리뷰 보기 시작 함수
* 리뷰 모달을 열고 타이머 시작
* @param {string|number} bnfInnb - 리뷰 혜택 고유 번호
* @param {string} linkUrl - 리뷰 페이지 URL
* @param {number} seconds - 리뷰를 봐야 하는 시간(초)
*/
function start(bnfInnb, linkUrl, seconds) {
// 현재 eventId를 명확히 설정하여 나중에 마일리지 적립시 올바른 ID 사용
currentEventId = bnfInnb;
remainingTime = seconds;
mileageAwarded = false;
startTime = new Date().getTime();
const endTime = startTime + (seconds * 1000);
$('#reviewIframe').attr('src', linkUrl);
$('#review-modal').fadeIn(300);
startTimer();
reviewCheckInterval = setInterval(function() {
if ($('#review-modal').is(':hidden')) {
clearInterval(reviewCheckInterval);
stopTimer();
// 토스트 메시지 제거하고 서버 응답에서만 표시
if (!mileageAwarded && remainingTime <= 0) {
awardMileage();
} else if (!mileageAwarded) {
cancelMileageAward();
}
}
}, 1000);
}
/**
* 리뷰 모달 닫기 함수
*/
function closeReviewModal() {
console.log('리뷰 모달 닫기 함수 호출');
$('#review-modal').fadeOut(300, function() {
$('#reviewIframe').attr('src', 'about:blank');
});
stopTimer();
// 토스트 메시지 제거하고 서버 응답에서만 표시
if (!mileageAwarded && remainingTime <= 0) {
awardMileage();
}
}
/**
* 타이머 시작 함수
* 리뷰 보기 시간 카운트다운 시작
*/
function startTimer() {
updateTimerDisplay(); // 초기 타이머 표시 업데이트
// 기존 타이머 있으면 정지
clearInterval(timer);
// 1초마다 카운트다운 실행
timer = setInterval(function() {
remainingTime--;
updateTimerDisplay();
// 시간이 모두 경과했을 경우
if (remainingTime <= 0) {
clearInterval(timer);
completeReviewViewing(); // 리뷰 보기 완료 처리
}
}, 1000);
}
/**
* 리뷰 보기 완료 처리 함수
*/
function completeReviewViewing() {
clearInterval(timer);
// 마일리지가 아직 적립되지 않았다면 적립 (토스트 메시지 제거)
if (!mileageAwarded && remainingTime <= 0) {
awardMileage(); // 서버 응답에서만 토스트 표시하도록 함
}
}
/**
* 마일리지 적립 함수
*/
function awardMileage() {
// 이미 적립되었다면 중복 호출 방지
if (mileageAwarded) {
console.log('마일리지 이미 적립됨 - 중복 호출 방지');
return;
}
// 적립 시작 시 플래그 설정
mileageAwarded = true;
// currentEventId (리뷰 보기 시작시 설정된 ID) 사용하여 정확한 마일리지 적립
$.ajax({
url: '/sopmal/bnft/awardConrevMileage',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ bnfInnb: currentEventId }),
success: function(response) {
let result;
try {
result = typeof response === 'string' ? JSON.parse(response) : response;
} catch (e) {
console.error('서버 응답 파싱 오류:', e);
mileageAwarded = false;
showToast('서버 응답 처리 중 오류가 발생했습니다.');
return;
}
// 서버 응답에 따른 정확한 메시지 표시
if (result.result === true) {
const successMessage = result.message || '리뷰 보기 완료! 마일리지가 적립되었습니다!';
showToast(successMessage);
} else {
// 실패 시에도 사용자에게 알림 (이미 적립된 경우 등)
const errorMessage = result.message || '마일리지 적립에 실패했습니다.';
showToast(errorMessage);
console.log('마일리지 적립 실패 - 오류 코드:', result.errorCode);
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('마일리지 적립 요청 중 오류 발생:', textStatus, errorThrown);
mileageAwarded = false;
showToast('마일리지 적립 요청 중 오류가 발생했습니다.');
}
});
}
/**
* 마일리지 적립 취소 함수
* 사용자가 리뷰 보기를 중단한 경우
*/
function cancelMileageAward() {
console.log('마일리지 적립이 취소되었습니다.');
// 필요한 경우 서버에 마일리지 적립 취소 요청을 보낼 수 있습니다.
}
/**
* 타이머 정지 함수
* 모든 타이머 및 인터벌 중지
*/
function stopTimer() {
if (timer) {
clearInterval(timer);
}
if (reviewCheckInterval) {
clearInterval(reviewCheckInterval);
}
if (autoSlideTimer) {
clearInterval(autoSlideTimer);
}
}
/**
* 이미지 로딩 함수
* 슬라이더의 모든 이미지를 로드
*/
function loadImages() {
// JSP에서 이미 CDN URL로 이미지 src가 설정되어 있으므로
// 추가적인 이미지 로딩 처리가 불필요함
console.log('이미지는 JSP에서 CDN URL로 이미 로드됨');
// 필요한 경우에만 이미지 로드 실패 처리
$('.reviewSlider .thum img').each(function(index) {
const $img = $(this);
// 이미지 로드 실패 시에만 처리
$img.on('error', function() {
console.error(`이미지 로드 실패: ${this.src}`);
this.src = '/mobile/images/image_placeholder.png'; // 모바일용 플레이스홀더로 변경
});
});
}
/**
* 앱에서 호출하는 콜백 함수
* 앱에서 리뷰 보기 완료 시 호출됨
* @param {string} url - 완료된 리뷰 URL
*/
function complete_open_blank_url(url) {
console.log('Review completed for URL:', url);
awardMileage(); // 마일리지 적립
}
/**
* 타이머 표시 업데이트 함수
* 남은 시간을 화면에 표시
*/
function updateTimerDisplay() {
// 카운트다운 타이머와 팝업 카운트다운에 남은 시간 표시
$('#countdown, #popupCountdown').text(remainingTime);
}
/**
* 더보기 기능 초기화
*/
function initializeReadMore() {
$('.reviewSlider li').each(function() {
const $reviewText = $(this).find('.reviewText');
const $slide = $(this);
// 원본 텍스트 저장
const originalText = $reviewText.html();
$reviewText.data('original-text', originalText);
// 높이 체크하여 더보기 버튼 필요 여부 판단
checkTextHeight($reviewText, $slide);
});
}
/**
* 텍스트 높이 체크 및 더보기 버튼 생성
*/
function checkTextHeight($reviewText, $slide) {
// 임시로 전체 텍스트 표시하여 높이 측정
$reviewText.removeClass('collapsed expanded');
const fullHeight = $reviewText.height();
// 3줄 높이로 변경: 3줄 * line-height(1.6) * font-size(16px) = 약 76.8px
const maxHeight = 3 * 1.6 * 16;
if (fullHeight > maxHeight) {
// 긴 텍스트인 경우 더보기 버튼 추가
$reviewText.addClass('collapsed');
// 더보기 버튼이 없으면 생성
if ($slide.find('.reviewText-toggle').length === 0) {
const $toggleBtn = $('<button class="reviewText-toggle">더보기</button>');
$reviewText.after($toggleBtn);
// 클릭 이벤트 바인딩 - 강화된 이벤트 방지
$toggleBtn.on('click touchend', function(event) {
// 모든 이벤트 전파 방지
event.stopPropagation();
event.stopImmediatePropagation();
event.preventDefault();
toggleReadMore($reviewText, $toggleBtn);
// 추가적인 이벤트 방지를 위한 return false
return false;
});
}
} else {
// 짧은 텍스트인 경우 더보기 버튼 제거
$slide.find('.reviewText-toggle').remove();
}
}
/**
* 더보기/접기 토글
*/
function toggleReadMore($reviewText, $toggleBtn) {
if ($reviewText.hasClass('collapsed')) {
// 펼치기
$reviewText.removeClass('collapsed').addClass('expanded');
$toggleBtn.text('접기');
} else {
// 접기
$reviewText.removeClass('expanded').addClass('collapsed');
$toggleBtn.text('더보기');
// 접을 때 해당 위치로 스크롤
$reviewText[0].scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
// 모듈의 공개 인터페이스
return {
init: initializeSlider, // 슬라이더 초기화
start: start, // 리뷰 보기 시작
stop: stopTimer, // 타이머 정지
loadImages: loadImages, // 이미지 로드
showToast: showToast, // 토스트 메시지 표시 함수 추가
fetchRewardInfo: fetchRewardInfo // 마일리지 보상 정보 가져오기 함수 추가
};
})();
/**
* DOM이 로드된 후 슬라이더 초기화
* 페이지 로드 완료 시 Conrev 모듈 초기화
*/
$(document).ready(function() {
Conrev.init();
// 페이지 로드 시 기존 alert 메시지가 있다면 토스트로 변환
if (typeof alertMessage !== 'undefined' && alertMessage) {
Conrev.showToast(alertMessage);
}
});
/**
* 창이 닫힐 때 타이머 정지
* 페이지 이탈 시 리소스 정리
*/
$(window).on('beforeunload', function() {
Conrev.stop();
});

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');
}
});

View File

@@ -0,0 +1,269 @@
/**
* Discshar.js
*
* 할인 공유 기능을 관리하는 모듈
* 할인 슬라이더, 공유 링크 생성, 공유 결과 확인 등의 기능 제공
*/
const Discshar = (function() {
// 모듈 내부 변수 선언
let $slider; // 슬라이더 컨테이너 (ul 요소)
let $slides; // 슬라이드 아이템들 (li 요소들)
let slideWidth; // 각 슬라이드 너비 (마진 포함)
let currentSlide; // 현재 표시 중인 슬라이드 인덱스
let totalSlides; // 전체 슬라이드 개수 (복제 슬라이드 포함)
let slideInterval; // 자동 슬라이드 간격 (밀리초)
let autoSlideTimeout; // 자동 슬라이드 타이머 ID
/**
* 모듈 초기화 함수
* 슬라이더 초기화 및 이벤트 바인딩
*/
function init() {
initSlider(); // 슬라이더 요소 초기화
bindEvents(); // 이벤트 리스너 등록
}
/**
* 슬라이더 초기화 함수
* 슬라이더 요소 참조 가져오기, 크기 설정, 무한 루프를 위한 슬라이드 복제
*/
function initSlider() {
// 슬라이더 요소 참조 가져오기
$slider = $('.discountSlider ul');
$slides = $('.discountSlider li');
// 슬라이드 너비 계산 (요소 너비 + 마진)
slideWidth = $slides.outerWidth() + parseInt($slides.css('margin-right'));
// 초기 슬라이드 인덱스 및 총 개수 설정
currentSlide = 0;
totalSlides = $slides.length;
slideInterval = 3000; // 슬라이드 자동 전환 간격 (밀리초)
// 무한 루프 슬라이드를 위한 복제 요소 추가
// 첫 번째 슬라이드 앞에 마지막 슬라이드 복제본 추가
$slides.first().before($slides.last().clone());
// 마지막 슬라이드 뒤에 첫 번째 슬라이드 복제본 추가
$slides.last().after($slides.first().clone());
// 복제된 슬라이드를 포함하여 총 슬라이드 수 업데이트
totalSlides += 2;
// 모든 슬라이드를 포함할 수 있도록 컨테이너 너비 설정
$slider.width(totalSlides * slideWidth);
// 초기 위치를 첫 번째 실제 슬라이드로 설정 (복제 슬라이드 건너뛰기)
$slider.css('margin-left', -slideWidth);
// 자동 슬라이드 시작
startAutoSlide();
}
/**
* 이벤트 리스너 등록 함수
* 슬라이더 조작을 위한 다양한 이벤트 핸들러 연결
*/
function bindEvents() {
// 슬라이더에 마우스 이벤트 및 터치 이벤트 바인딩
$slider.on('mouseenter', stopAutoSlide) // 마우스 진입 시 자동 슬라이드 정지
.on('mouseleave', startAutoSlide) // 마우스 이탈 시 자동 슬라이드 재시작
.on('mousedown touchstart', startDrag) // 드래그 시작
.on('mousemove touchmove', drag) // 드래그 중
.on('mouseup touchend', endDrag); // 드래그 종료
// 문서 전체에 마우스업/터치엔드 이벤트 리스너 추가 (드래그 도중 슬라이더 밖으로 나갔을 때 대비)
$(document).on('mouseup touchend', endDrag);
// 이미지 기본 드래그 방지 (브라우저의 기본 동작 차단)
$('.discountSlider img').on('dragstart', function(event) { event.preventDefault(); });
}
/**
* 자동 슬라이드 시작 함수
* 일정 간격으로 다음 슬라이드로 자동 이동
*/
function startAutoSlide() {
autoSlideTimeout = setInterval(nextSlide, slideInterval);
}
/**
* 자동 슬라이드 정지 함수
* 자동 슬라이드 타이머 중지
*/
function stopAutoSlide() {
clearInterval(autoSlideTimeout);
}
/**
* 다음 슬라이드로 이동 함수
* 현재 슬라이드 인덱스 증가 후 슬라이드 이동
*/
function nextSlide() {
currentSlide++;
moveSlide();
}
/**
* 슬라이드 이동 함수
* 현재 슬라이드 인덱스에 맞게 슬라이더 위치 애니메이션
* 무한 루프 슬라이딩을 위한 위치 보정 처리 포함
*/
function moveSlide() {
// 애니메이션으로 슬라이더 위치 이동
$slider.animate({'margin-left': -currentSlide * slideWidth}, 500, function() {
// 애니메이션 완료 후 위치 보정 (무한 루프 구현)
if (currentSlide >= totalSlides - 1) {
// 마지막 슬라이드(복제본)에 도달하면 첫 번째 실제 슬라이드로 위치 즉시 이동
currentSlide = 1;
$slider.css('margin-left', -slideWidth);
} else if (currentSlide <= 0) {
// 첫 번째 슬라이드(복제본)에 도달하면 마지막 실제 슬라이드로 위치 즉시 이동
currentSlide = totalSlides - 2;
$slider.css('margin-left', -currentSlide * slideWidth);
}
});
}
// 드래그 관련 상태 변수
let isDragging = false; // 드래그 중인지 여부
let startPos = 0; // 드래그 시작 위치 (X좌표)
let currentPos = 0; // 현재 슬라이더 위치
/**
* 드래그 시작 처리 함수
* 드래그 시작 위치와 현재 슬라이더 위치 기록
* @param {Event} e - 마우스 또는 터치 이벤트
*/
function startDrag(e) {
isDragging = true;
// 마우스 또는 터치 이벤트의 X좌표 가져오기
startPos = e.pageX || e.originalEvent.touches[0].pageX;
// 현재 슬라이더 위치 기록
currentPos = $slider.css('margin-left');
// 자동 슬라이드 정지
stopAutoSlide();
}
/**
* 드래그 중 처리 함수
* 마우스/터치 위치에 따라 슬라이더 위치 실시간 업데이트
* @param {Event} e - 마우스 또는 터치 이벤트
*/
function drag(e) {
if (!isDragging) return; // 드래그 중이 아니면 무시
// 현재 마우스/터치 위치 가져오기
let currentMousePos = e.pageX || e.originalEvent.touches[0].pageX;
// 시작 위치와의 차이 계산
let diff = currentMousePos - startPos;
// 슬라이더 위치 실시간 업데이트
$slider.css('margin-left', parseInt(currentPos) + diff);
}
/**
* 드래그 종료 처리 함수
* 드래그 방향과 거리에 따라 슬라이드 이동 또는 원위치
* @param {Event} e - 마우스 또는 터치 이벤트
*/
function endDrag(e) {
if (!isDragging) return; // 드래그 중이 아니면 무시
isDragging = false;
// 마우스/터치 종료 위치 가져오기
let endPos = e.pageX || e.originalEvent.changedTouches[0].pageX;
// 시작 위치와의 차이 계산
let diff = endPos - startPos;
// 슬라이드 전환을 위한 최소 드래그 거리(50px) 이상이면 슬라이드 전환
if (Math.abs(diff) > 50) {
// 오른쪽으로 드래그: 이전 슬라이드, 왼쪽으로 드래그: 다음 슬라이드
currentSlide += (diff > 0) ? -1 : 1;
moveSlide();
} else {
// 최소 거리 미만이면 원래 위치로 되돌리기
$slider.animate({'margin-left': currentPos}, 200);
}
// 자동 슬라이드 재시작
startAutoSlide();
}
/**
* 공유 링크 생성 함수
* 할인 공유를 위한 고유 링크 생성 및 모달에 표시
* @param {number} bnfInnb - 혜택 고유 번호
*/
function copyLink(bnfInnb) {
$.ajax({
url: '/sopmal/bnft/copyDiscsharLink', // 공유 링크 생성 API
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ bnfInnb: bnfInnb }), // 혜택 고유 번호 전달
success: function(response) {
if (response.success && response.links && response.links.length > 0) {
// 성공 시 첫 번째 링크 정보 사용
const link = response.links[0];
// 생성된 고유 URL을 입력 필드에 설정
$('#linkToCopy').val(link.uniqueUrl);
// 링크 만료일 표시 형식 변환
const validDate = new Date(link.validDt);
const formattedDate = validDate.toISOString().split('T')[0];
$('#linkExpiryDate').text(formattedDate);
// 링크 복사 모달 표시
MicroModal.show('modal-1');
} else {
// 링크 정보 없음 알림
alert('유효한 링크 정보가 없습니다.');
}
},
error: function(xhr, status, error) {
// 통신 오류 처리
console.error('서버 통신 오류:', error);
alert('서버 통신 중 오류가 발생했습니다.');
}
});
}
/**
* 공유 결과 확인 함수
* 사용자의 할인 공유 결과에 따라 적절한 모달 표시
*/
function showShareResult() {
$.ajax({
url: '/sopmal/bnft/checkDiscsharResult', // 공유 결과 확인 API
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({}),
success: function(response) {
// 공유 링크로 구매한 사람이 있는지 여부에 따라 다른 모달 표시
if (response.hasPurchase) {
// 구매자가 있는 경우 (성공)
MicroModal.show('modal-3');
} else {
// 구매자가 없는 경우 (아직 공유 중)
MicroModal.show('modal-2');
}
},
error: function() {
// 통신 오류 처리
alert('공유 결과를 확인하는데 실패했습니다.');
}
});
}
// 모듈의 공개 인터페이스
return {
init: init, // 모듈 초기화
copyLink: copyLink, // 공유 링크 생성
showShareResult: showShareResult // 공유 결과 확인
};
})();
/**
* 문서 로드 완료 시 실행
* Discshar 모듈 초기화
*/
$(document).ready(function() {
Discshar.init();
});

View File

@@ -0,0 +1,351 @@
/**
* 이벤트 보기 관련 기능 모듈
*/
const EventView = (function() {
// 비공개 변수
let _bnfInnb = 0; // 혜택 고유번호
let _eventDesc = ''; // 이벤트 설명
let _loadingTime = 0; // 로딩 시간(초)
let _mileageAmount = 0; // 마일리지 금액
let _timer = null; // 타이머 객체
let _countdownValue = 0; // 카운트다운 값
let _isParticipated = false;// 참여 여부
let _eventUrl = ''; // 이벤트 URL
let _reviewCheckInterval; // 모달이 열려있는지 확인하는 인터벌
/**
* 토스트 메시지 표시 함수
* @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 _createEventModal() {
// 기존 모달이 있다면 제거
$('#event-modal').remove();
// 모달 HTML 구조 생성
const modalHTML = `
<div class="eventModal" id="event-modal" style="display:none;">
<div class="eventModal__overlay" style="position:fixed; top:0; left:0; right:0; bottom:0; background-color:rgba(0,0,0,0.6); z-index:999;">
<div class="eventModal__container" style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:90%; max-width:800px; max-height:80vh; background-color:#fff; border-radius:5px; overflow:hidden;">
<div class="eventModal__header" style="display:flex; justify-content:space-between; align-items:center; padding:15px; border-bottom:1px solid #e0e0e0;">
<h2 class="eventModal__title" style="margin:0; font-size:18px; font-weight:bold;">이벤트 보기</h2>
<button class="eventModal__close" style="background:transparent; border:0; width:24px; height:24px; position:relative; cursor:pointer;">
<span style="position:absolute; width:2px; height:16px; background-color:#666; top:4px; left:11px; transform:rotate(45deg);"></span>
<span style="position:absolute; width:2px; height:16px; background-color:#666; top:4px; left:11px; transform:rotate(-45deg);"></span>
</button>
</div>
<div class="eventModal__body" style="max-height:calc(80vh - 130px); overflow:hidden;">
<div class="eventModal__content">
<div class="event-iframe-container" style="width:100%; height:calc(80vh - 170px); min-height:300px; overflow:hidden;">
<iframe id="eventIframe" src="about:blank" frameborder="0" style="width:100%; height:100%; border:none;"></iframe>
</div>
<div class="event-timer" style="padding:10px 15px; text-align:center; background-color:#f8f8f8; border-top:1px solid #e0e0e0; font-weight:bold;">
남은 시간: <span id="popupCountdown">0</span>초
</div>
</div>
</div>
<div class="eventModal__btnContainer" style="padding:15px; border-top:1px solid #e0e0e0; text-align:right;">
<button id="event-modal-close-btn" style="padding:8px 16px; border-radius:4px; cursor:pointer; border:none; font-weight:bold; background-color:#f1f1f1; color:#333;">닫기</button>
</div>
</div>
</div>
</div>
`;
// 모달을 페이지에 추가
$('body').append(modalHTML);
// 모달 닫기 버튼에 이벤트 핸들러 직접 연결
$('#event-modal-close-btn, .eventModal__close').on('click', function() {
_closeEventModal();
});
}
/**
* 이벤트 모달 닫기 함수
*/
function _closeEventModal() {
console.log('이벤트 모달 닫기 함수 호출');
// jQuery를 사용하여 모달 직접 닫기
$('#event-modal').fadeOut(300, function() {
// 모달이 완전히 닫힌 후 iframe 소스 비우기
$('#eventIframe').attr('src', 'about:blank');
});
// 타이머 정지
if (_timer) {
clearInterval(_timer);
}
if (_reviewCheckInterval) {
clearInterval(_reviewCheckInterval);
}
// 남은 시간이 없으면 마일리지 적립
if (_countdownValue <= 0) {
_participateEvent();
_showToast('이벤트 보기 완료! 마일리지가 적립되었습니다.');
}
}
// 타이머 시작 함수
function _startTimer() {
// 이미 참여했는지 확인
_checkParticipationAvailable().then(function(available) {
if (!available) {
_isParticipated = true;
showCustomAlert("이미 참여한 이벤트입니다.");
// 이미 참여한 경우 UI 업데이트
_updateUIForCompletedEvent();
return;
}
// 모달 생성
_createEventModal();
// URL을 iframe에 로드
$('#eventIframe').attr('src', _eventUrl);
$('#event-modal').fadeIn(300);
// 카운트다운 표시 업데이트
_countdownValue = _loadingTime;
_updateTimerDisplay();
// 카운트다운 타이머 시작
_timer = setInterval(function() {
_countdownValue--;
_updateTimerDisplay();
if (_countdownValue <= 0) {
clearInterval(_timer);
_participateEvent();
_showToast('이벤트 보기 완료! 마일리지가 적립되었습니다.');
}
}, 1000);
// 모달이 열려있는지 확인하는 인터벌 설정
_reviewCheckInterval = setInterval(function() {
// 모달이 닫혀있으면 처리
if ($('#event-modal').is(':hidden')) {
clearInterval(_reviewCheckInterval);
clearInterval(_timer);
}
}, 1000);
});
}
/**
* 타이머 표시 업데이트 함수
*/
function _updateTimerDisplay() {
$('#popupCountdown').text(_countdownValue);
}
/**
* 이벤트 참여 완료 후 UI 업데이트 함수
*/
function _updateUIForCompletedEvent() {
// 해당 이벤트의 마일리지 표시 요소를 찾아 '적립완료'로 변경
$('.li_mile[data-bnf-innb="' + _bnfInnb + '"]').addClass('on').html('<span>적립완료</span>');
}
/**
* 페이지 초기화 함수
* 페이지 로드 시 이미 참여한 이벤트 표시 업데이트
*/
function _initializePage() {
// 모든 이벤트 요소 확인
$('.li_mile').each(function() {
const bnfInnb = $(this).data('bnf-innb');
if (bnfInnb) {
// 각 이벤트마다 참여 여부 확인
_checkEventParticipation(bnfInnb);
}
});
}
/**
* 특정 이벤트 참여 여부 확인 및 UI 업데이트
* @param {number|string} bnfInnb - 이벤트 고유 번호
*/
function _checkEventParticipation(bnfInnb) {
$.ajax({
url: '/sopmal/bnft/checkParticipationAvailable',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: bnfInnb
}),
success: function(response) {
try {
if (response && response.resBody && response.resBody.result) {
const resultObj = JSON.parse(response.resBody.result);
// 참여 불가능(이미 참여)한 경우 UI 업데이트
if (!resultObj.available) {
_updateUIForCompletedEvent(bnfInnb);
}
}
} catch (e) {
console.error('응답 처리 중 오류 발생:', e);
}
},
error: function(error) {
console.error('이벤트 참여 여부 확인 실패:', error);
}
});
}
// 참여 가능 여부 확인
function _checkParticipationAvailable() {
return new Promise(function(resolve, reject) {
$.ajax({
url: '/sopmal/bnft/checkParticipationAvailable',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: _bnfInnb
}),
success: function(response) {
try {
// 응답 구조에 맞게 처리
if (response && response.resBody && response.resBody.result) {
// JSON 문자열을 객체로 파싱
const resultObj = JSON.parse(response.resBody.result);
resolve(resultObj.available);
} else {
console.error('응답 형식이 올바르지 않습니다:', response);
resolve(false);
}
} catch (e) {
console.error('응답 처리 중 오류 발생:', e);
resolve(false);
}
},
error: function(error) {
console.error('참여 가능 여부 확인 실패:', error);
resolve(false);
}
});
});
}
// 이벤트 참여 및 마일리지 적립
function _participateEvent() {
if (_isParticipated) return;
$.ajax({
url: '/sopmal/bnft/participateEvent',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: _bnfInnb
}),
success: function(response) {
try {
// 응답 구조에 맞게 처리
if (response && response.resBody && response.resBody.result) {
// JSON 문자열을 객체로 파싱
const resultObj = JSON.parse(response.resBody.result);
if (resultObj.success) {
_isParticipated = true;
// UI 업데이트: 마일리지 -> 적립완료
_updateUIForCompletedEvent();
// 토스트 메시지 표시
_showToast('이벤트 보기 완료! 마일리지가 적립되었습니다.');
} else {
console.log('resultObj.message', resultObj.message);
showCustomAlert('마일리지 적립에 실패했습니다.');
}
} else {
console.error('응답 형식이 올바르지 않습니다:', response);
showCustomAlert('마일리지 적립에 실패했습니다.');
}
} catch (e) {
console.error('응답 처리 중 오류 발생:', e);
showCustomAlert('마일리지 적립 중 오류가 발생했습니다.');
}
},
error: function(error) {
console.error('이벤트 참여 실패:', error);
showCustomAlert('이벤트 참여 중 오류가 발생했습니다.');
}
});
}
// 이벤트 시작 함수
function start(bnfInnb, eventDesc, loadingTime, mileageAmount, eventUrl, startDate, endDate) {
// 날짜 검증 추가
if (startDate && endDate) {
const now = new Date();
const eventStart = new Date(startDate);
const eventEnd = new Date(endDate);
if (now < eventStart) {
_showToast("아직 시작되지 않은 이벤트입니다.");
return;
}
if (now > eventEnd) {
_showToast("종료된 이벤트입니다.");
return;
}
}
_bnfInnb = bnfInnb;
_eventDesc = eventDesc;
_loadingTime = loadingTime;
_mileageAmount = mileageAmount;
_eventUrl = eventUrl || '';
_isParticipated = false;
// 타이머 종료
if (_timer) {
clearInterval(_timer);
}
// 타이머 시작
_startTimer();
}
// 페이지 초기화 실행
$(document).ready(function() {
_initializePage();
});
// 공개 API
return {
start: start,
participateEvent: _participateEvent,
showToast: _showToast,
init: _initializePage // 페이지 초기화 함수 노출
};
})();

View File

@@ -0,0 +1,355 @@
/**
* Hlthset.js
*
* 건강 관심분야 설정 기능을 관리하는 모듈
* 사용자가 건강 관심분야를 선택하고 저장하여 맞춤 정보 및 마일리지를 받을 수 있는 기능 구현
*/
$(document).ready(function() {
// 전역 변수 - 관심분야 목록 데이터
var interestAreas = [];
// 전역 변수 - 기본 정보 (마일리지 등)
var basicInfo = {};
// 전역 변수 - 혜택 고유 번호
var bnfInnb = null;
// CDN URL (JSP에서 전달받은 값 사용)
var cdnUrl = '${cdnurl}';
/**
* 관심건강분야 설정 정보 조회 함수
* 서버에서 관심분야 목록 데이터를 가져옴
*/
function loadHealthInterestInfo() {
console.log('=== loadHealthInterestInfo 시작 ===');
// 서버 API 호출 - 빈 값 또는 null로 요청
$.ajax({
url: '/sopmal/bnft/selectHealthInterestInfo', // 관심건강분야 설정 정보 조회 API
method: 'POST', // HTTP 메서드
contentType: 'application/json', // 요청 콘텐츠 타입
data: JSON.stringify({}), // 빈 객체로 요청
beforeSend: function() {
console.log('API 요청 시작 - URL:', '/sopmal/bnft/selectHealthInterestInfo');
console.log('요청 데이터:', {});
},
success: function(response) {
console.log('=== API 응답 성공 ===');
console.log('전체 응답 데이터:', response);
try {
// 응답에서 관심분야 데이터 추출 및 전역 변수에 저장
if (response && response.resBody && response.resBody.result) {
// JSON 문자열을 파싱
const resultData = JSON.parse(response.resBody.result);
console.log('파싱된 resultData:', resultData);
// 기본 정보 (마일리지, 이벤트 설명 등) 저장
if (resultData.basicInfo) {
basicInfo = resultData.basicInfo;
console.log('기본 정보:', basicInfo);
// bnfInnb 값을 전역 변수에 저장
if (basicInfo.bnfInnb) {
bnfInnb = basicInfo.bnfInnb;
console.log('bnfInnb 값 설정:', bnfInnb);
}
// 마일리지 정보 화면에 업데이트
updateMileageDisplay(basicInfo.mlgRwdAmt);
// 이벤트 설명 화면에 업데이트
updateEventDescription(basicInfo.bnfWordsCn);
// 동의 여부에 따라 체크박스 상태 설정
updateAgreementCheckbox(basicInfo.agreAt);
}
// 관심분야 데이터 처리
if (resultData.interestAreas && resultData.interestAreas.length > 0) {
console.log('관심분야 개수:', resultData.interestAreas.length);
// 서버 데이터를 클라이언트에서 사용할 형태로 변환 (choiseAt 포함)
interestAreas = resultData.interestAreas.map(function(item, index) {
console.log(`관심분야 ${index + 1}:`, item);
const mappedItem = {
id: item.bnfIntrstInnb, // 관심분야 ID
name: item.intrstNm, // 관심분야 이름
beforeImageUrl: window.cdnUrl + item.bfeImgFilCours, // 이미지 URL
sortOrder: item.sortOrdr, // 정렬 순서
choiseAt: item.choiseAt || 'N' // 사용자 선택 여부 (기본값 N)
};
console.log(`변환된 관심분야 ${index + 1}:`, mappedItem);
return mappedItem;
});
console.log('변환 완료된 interestAreas:', interestAreas);
// 정렬 순서대로 정렬
interestAreas.sort(function(a, b) {
return a.sortOrder - b.sortOrder;
});
console.log('정렬 완료된 interestAreas:', interestAreas);
} else {
console.warn('관심분야 데이터가 없습니다.');
interestAreas = [];
}
} else {
console.warn('응답 구조가 올바르지 않습니다.');
interestAreas = [];
}
} catch (e) {
console.error('응답 데이터 파싱 실패:', e);
interestAreas = [];
}
console.log('최종 interestAreas:', interestAreas);
console.log('최종 bnfInnb:', bnfInnb);
// 관심분야 목록 렌더링
console.log('renderInterestList 호출 시작');
renderInterestList();
// 사용자 선택 상태로 UI 업데이트 (choiseAt 값 활용)
console.log('updateUIWithUserInterests 호출 시작');
updateUIWithUserInterests(interestAreas);
console.log('=== loadHealthInterestInfo 완료 ===');
},
error: function(xhr, status, error) {
console.error('=== API 호출 실패 ===');
console.error('xhr:', xhr);
console.error('status:', status);
console.error('error:', error);
showToast('관심분야 정보를 불러오는데 실패했습니다.');
// 오류 발생 시 빈 배열로 초기화하여 에러 방지
interestAreas = [];
renderInterestList();
}
});
}
/**
* 마일리지 표시 업데이트 함수
* 화면의 마일리지 관련 텍스트를 동적으로 업데이트
* @param {number} mileageAmount - 마일리지 금액
*/
function updateMileageDisplay(mileageAmount) {
if (mileageAmount && mileageAmount > 0) {
// 정수로 변환하여 표시
const mileageInt = Math.floor(mileageAmount);
console.log('마일리지 업데이트:', mileageInt);
// 상단 마일리지 안내 텍스트 업데이트
$('.topupPoint.noSticky span').html(
'정보받기 동의하고 저장하면 <i class="ico_mile"></i><b>' +
mileageInt.toLocaleString() + ' 원</b>적립'
);
}
}
/**
* 이벤트 설명 업데이트 함수
* 화면의 이벤트 설명 텍스트를 동적으로 업데이트
* @param {string} eventDescription - 이벤트 설명 내용 (HTML 형태)
*/
function updateEventDescription(eventDescription) {
if (eventDescription) {
console.log('이벤트 설명 업데이트:', eventDescription);
// 이벤트 설명 HTML 업데이트 (.text() 대신 .html() 사용)
$('.comment-box p:last-child').html(eventDescription);
} else {
console.log('이벤트 설명이 없습니다.');
// 설명이 없는 경우 기본 메시지 표시 (텍스트는 .text() 사용)
$('.comment-box p:last-child').text('이벤트 설명을 불러오는 중입니다...');
}
}
/**
* UI 업데이트 함수
* 관심분야 데이터의 choiseAt 값으로 UI 상태 업데이트
* @param {Array} interests - 관심분야 배열 (choiseAt 포함)
*/
function updateUIWithUserInterests(interests) {
// 모든 관심분야 항목 선택 해제 (초기화)
$('.favoriteList li').removeClass('on');
// choiseAt이 'Y'인 관심분야를 선택 상태로 변경
interests.forEach(function(interest) {
if (interest.choiseAt === 'Y') {
// 관심분야 ID로 매칭되는 요소 찾아 선택 상태로 설정
$('.favoriteList li img[data-interest-id="' + interest.id + '"]')
.closest('li')
.addClass('on');
console.log('Selected interest:', interest.name, 'ID:', interest.id);
}
});
}
/**
* 관심분야 목록 렌더링 함수
* 서버에서 받아온 관심분야 데이터를 화면에 동적으로 표시
*/
function renderInterestList() {
const $favoriteList = $('.favoriteList'); // 관심분야 목록을 표시할 컨테이너
$favoriteList.empty(); // 기존 목록 초기화 (중복 방지)
// 관심분야 데이터가 없는 경우 처리
if (!interestAreas || interestAreas.length === 0) {
$favoriteList.append('<li><p style="text-align:center; padding:20px;">관심분야 정보를 불러오는 중입니다...</p></li>');
return;
}
// 각 관심분야에 대해 리스트 아이템 생성 및 추가
interestAreas.forEach(function(interest) {
// 관심분야별 리스트 아이템 구조 생성
const $li = $('<li>'); // 리스트 아이템
const $a = $('<a>').attr('href', '#'); // 클릭 가능한 링크 요소
const $imgP = $('<p>').addClass('img'); // 이미지 컨테이너
const $img = $('<img>') // 이미지 요소
.attr('src', interest.beforeImageUrl) // 초기 이미지 URL
.attr('alt', interest.name) // 대체 텍스트
.attr('data-interest-id', interest.id); // 관심분야 ID 데이터 속성
const $titleP = $('<p>').addClass('tit').text(interest.name); // 관심분야 이름 요소
// 요소들 조합하여 DOM에 추가
$imgP.append($img); // 이미지를 이미지 컨테이너에 추가
$a.append($imgP).append($titleP); // 컨테이너와 제목을 링크에 추가
$li.append($a); // 링크를 리스트 아이템에 추가
$favoriteList.append($li); // 완성된 아이템을 목록에 추가
});
// 클릭 이벤트 리스너 추가 - 관심분야 선택/해제 토글
$('.favoriteList li a').on('click', function(e) {
e.preventDefault(); // 기본 링크 동작 방지
$(this).closest('li').toggleClass('on'); // 선택 상태 토글 (선택: on 클래스 추가)
});
}
// 초기 로딩 함수 호출
loadHealthInterestInfo();
/**
* 동의 체크박스 상태 업데이트 함수
* 서버에서 받아온 동의 여부에 따라 체크박스 상태를 설정
* @param {string} agreementStatus - 동의 여부 ('Y' 또는 'N')
*/
function updateAgreementCheckbox(agreementStatus) {
const $agreementCheckbox = $('#agreementCheckbox');
if (agreementStatus === 'Y') {
// 동의 상태인 경우 체크박스 체크
$agreementCheckbox.prop('checked', true);
console.log('동의 상태로 체크박스 설정: Y');
} else {
// 비동의 상태인 경우 체크박스 해제
$agreementCheckbox.prop('checked', false);
console.log('비동의 상태로 체크박스 설정: N');
}
}
/**
* 저장 버튼 클릭 이벤트 핸들러
* 선택된 관심분야와 동의 여부 확인 후 서버에 저장 요청
*/
$('#saveBtn').on('click', function() {
// bnfInnb 값이 없으면 저장 불가
if (!bnfInnb) {
showToast('데이터 로딩 중입니다. 잠시 후 다시 시도해주세요.');
console.error('bnfInnb 값이 없어서 저장할 수 없습니다.');
return;
}
// 동의 체크박스 상태 확인
var agreementChecked = $('#agreementCheckbox').is(':checked');
// 동의 체크박스 미선택 시 알림
if (!agreementChecked) {
showToast('관심분야 정보받기에 동의해주세요.');
return;
}
// 선택된 관심분야 정보 수집 - on 클래스가 있는 항목만 필터링
var selectedInterests = $('.favoriteList li.on').map(function() {
return {
bnfIntrstInnb: $(this).find('img').data('interest-id'), // 관심분야 ID
selected: true // 선택 여부
};
}).get(); // jQuery 객체를 일반 배열로 변환
// 최소 1개 이상의 관심분야 선택 여부 확인
if (selectedInterests.length === 0) {
showToast('최소 1개 이상의 관심분야를 선택해주세요.');
return;
}
// 서버로 전송할 데이터 준비
var data = {
bnfInnb: bnfInnb, // 전역 변수 사용
interestAreas: selectedInterests, // 선택된 관심분야 목록
agreAt: agreementChecked ? 'Y' : 'N' // 동의 여부를 Y/N으로 변환
};
console.log('저장할 데이터:', data); // 디버깅용 로그
// 관심분야 저장 API 호출
$.ajax({
url: '/sopmal/bnft/saveUserInterests', // 관심분야 저장 API
method: 'POST', // HTTP 메서드
contentType: 'application/json', // 요청 콘텐츠 타입
data: JSON.stringify(data), // 요청 데이터
success: function(response) {
// 응답 데이터 파싱
const result = JSON.parse(response.resBody.result);
if (result.success) {
// 저장 성공 시 알림 표시
showToast(result.message);
// 마일리지 적립 여부 확인 및 처리
if (result.mileageAccumulated && result.mlgRwdAmt) {
const mileageAmount = Math.floor(result.mlgRwdAmt);
showToast('마일리지 ' + mileageAmount.toLocaleString() + '원이 적립되었습니다.');
}
// 사용자 관심분야 정보 다시 로드하여 UI 갱신
loadHealthInterestInfo();
} else {
// 저장 실패 시 오류 메시지 표시
showToast('저장 중 오류가 발생했습니다: ' + result.message);
}
},
error: function(xhr, status, error) {
// 통신 오류 처리
showToast('서버와의 통신 중 오류가 발생했습니다.');
console.error('Error:', error);
}
});
});
/**
* 토스트 메시지 표시 함수
* 사용자에게 일시적인 알림 메시지 표시
* @param {string} message - 표시할 메시지 내용
*/
function showToast(message) {
var $toastPop = $('#toastPop'); // 토스트 팝업 요소
$toastPop.find('.txt').text(message); // 메시지 설정
$toastPop.addClass('show'); // 토스트 표시
// 2초 후 토스트 메시지 자동 숨김
setTimeout(function() {
$toastPop.removeClass('show');
}, 2000);
}
});

View File

@@ -0,0 +1,200 @@
/**
* 섭취챌린지 관련 스크립트
* 최근 1년 이내 구매한 제품과 위시리스트 저장 제품 중에서
* 섭취챌린지에 추가할 제품을 선택하는 기능을 제공합니다.
*/
$(document).ready(function() {
// 변수 선언
const $purchasedTab = $('#purchased-tab');
const $wishlistTab = $('#wishlist-tab');
const $purchasedList = $('.challenge_checklist.purchased');
const $wishlistList = $('.challenge_checklist.wishlist');
const $addToChallengeBtn = $('#addToChallengeBtn');
const bnfInnb = $('#bnfInnbHidden').val();
/**
* 탭 전환 처리
* 구매한 제품 탭과 위시리스트 탭 간 전환 기능
*/
$purchasedTab.on('click', function(e) {
e.preventDefault();
// 탭 활성화 상태 변경
$purchasedTab.addClass('active');
$wishlistTab.removeClass('active');
// 리스트 표시 상태 변경
$purchasedList.show();
$wishlistList.hide();
});
$wishlistTab.on('click', function(e) {
e.preventDefault();
// 탭 활성화 상태 변경
$wishlistTab.addClass('active');
$purchasedTab.removeClass('active');
// 리스트 표시 상태 변경
$wishlistList.show();
$purchasedList.hide();
});
/**
* 제품 체크박스 상태 관리
* 체크박스 토글 시 시각적 구분 처리
*/
$('.challenge_checklist input[type="checkbox"]').on('change', function() {
const $checkboxItem = $(this).closest('li');
if ($(this).is(':checked')) {
$checkboxItem.addClass('selected');
} else {
$checkboxItem.removeClass('selected');
}
});
/**
* 섭취챌린지 제품 추가
* 선택한 제품들을 섭취챌린지에 추가하는 기능
*/
$addToChallengeBtn.on('click', function() {
// 선택된 제품 ID 수집
const selectedProducts = [];
// 구매 제품 탭에서 선택된 제품 처리
$('.challenge_checklist.purchased input[type="checkbox"]:checked').each(function() {
selectedProducts.push({
goodsInnb: $(this).val(),
originDvCd: '01' // 구매 제품 출처 코드
});
});
// 위시리스트 탭에서 선택된 제품 처리
$('.challenge_checklist.wishlist input[type="checkbox"]:checked').each(function() {
selectedProducts.push({
goodsInnb: $(this).val(),
originDvCd: '02' // 위시리스트 출처 코드
});
});
// 선택된 제품이 없는 경우 알림 표시
if (selectedProducts.length === 0) {
showCustomAlert('섭취챌린지에 추가할 제품을 선택해주세요.');
return;
}
// 확인 팝업 표시
showConfirmModal('선택한 제품을 섭취챌린지에 추가하시겠습니까?', function() {
// API 호출 데이터 준비
const requestData = {
bnfInnb: bnfInnb,
products: selectedProducts
};
// API 호출
$.ajax({
url: '/sopmal/bnft/addSelectedProducts',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(requestData),
success: function(response) {
// 응답 구조 파싱
try {
// result 필드가 문자열 형태의 JSON이므로 파싱
const resultObj = JSON.parse(response.resBody.result);
if (resultObj.success) {
// 성공 시 처리
showCustomAlert(resultObj.message || '제품이 섭취챌린지에 추가되었습니다.');
// 추가된 제품을 목록에서 제거
removeAddedProducts(selectedProducts);
} else {
// 실패 시 처리
showCustomAlert(resultObj.message || '제품 추가 중 오류가 발생했습니다.');
}
} catch (e) {
// JSON 파싱 오류 처리
showCustomAlert('응답 처리 중 오류가 발생했습니다.');
console.error('응답 파싱 오류:', e);
}
},
error: function() {
showCustomAlert('서버 통신 중 오류가 발생했습니다.');
}
});
});
});
/**
* 추가된 제품을 목록에서 제거
* @param {Array} productIds - 제거할 제품 ID 배열
*/
function removeAddedProducts(productIds) {
productIds.forEach(function(productId) {
$(`.challenge_checklist input[value="${productId.goodsInnb}"]`).closest('li').remove();
});
}
/**
* 확인 모달 표시
* @param {string} message - 표시할 메시지
* @param {Function} confirmCallback - 확인 버튼 클릭 시 실행할 콜백 함수
*/
function showConfirmModal(message, confirmCallback) {
// 모달 내용 설정
$('#modal-1 .microModal__title').html(message);
// 모달 버튼 영역 수정
$('#modal-1 .microModal__btnContainer').html(`
<button class="microModal__btn" id="modal-confirm-cancel" aria-label="취소">취소</button>
<button class="microModal__btn microModal__btn-primary" id="modal-confirm-ok">확인</button>
`);
// 기존 이벤트 핸들러 제거 후 새로 등록
$('#modal-confirm-ok').off('click').on('click', function() {
MicroModal.close('modal-1');
if (typeof confirmCallback === 'function') {
confirmCallback();
}
});
// 취소 버튼 이벤트
$('#modal-confirm-cancel').off('click').on('click', function() {
MicroModal.close('modal-1');
});
// 모달 표시
MicroModal.show('modal-1');
}
/**
* 커스텀 알림 표시 함수
* @param {string} message - 표시할 메시지
*/
function showCustomAlert(message) {
// 모달 내용 설정
$('#modal-1 .microModal__title').html(message);
// 모달 버튼 영역 수정
$('#modal-1 .microModal__btnContainer').html(`
<button class="microModal__btn" id="continueAdding" aria-label="계속추가">계속추가</button>
<button class="microModal__btn microModal__btn-primary" id="startChallenge">챌린지 시작</button>
`);
// 기존 이벤트 핸들러 제거 후 새로 등록
$('#continueAdding').off('click').on('click', function() {
MicroModal.close('modal-1');
});
$('#startChallenge').off('click').on('click', function() {
MicroModal.close('modal-1');
// 챌린지 페이지로 이동 (bnfInnb 파라미터 추가)
window.location.href = '/sopmal/bnft/intakchlDailyRecordMain?bnfInnb=' + bnfInnb;
});
// 모달 표시
MicroModal.show('modal-1');
}
});

View File

@@ -0,0 +1,473 @@
/**
* 섭취챌린지 알림설정 관련 JavaScript
*
* 이 파일은 섭취챌린지 알림설정 기능을 구현한 JavaScript 파일입니다.
* 주요 기능:
* 1. 알림 시간 설정 - 타임피커를 통해 시간, 분, 오전/오후 설정
* 2. 알림 활성화/비활성화 - 토글 스위치를 통한 알림 설정 관리
* 3. 미완료 알림 설정 - 섭취 미완료 시 알림 설정 기능
* 4. 서버 통신 - AJAX를 통한 알림 설정 정보 저장 및 조회
*
* 사용 방법:
* - 페이지 로드 시 자동으로 초기화되어 기존 알림 설정을 불러옴
* - 사용자가 알림 시간을 변경하면 서버에 자동으로 저장됨
* - 알림 토글 변경 시 즉시 서버에 반영됨
*
*/
const Intakchl = (() => {
// 전역 변수
let currentNotificationId = null;
let contextPath = '';
// 초기화
function init() {
setupTimepicker();
setupHeaderTitle();
initializeAlerts();
initializeIncompleteNotification();
setupMainAlarmToggle();
renderTimeNotifications();
// MicroModal 초기화 추가
if(typeof MicroModal !== 'undefined') {
MicroModal.init({
disableScroll: true,
awaitCloseAnimation: true
});
} else {
console.error('MicroModal 라이브러리가 로드되지 않았습니다');
}
}
// 헤더 타이틀 설정
function setupHeaderTitle() {
$(".header__title").text("알림설정");
}
// 알림 메시지 초기화
function initializeAlerts() {
if (notificationData.alertMessage) {
showCustomAlert(notificationData.alertMessage);
if (notificationData.goBack === 'Y') {
$('#modal-alert .microModal__btn-primary').on('click', function() {
window.history.back();
});
}
}
}
// 미완료 알림 설정 초기화
function initializeIncompleteNotification() {
if (notificationData.incompleteNotification) {
const useAt = notificationData.incompleteNotification.useAt === "Y";
$("#mainAlarmToggle").prop("checked", useAt);
}
}
// 메인 알림 토글 설정
function setupMainAlarmToggle() {
$("#mainAlarmToggle").on("change", function() {
updateIncompleteNotification($(this).is(":checked") ? "Y" : "N");
});
}
// 타임피커 설정
function setupTimepicker() {
// 시간 옵션 생성
const hourSelect = document.getElementById('hour-select');
hourSelect.innerHTML = '';
for (let i = 1; i <= 12; i++) {
const option = document.createElement('option');
option.value = i.toString().padStart(2, '0');
option.textContent = i.toString().padStart(2, '0');
hourSelect.appendChild(option);
}
// 분 옵션 생성
const minuteSelect = document.getElementById('minute-select');
minuteSelect.innerHTML = '';
for (let i = 0; i < 60; i += 10) {
const option = document.createElement('option');
option.value = i.toString().padStart(2, '0');
option.textContent = i.toString().padStart(2, '0');
minuteSelect.appendChild(option);
}
// 셀렉트 박스가 포커스를 잃었을 때 원래 값으로 돌아가기
['ampm', 'hour', 'minute'].forEach(type => {
const select = document.getElementById(`${type}-select`);
select.addEventListener('blur', () => updateValue(type));
});
}
// 타임피커 값 조정
function adjustValue(type, direction) {
const elem = document.getElementById(type);
const select = document.getElementById(`${type}-select`);
if (type === 'ampm') {
// 오전/오후 전환
elem.textContent = elem.textContent === '오전' ? '오후' : '오전';
} else if (type === 'hour') {
// 1~12 사이에서 순환
let currentValue = parseInt(elem.textContent, 10);
currentValue = (currentValue + direction + 12 - 1) % 12 + 1;
elem.textContent = currentValue.toString().padStart(2, '0');
} else if (type === 'minute') {
// 0~50 사이에서 10분 단위로 순환
let currentValue = parseInt(elem.textContent, 10);
currentValue = (currentValue + direction * 10 + 60) % 60;
elem.textContent = currentValue.toString().padStart(2, '0');
}
}
// 셀렉트 박스 표시
function showSelect(type) {
const elem = document.getElementById(type);
const select = document.getElementById(`${type}-select`);
elem.style.display = 'none';
select.style.display = 'inline-block';
select.value = elem.textContent;
select.focus();
}
// 선택된 값으로 업데이트
function updateValue(type) {
const elem = document.getElementById(type);
const select = document.getElementById(`${type}-select`);
elem.textContent = select.value;
elem.style.display = 'inline-block';
select.style.display = 'none';
}
// 미완료 알림 설정 업데이트
function updateIncompleteNotification(useAt) {
if (notificationData.incompleteNotification) {
$.ajax({
url: `${contextPath}/sopmal/bnft/updateIntakeChallengeIncompleteNotification`,
type: "POST",
data: JSON.stringify({
bnfPushEvtSetupInnb: notificationData.incompleteNotification.bnfPushEvtSetupInnb,
useAt: useAt
}),
contentType: "application/json",
success: function(response) {
try {
const result = JSON.parse(response.resBody.result);
if (result.success) {
showCustomAlert("알림 설정이 변경되었습니다.");
} else {
showCustomAlert("알림 설정 변경에 실패했습니다.");
$("#mainAlarmToggle").prop("checked", useAt == "Y" ? false : true);
}
} catch (e) {
console.error("응답 처리 중 오류 발생:", e);
showCustomAlert("알림 설정 변경에 실패했습니다.");
$("#mainAlarmToggle").prop("checked", useAt == "Y" ? false : true);
}
},
error: function() {
showCustomAlert("알림 설정 변경 중 오류가 발생했습니다.");
$("#mainAlarmToggle").prop("checked", useAt == "Y" ? false : true);
}
});
}
}
// 시간 알림 목록 렌더링
function renderTimeNotifications() {
const alarmList = $(".alarmList");
alarmList.empty();
if (notificationData.timeNotifications && notificationData.timeNotifications.length > 0) {
notificationData.timeNotifications.forEach(notification => {
const li = $("<li>");
const time = notification.ntcnTme;
const useAt = notification.useAt === "Y";
const id = notification.bnfNtkNtcnInnb;
li.append('<span><a data-micromodal-trigger="modal-1" data-id="' + id + '" data-time="' + time + '">' + formatTime(time) + '</a></span>');
li.append(
'<fieldset class="toggle">' +
'<label>' +
'<input role="switch" type="checkbox" data-id="' + id + '" class="time-toggle" ' + (useAt ? 'checked' : '') + '>' +
'</label>' +
'</fieldset>'
);
alarmList.append(li);
});
}
// 시간 토글 변경 이벤트 바인딩
$(".time-toggle").on("change", function() {
const id = $(this).data("id");
const useAt = $(this).is(":checked") ? "Y" : "N";
const time = $('a[data-id="' + id + '"]').data("time");
updateTimeNotification(id, useAt, time);
});
// 시간 설정 모달 설정 - 이벤트 버블링 방지 추가
$("[data-micromodal-trigger='modal-1']").on("click", function(e) {
e.preventDefault(); // 기본 동작 방지
e.stopPropagation(); // 이벤트 버블링 방지
const id = $(this).data("id");
const time = $(this).data("time");
setupTimeModal(id, time);
MicroModal.show('modal-1');
});
}
// 시간 표시 포맷 (HH:MM -> 오전/오후 HH:MM)
function formatTime(timeStr) {
if (!timeStr) return "";
const [hours, minutes] = timeStr.split(":");
const hour = parseInt(hours, 10);
if (hour < 12) {
return '오전 ' + (hour == 0 ? '12' : hour) + ':' + minutes;
} else {
return '오후 ' + (hour == 12 ? '12' : hour - 12) + ':' + minutes;
}
}
// 시간 알림 업데이트
function updateTimeNotification(id, useAt, ntcnTme) {
// bnfInnb 파라미터 추출
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
// 로그 출력
console.log('[알림 설정] bnfInnb 파라미터:', bnfInnb);
// 매개변수 검증 추가
if (!id) {
console.error("알림 ID가 누락되었습니다");
return;
}
// useAt이 없으면 현재 토글 상태에서 가져옴
if (useAt === undefined) {
useAt = $('input[data-id="' + id + '"]').is(":checked") ? "Y" : "N";
}
// ntcnTme이 없으면 현재 시간 데이터에서 가져옴
if (!ntcnTme) {
ntcnTme = $('a[data-id="' + id + '"]').data("time");
}
$.ajax({
url: `${contextPath}/sopmal/bnft/updateIntakeChallengeNotification`,
type: "POST",
data: JSON.stringify({
bnfNtkNtcnInnb: id,
useAt: useAt,
ntcnTme: ntcnTme,
bnfInnb: bnfInnb
}),
contentType: "application/json",
success: function(response) {
try {
// resBody.result가 문자열로 된 JSON이므로 파싱 필요
const result = JSON.parse(response.resBody.result);
if (result.success) {
// 성공적으로 업데이트 됨
} else {
showCustomAlert("알림 설정 변경에 실패했습니다.");
$('input[data-id="' + id + '"]').prop("checked", useAt == "Y" ? false : true);
}
} catch (e) {
console.error("응답 처리 중 오류 발생:", e);
showCustomAlert("알림 설정 변경에 실패했습니다.");
$('input[data-id="' + id + '"]').prop("checked", useAt == "Y" ? false : true);
}
},
error: function() {
showCustomAlert("알림 설정 변경 중 오류가 발생했습니다.");
$('input[data-id="' + id + '"]').prop("checked", useAt == "Y" ? false : true);
}
});
}
// 시간 설정 모달 초기화
function setupTimeModal(id, timeStr) {
currentNotificationId = id;
if (!timeStr) return;
const [hours, minutes] = timeStr.split(":");
const hour = parseInt(hours, 10);
// 오전/오후 설정
const ampm = hour < 12 ? "오전" : "오후";
$("#ampm").text(ampm);
// 시간 설정 (12시간제)
const hour12 = hour == 0 ? 12 : (hour > 12 ? hour - 12 : hour);
$("#hour").text(hour12.toString().padStart(2, '0'));
// 분 설정
$("#minute").text(minutes);
// 모달 저장 버튼 이벤트 바인딩 수정
$("#modal-1 .btn.lg.full.theme").off("click").on("click", function(e) {
e.preventDefault(); // 기본 동작 방지
e.stopPropagation(); // 이벤트 버블링 방지
saveTimeNotification();
// 여기서 모달 닫기는 saveTimeNotification 성공 시 내부에서 처리됨
});
}
// 시간 설정 저장
function saveTimeNotification() {
if (!currentNotificationId) return;
// bnfInnb 파라미터 추출
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
const ampm = $("#ampm").text();
let hour = parseInt($("#hour").text(), 10);
const minute = $("#minute").text();
// 24시간제로 변환
if (ampm === "오후" && hour !== 12) {
hour += 12;
} else if (ampm === "오전" && hour === 12) {
hour = 0;
}
const timeStr = hour.toString().padStart(2, '0') + ':' + minute;
// 현재 토글 상태 가져오기
const useAt = $('input[data-id="' + currentNotificationId + '"]').is(":checked") ? "Y" : "N";
$.ajax({
url: `${contextPath}/sopmal/bnft/updateIntakeChallengeNotification`,
type: "POST",
data: JSON.stringify({
bnfNtkNtcnInnb: currentNotificationId,
ntcnTme: timeStr,
useAt: useAt, // 토글 상태 추가
bnfInnb: bnfInnb
}),
contentType: "application/json",
success: function(response) {
try {
// resBody.result가 문자열로 된 JSON이므로 파싱 필요
const result = JSON.parse(response.resBody.result);
if (result.success) {
// 시간 표시 업데이트
$('a[data-id="' + currentNotificationId + '"]').text(formatTime(timeStr));
$('a[data-id="' + currentNotificationId + '"]').data("time", timeStr);
MicroModal.close('modal-1'); // 성공 시 모달 닫기
// 디버깅용 알림 추가
console.log("알림 시간이 변경되었습니다: " + formatTime(timeStr));
} else {
showCustomAlert("알림 시간 변경에 실패했습니다.");
}
} catch (e) {
console.error("응답 처리 중 오류 발생:", e);
showCustomAlert("알림 시간 변경에 실패했습니다.");
}
},
error: function() {
showCustomAlert("알림 시간 변경 중 오류가 발생했습니다.");
}
});
}
// 기본 알림시간 초기화
function resetDefaultTimes() {
if (confirm('알림 시간을 기본값으로 초기화하시겠습니까?')) {
$.ajax({
url: `${contextPath}/sopmal/bnft/resetDefaultNotificationTimes`,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({}),
success: function(response) {
try {
// resBody.result가 문자열로 된 JSON이므로 파싱 필요
const result = JSON.parse(response.resBody.result);
if (result.success) {
showCustomAlert('알림 시간이 기본값으로 초기화되었습니다.');
window.location.reload();
} else {
showCustomAlert('알림 시간 초기화에 실패했습니다.');
}
} catch (e) {
console.error("응답 처리 중 오류 발생:", e);
showCustomAlert('알림 시간 초기화에 실패했습니다.');
}
},
error: function() {
showCustomAlert('알림 시간 초기화 중 오류가 발생했습니다.');
}
});
}
}
// 개별 알림 삭제
function deleteNotification(id) {
if (confirm('이 알림을 삭제하시겠습니까?')) {
$.ajax({
url: `${contextPath}/sopmal/bnft/deleteNotification`,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfNtkNtcnInnb: id
}),
success: function(response) {
try {
// resBody.result가 문자열로 된 JSON이므로 파싱 필요
const result = JSON.parse(response.resBody.result);
if (result.success) {
showCustomAlert('알림이 삭제되었습니다.');
window.location.reload();
} else {
showCustomAlert('알림 삭제에 실패했습니다.');
}
} catch (e) {
console.error("응답 처리 중 오류 발생:", e);
showCustomAlert('알림 삭제에 실패했습니다.');
}
},
error: function() {
showCustomAlert('알림 삭제 중 오류가 발생했습니다.');
}
});
}
}
// 알림용 모달 표시 함수
function showCustomAlert(message) {
// 기존 모달 스타일을 활용한 새로운 알림 모달
$('#modal-alert .microModal__content p').text(message);
MicroModal.show('modal-alert');
}
// 초기 데이터 설정
function setInitialData(data, path) {
notificationData = data;
contextPath = path;
}
// 공개 API
return {
init,
adjustValue,
showSelect,
updateValue,
resetDefaultTimes,
deleteNotification,
setInitialData,
showCustomAlert
};
})();
// 페이지 로드 시 초기화
$(document).ready(function() {
// 초기화는 JSP에서 데이터 설정 후 호출됨
});

View File

@@ -0,0 +1,108 @@
/**
* IntakchlChallengeSettings.js
*
* 섭취 챌린지 설정 화면을 관리하는 모듈
* 사용자가 섭취 제품 관리와 알림 설정 페이지로 이동할 수 있는 기능 제공
*
* 주요 기능:
* 1. 섭취 제품 관리 페이지 이동 처리
* 2. 알림 설정 페이지 이동 처리
* 3. 이벤트 리스너 등록 및 관리
*
* 연관 파일:
* - IntakchlAlarmSettings.js: 알림 설정 관련 기능
* - IntakchlProductDetail.js: 제품 상세 관련 기능
*/
$(function() {
/**
* 문서 로딩 완료 후 초기화 함수
* 각 버튼에 이벤트 리스너 등록
*/
function init() {
// 이벤트 리스너 등록
registerEventListeners();
}
/**
* URL에서 특정 파라미터 값을 추출하는 함수
* @param {string} paramName - 추출할 파라미터명
* @return {string|null} 파라미터 값 또는 null
*/
function getUrlParameter(paramName) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(paramName);
}
/**
* 각 버튼 및 요소에 이벤트 리스너 등록
*/
function registerEventListeners() {
// 섭취제품 관리 버튼 클릭 이벤트
$("#manageProducts").on("click", function(e) {
e.preventDefault(); // 기본 이벤트 방지
navigateToProductManagement();
});
// 알림설정 버튼 클릭 이벤트
$("#setAlarms").on("click", function(e) {
e.preventDefault(); // 기본 이벤트 방지
navigateToAlarmSettings();
});
}
/**
* 섭취제품 관리 페이지로 이동
* 해당 컨트롤러의 intakchlChallengeProductManagement 메소드 호출
*/
function navigateToProductManagement() {
const bnfInnb = getUrlParameter('bnfInnb');
const source = getUrlParameter('source'); // source 파라미터도 추출
let url = '/sopmal/bnft/intakchlChallengeProductManagement';
const params = [];
// bnfInnb 파라미터가 있는 경우 추가
if (bnfInnb) {
params.push('bnfInnb=' + encodeURIComponent(bnfInnb));
}
// source 파라미터가 있는 경우 추가
if (source) {
params.push('source=' + encodeURIComponent(source));
}
// 파라미터가 있으면 URL에 추가
if (params.length > 0) {
url += '?' + params.join('&');
}
// 페이지 이동 전 로딩 표시 등 추가 필요시 여기에 작성
window.location.href = url;
}
/**
* 알림설정 페이지로 이동
* 해당 컨트롤러의 intakchllAlarmSettings 메소드 호출
*/
function navigateToAlarmSettings() {
const bnfInnb = getUrlParameter('bnfInnb');
let url = '/sopmal/bnft/intakchllAlarmSettings';
// bnfInnb 파라미터가 있는 경우 URL에 추가
if (bnfInnb) {
url += '?bnfInnb=' + encodeURIComponent(bnfInnb);
}
// 페이지 이동 전 로딩 표시 등 추가 필요시 여기에 작성
window.location.href = url;
}
// 문서 로딩 완료 시 초기화 함수 호출
init();
// 에러 처리를 위한 전역 에러 핸들러 추가
$(window).on("error", function(e) {
console.error("JavaScript 오류 발생:", e.originalEvent.message);
});
});

View File

@@ -0,0 +1,660 @@
/**
* 섭취챌린지 일일 기록 관련 스크립트
* 달력을 통한 날짜별 섭취 기록 관리 및 섭취 상태 변경 기능 제공
*/
$(document).ready(function() {
// 변수 선언
let currentDate = new Date();
let selectedDate = new Date();
const dayNames = ['일', '월', '화', '수', '목', '금', '토'];
// 요소 캐싱
const $calendarDays = $('#calendarDays');
const $currentMonth = $('#currentMonth');
const $prevWeek = $('#prevWeek');
const $nextWeek = $('#nextWeek');
const $challengeProductContainer = $('.sub-box:nth-child(3)');
const $addChallengeBtn = $('.btn_add_challenge');
// 초기화
init();
/**
* 초기화 함수
*/
function init() {
// 달력 초기화
updateCalendar();
// 이벤트 리스너 등록
registerEventListeners();
// 현재 날짜 데이터 로드
loadDailyRecords(formatDate(selectedDate));
}
/**
* 이벤트 리스너 등록
*/
function registerEventListeners() {
// 이전 주 버튼 클릭
$prevWeek.on('click', function() {
currentDate.setDate(currentDate.getDate() - 7);
selectedDate = new Date(currentDate); // 선택된 날짜도 함께 업데이트
updateCalendar();
loadDailyRecords(formatDate(selectedDate)); // 데이터 로드
});
// 다음 주 버튼 클릭
$nextWeek.on('click', function() {
currentDate.setDate(currentDate.getDate() + 7);
selectedDate = new Date(currentDate); // 선택된 날짜도 함께 업데이트
updateCalendar();
loadDailyRecords(formatDate(selectedDate)); // 데이터 로드
});
// 제품 추가 버튼 클릭
$addChallengeBtn.on('click', function() {
window.location.href = '/sopmal/bnft/intakchlMain';
});
// 섭취 상태 변경 이벤트 위임
$(document).on('click', '.drugCheck span.drug_off', function(e) {
e.preventDefault();
e.stopPropagation();
const $this = $(this);
const bnfNtkInnb = $this.closest('.challengeProduct').data('bnfntkinnb');
const intakeSequence = $this.data('sequence');
updateIntakeStatus(bnfNtkInnb, intakeSequence, $this);
});
}
/**
* 달력 업데이트
*/
function updateCalendar() {
$calendarDays.empty();
$currentMonth.text(`${currentDate.getFullYear()}년 ${currentDate.getMonth() + 1}월`);
// 현재 주의 날짜 표시 (-3일 ~ +3일)
for (let i = -3; i <= 3; i++) {
const date = new Date(currentDate);
date.setDate(currentDate.getDate() + i);
const dayElement = $('<div>').addClass('day');
if (i === 0) dayElement.addClass('center');
// 오늘 날짜 표시
if (isSameDate(date, new Date())) {
dayElement.addClass('today');
}
// 선택된 날짜 표시
if (isSameDate(date, selectedDate)) {
dayElement.addClass('focused');
}
// 요일 및 날짜 추가
dayElement.append(
$('<div>').addClass('day-name').text(dayNames[date.getDay()]),
$('<div>').addClass('day-number').text(date.getDate())
);
// 날짜 클릭 이벤트
dayElement.on('click', function() {
selectedDate = new Date(date);
updateCalendar();
loadDailyRecords(formatDate(selectedDate));
});
$calendarDays.append(dayElement);
}
// 스와이프 이벤트 추가
setupSwipeEvents();
}
/**
* 스와이프 이벤트 설정
*/
function setupSwipeEvents() {
let touchStartX = 0;
let touchStartY = 0;
let touchStartTime = 0;
let isSwipeInProgress = false; // 스와이프 진행 중 플래그
const $calendar = $('.calendar-days');
// 기존 이벤트 리스너 제거 (중복 방지)
$calendar.off('touchstart touchend touchmove');
$calendar.on('touchstart', function(e) {
if (isSwipeInProgress) return; // 이미 스와이프 진행 중이면 무시
const touch = e.originalEvent.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
touchStartTime = new Date().getTime();
});
$calendar.on('touchmove', function(e) {
// 스크롤 방지 (세로 스크롤은 허용하되 가로 스와이프 시에만)
const touch = e.originalEvent.touches[0];
const diffX = Math.abs(touch.clientX - touchStartX);
const diffY = Math.abs(touch.clientY - touchStartY);
// 가로 움직임이 세로 움직임보다 클 경우에만 스크롤 방지
if (diffX > diffY && diffX > 10) {
e.preventDefault();
}
});
$calendar.on('touchend', function(e) {
if (isSwipeInProgress) return; // 이미 스와이프 진행 중이면 무시
const touch = e.originalEvent.changedTouches[0];
const touchEndX = touch.clientX;
const touchEndY = touch.clientY;
const touchEndTime = new Date().getTime();
const diffX = touchEndX - touchStartX;
const diffY = touchEndY - touchStartY;
const timeDiff = touchEndTime - touchStartTime;
// 스와이프 조건 검증
const minDistance = 80; // 최소 스와이프 거리 (50 -> 80으로 증가)
const maxTime = 500; // 최대 스와이프 시간 (0.5초)
const maxVerticalDistance = 100; // 세로 방향 최대 허용 거리
// 가로 방향 움직임이 세로 방향보다 크고, 조건을 만족하는 경우에만 스와이프 인식
if (Math.abs(diffX) > minDistance &&
Math.abs(diffX) > Math.abs(diffY) &&
Math.abs(diffY) < maxVerticalDistance &&
timeDiff < maxTime) {
isSwipeInProgress = true; // 스와이프 진행 플래그 설정
if (diffX > 0) {
// 오른쪽 스와이프 - 이전 주 (7일 이전)
currentDate.setDate(currentDate.getDate() - 7);
} else {
// 왼쪽 스와이프 - 다음 주 (7일 다음)
currentDate.setDate(currentDate.getDate() + 7);
}
// 스와이프 후 중앙 날짜를 선택된 날짜로 설정
selectedDate = new Date(currentDate);
// 달력 업데이트
updateCalendar();
// 선택된 날짜의 데이터 로드
loadDailyRecords(formatDate(selectedDate));
// 스와이프 완료 후 일정 시간 후 플래그 해제 (연속 스와이프 방지)
setTimeout(function() {
isSwipeInProgress = false;
}, 300); // 0.3초 후 다시 스와이프 가능
}
});
}
/**
* 일일 섭취 기록 로드
* @param {string} date - 날짜 (YYYY-MM-DD 형식)
*/
function loadDailyRecords(date) {
// URL에서 bnfInnb 파라미터 추출
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
// 먼저 일일 섭취율 정보 조회
$.ajax({
url: '/sopmal/bnft/selectDailyTotalIntakeRecord',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
selectedDate: date,
bnfInnb: bnfInnb // BNF_INNB 추가
}),
success: function(response) {
if (response && response.resBody && response.resBody.result) {
try {
const result = JSON.parse(response.resBody.result);
if (result.success) {
// 일일 총 섭취율 업데이트
const intakeRatio = result.dailyTotalIntakeRecord ?
result.dailyTotalIntakeRecord.overallIntakeRatePercentage : 0;
updateTotalIntakeRate(intakeRatio);
// 초기 로드 시 마일리지 적립 상태 체크
const mileageAlreadyAwarded = result.mileageAlreadyAwarded || false;
const mileageAmount = result.rewardInfo || 10;
// 초기 로드 시에도 마일리지 상태 표시
checkMileageStatusOnLoad(intakeRatio, mileageAmount, mileageAlreadyAwarded);
// 현재 챌린지 중인 제품 목록 조회
loadChallengeProducts(date);
} else {
console.error('일일 섭취율 정보 조회 실패:', result.message);
showCustomAlert('데이터 조회 중 오류가 발생했습니다.');
}
} catch (e) {
console.error('응답 파싱 오류:', e, response.resBody.result);
showCustomAlert('데이터 처리 중 오류가 발생했습니다.');
}
}
},
error: function() {
showCustomAlert('서버 통신 중 오류가 발생했습니다.');
}
});
}
/**
* 초기 로드 시 마일리지 적립 상태 체크
* @param {number} rate - 현재 섭취율
* @param {number} amount - 마일리지 금액
* @param {boolean} alreadyAwarded - 이미 적립된 상태 여부
*/
function checkMileageStatusOnLoad(rate, amount, alreadyAwarded) {
const $topupPoint = $('.topupPoint');
if (rate >= 100 && alreadyAwarded) {
// 100% 달성하고 이미 적립된 경우
$topupPoint.html(`<span><i class="ico_mile"></i><b>마일리지 ${amount}원</b> 적립완료!</span>`);
} else if (rate >= 100) {
// 100% 달성했지만 아직 적립되지 않은 경우 (이론적으로는 발생하지 않아야 함)
$topupPoint.html(`<span><i class="ico_mile"></i><b>마일리지 ${amount}원</b> 적립가능!</span>`);
} else {
// 100% 미달성인 경우
$topupPoint.html(`<span>오늘의 섭취기록 100% 도달시<i class="ico_mile"></i><b>마일리지 ${amount}원</b>적립</span>`);
}
}
// 챌린지 중인 제품 목록 조회 함수 추가
function loadChallengeProducts(date) {
// URL에서 bnfInnb 파라미터 추출
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
$.ajax({
url: '/sopmal/bnft/selectCurrentChallengeProducts',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
selectedDate: date,
bnfInnb: bnfInnb // BNF_INNB 추가
}),
success: function(response) {
if (response && response.resBody && response.resBody.result) {
try {
const result = JSON.parse(response.resBody.result);
if (result.success) {
console.log("제품 목록:", result.currentChallengeProducts);
renderProductList(result.currentChallengeProducts || []);
} else {
console.log("제품 목록 조회 실패:", result.message);
renderProductList([]);
}
} catch (e) {
console.error('제품 데이터 파싱 오류:', e, response.resBody.result);
renderProductList([]);
}
} else {
renderProductList([]);
}
},
error: function() {
console.error('제품 목록 조회 중 오류 발생');
renderProductList([]);
}
});
}
/**
* 총 섭취율 업데이트
* @param {number} rate - 섭취율 (0-100)
*/
function updateTotalIntakeRate(rate) {
const intakeRate = rate || 0;
$('.challengeBar-container .per').css('width', `${intakeRate}%`);
$('.challengeBar-container .per-txt').text(`${intakeRate}%`);
}
/**
* 이미지 URL에 CDN 적용
* @param {string} imageUrl - 원본 이미지 URL
* @returns {string} - CDN이 적용된 이미지 URL
*/
function applyCdnUrl(imageUrl) {
if (!imageUrl) return '';
// 이미 완전한 URL인 경우 (http:// 또는 https://로 시작)
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
return imageUrl;
}
// CDN URL이 있고 상대 경로인 경우
if (window.CDN_URL && imageUrl.startsWith('/')) {
return window.CDN_URL + imageUrl;
}
return imageUrl;
}
/**
* 오늘 날짜인지 확인
* @param {Date} date - 확인할 날짜
* @returns {boolean} - 오늘 날짜면 true, 아니면 false
*/
function isToday(date) {
const today = new Date();
return isSameDate(date, today);
}
/**
* 토스트 메시지 표시
* @param {string} message - 표시할 메시지
*/
function showToast(message) {
// 토스트 요소가 없으면 생성
if ($("#toastPop").length === 0) {
$("body").append('<div id="toastPop" class="toastPop"><p></p></div>');
}
$('#toastPop p').text(message);
$('#toastPop').addClass('show');
// 2초 후 토스트 메시지 숨김
setTimeout(function() {
$('#toastPop').removeClass('show');
}, 2000);
}
/**
* 제품 목록 렌더링
* @param {Array} products - 제품 목록
*/
function renderProductList(products) {
$challengeProductContainer.empty();
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
$challengeProductContainer.empty();
if (!products || products.length === 0) {
$challengeProductContainer.append(
$('<div>').addClass('no-products').text('섭취 챌린지 중인 제품이 없습니다.')
);
return;
}
// 오늘 날짜인지 확인
const isTodaySelected = isToday(selectedDate);
// 제품 목록 렌더링
products.forEach(product => {
const $productElement = $('<div>')
.addClass('challengeProduct')
.attr('data-bnfntkinnb', product.bnfNtkInnb);
// 제품 컨테이너 생성 (링크 또는 div)
let $productContainer;
if (isTodaySelected) {
// 오늘 날짜인 경우 링크 생성
$productContainer = $('<a>').attr('href',
`/sopmal/bnft/intakchlProductDetail?bnfNtkInnb=${product.bnfNtkInnb}&bnfInnb=${bnfInnb || ''}&returnPage=dailyRecord`
);
} else {
// 오늘 날짜가 아닌 경우 div 생성하되 기존 a 태그와 동일한 CSS 클래스/스타일 적용
$productContainer = $('<div>').addClass('product-link-disabled');
}
// 썸네일 - CDN URL 적용
$productContainer.append(
$('<div>').addClass('thum').append(
$('<img>').attr('src', applyCdnUrl(product.imageUrl)).attr('alt', product.productName)
)
);
// 제품명과 섭취 정보
$productContainer.append(
$('<div>').addClass('cpTit').append(
$('<p>').addClass('tit').text(product.productName),
$('<p>').addClass('dayCheck').html(`<span class="ico_tablet"></span>${product.dailyIntakeCount}회`)
)
);
// 섭취 상태 표시
const $drugCheck = $('<div>').addClass('drugCheck');
// 섭취 횟수가 0이거나 정보가 없는 경우 알약 그림 표시하지 않음
if (product.dailyIntakeCount && product.dailyIntakeCount > 0) {
if (!product.intakeStatuses || product.intakeStatuses.length === 0) {
// 섭취 정보가 없지만 섭취 횟수가 있는 경우 기본 1개 표시
const $statusSpan = $('<span>')
.addClass('drug_off')
.text('섭취미완료')
.attr('data-sequence', 1);
// 오늘 날짜가 아닌 경우 비활성화
if (!isTodaySelected) {
$statusSpan.addClass('disabled').css({
'opacity': '0.5',
'pointer-events': 'none'
});
}
$drugCheck.append($statusSpan);
} else {
// 섭취 상태 표시
product.intakeStatuses.forEach((status, index) => {
const $statusSpan = $('<span>')
.addClass(status ? 'drug_on' : 'drug_off')
.text(status ? '섭취완료' : '섭취미완료')
.attr('data-sequence', index + 1);
// 이미 섭취 완료된 경우 또는 오늘 날짜가 아닌 경우 비활성화
if (status || !isTodaySelected) {
$statusSpan.css('pointer-events', 'none');
if (!isTodaySelected && !status) {
$statusSpan.addClass('disabled').css('opacity', '0.5');
}
}
$drugCheck.append($statusSpan);
});
}
}
$productContainer.append($drugCheck);
// 오늘 날짜가 아닌 경우 전체 제품 요소에 비활성화 클래스 추가
if (!isTodaySelected) {
$productElement.addClass('product-disabled');
}
$productElement.append($productContainer);
$challengeProductContainer.append($productElement);
});
// 오늘 날짜가 아닌 경우 안내 메시지 추가
if (!isTodaySelected) {
const $noticeElement = $('<div>')
.addClass('date-notice')
.css({
'text-align': 'center',
'padding': '15px',
'margin': '10px 0',
'background-color': '#f8f9fa',
'border-radius': '8px',
'color': '#6c757d',
'font-size': '14px'
})
.text('섭취 기록은 오늘 날짜만 추가할 수 있습니다.');
$challengeProductContainer.append($noticeElement);
}
}
/**
* 섭취 상태 업데이트
* @param {string} productId - 제품 ID
* @param {number} sequence - 섭취 순서
* @param {jQuery} $element - 업데이트할 요소
*/
function updateIntakeStatus(productId, sequence, $element) {
// 오늘 날짜가 아닌 경우 업데이트 차단
if (!isToday(selectedDate)) {
showToast('섭취 기록은 오늘 날짜만 추가할 수 있습니다.');
return;
}
// URL에서 bnfInnb 파라미터 추출
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
$.ajax({
url: '/sopmal/bnft/updateIntakeStatus',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfNtkInnb: productId,
intakeSequence: sequence,
selectedDate: formatDate(selectedDate),
bnfInnb: bnfInnb // BNF_INNB 추가
}),
success: function(response) {
if (response && response.resBody && response.resBody.result) {
try {
const result = JSON.parse(response.resBody.result);
if (result.success) {
// 성공적으로 업데이트됐을 때 UI 변경
$element.removeClass('drug_off').addClass('drug_on');
$element.text('섭취완료');
$element.css('pointer-events', 'none');
// 일일 총 섭취율 업데이트
updateTotalIntakeRate(result.newTotalIntakeRate);
// 마일리지 적립 상태 체크 (mileageAlreadyAwarded 추가)
checkMileageStatus(
result.mileageAwarded,
result.newTotalIntakeRate,
result.amount,
result.mileageAlreadyAwarded // 추가
);
} else {
showToast(result.message || '상태 업데이트 중 오류가 발생했습니다.');
}
} catch (e) {
console.error('응답 파싱 오류:', e);
showToast('데이터 처리 중 오류가 발생했습니다.');
}
}
},
error: function() {
showToast('서버 통신 중 오류가 발생했습니다.');
}
});
}
/**
* 마일리지 적립 상태 체크
* @param {boolean} awarded - 적립 완료 여부
* @param {number} rate - 현재 섭취율
* @param {number} amount - 마일리지 금액
* @param {boolean} alreadyAwarded - 이미 적립된 상태 여부
*/
function checkMileageStatus(awarded, rate, amount, alreadyAwarded) {
const $topupPoint = $('.topupPoint');
if (awarded && rate >= 100) {
if (alreadyAwarded) {
// 이미 적립된 경우
$topupPoint.html(`<span><i class="ico_mile"></i><b>마일리지 ${amount || 10}원</b> 이미 적립완료!</span>`);
} else {
// 새로 적립된 경우
$topupPoint.html(`<span><i class="ico_mile"></i><b>마일리지 ${amount || 10}원</b> 적립완료!</span>`);
}
} else if (rate >= 100) {
// 100% 달성했지만 마일리지가 아직 처리되지 않은 경우
awardMileage();
}
}
/**
* 마일리지 적립 API 호출
*/
function awardMileage() {
// URL에서 bnfInnb 파라미터 추출
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
$.ajax({
url: '/sopmal/bnft/awardIntakeChallengesMileage',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
intakeDate: formatDate(selectedDate),
bnfInnb: bnfInnb // BNF_INNB 추가
}),
success: function(response) {
if (response && response.resBody && response.resBody.result) {
try {
const result = JSON.parse(response.resBody.result);
if (result.success) {
$('.topupPoint').html('<span><i class="ico_mile"></i><b>마일리지 10원</b> 적립완료!</span>');
}
} catch (e) {
console.error('응답 파싱 오류:', e);
}
}
},
error: function() {
console.error('마일리지 적립 중 오류 발생');
}
});
}
/**
* 날짜가 같은지 비교
* @param {Date} date1 - 비교할 첫 번째 날짜
* @param {Date} date2 - 비교할 두 번째 날짜
* @returns {boolean} - 날짜가 같으면 true, 다르면 false
*/
function isSameDate(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
/**
* 날짜 포맷팅 (YYYY-MM-DD)
* @param {Date} date - 포맷팅할 날짜
* @returns {string} - 포맷팅된 날짜 문자열
*/
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 커스텀 알림 표시 (기존 showCustomAlert 함수를 토스트로 변경)
* @param {string} message - 표시할 메시지
*/
function showCustomAlert(message) {
showToast(message);
}
});

View File

@@ -0,0 +1,280 @@
/**
* 섭취 챌린지 마일리지 요약 페이지 JavaScript
*
* 주요 기능:
* 1. 달력 렌더링 (월별 섭취 현황 표시)
* 2. 날짜별 상태 조회
*/
$(document).ready(function() {
console.log('[마일리지 요약 페이지] 초기화 시작');
// 월 이름 배열
const monthNames = ["1월", "2월", "3월", "4월", "5월", "6월",
"7월", "8월", "9월", "10월", "11월", "12월"];
console.log('[마일리지 요약 페이지] 월 이름 배열 설정 완료');
// 현재 날짜 및 상태 변수 초기화
let currentDate = new Date();
let isLoading = false;
const $calendarContainer = $("#stats-dates");
console.log('[마일리지 요약 페이지] 현재 날짜:', currentDate);
// 날짜별 상태 클래스 데이터 - 서버에서 받은 데이터 사용
let dateClasses = {};
console.log('[마일리지 요약 페이지] dateClasses 객체 초기화');
// 서버에서 받은 데이터를 객체 형태로 변환
if (window.initialDateClasses && Array.isArray(window.initialDateClasses)) {
console.log('[마일리지 요약 페이지] 서버에서 받은 initialDateClasses 데이터:', window.initialDateClasses);
window.initialDateClasses.forEach(item => {
if (item.date) {
// 상태 클래스들을 조합하여 저장
let classes = [];
if (item.statsDone) classes.push(item.statsDone);
if (item.statsIng && item.statsIng !== item.statsDone) classes.push(item.statsIng);
if (item.stats30done) classes.push(item.stats30done);
if (classes.length > 0) {
dateClasses[item.date] = classes.join(' ');
console.log(`[마일리지 요약 페이지] 날짜 클래스 매핑: ${item.date} => ${dateClasses[item.date]}`);
}
}
});
} else {
console.log('[마일리지 요약 페이지] 초기 날짜 클래스 데이터가 없거나 유효하지 않음');
}
console.log('[마일리지 요약 페이지] 최종 dateClasses 객체:', dateClasses);
/**
* 달력을 렌더링하는 함수
* @param {Date} date - 렌더링할 달의 날짜
* @param {String} position - 추가 위치 ('top' 또는 'bottom')
*/
function renderCalendar(date, position = 'bottom') {
console.log(`[마일리지 요약 페이지] 달력 렌더링 시작: ${date.getFullYear()}년 ${date.getMonth() + 1}월, 위치: ${position}`);
const $statsDatesElement = $("#stats-dates");
const currentYear = date.getFullYear();
const currentMonth = date.getMonth();
const monthYearText = `${monthNames[currentMonth]} ${currentYear}`;
console.log(`[마일리지 요약 페이지] 렌더링할 월/년: ${monthYearText}`);
// 이미 해당 월이 렌더링 되어 있는지 확인
if (!$statsDatesElement.html().includes(monthYearText)) {
console.log(`[마일리지 요약 페이지] ${monthYearText} 달력 새로 렌더링 시작`);
const firstDay = new Date(currentYear, currentMonth, 1).getDay();
const lastDate = new Date(currentYear, currentMonth + 1, 0).getDate();
console.log(`[마일리지 요약 페이지] 첫 요일: ${firstDay}, 마지막 날짜: ${lastDate}`);
const today = new Date();
const isCurrentMonth = today.getFullYear() === currentYear && today.getMonth() === currentMonth;
const todayDate = today.getDate();
console.log(`[마일리지 요약 페이지] 현재 월 여부: ${isCurrentMonth}, 오늘 날짜: ${todayDate}`);
const $monthContainer = $("<div>").addClass("month-container");
// 월 헤더 추가
const $monthHeader = $("<div>").addClass("month-header").text(monthYearText);
$monthContainer.append($monthHeader);
console.log('[마일리지 요약 페이지] 월 헤더 추가 완료');
// 요일 행 추가
const $daysRow = $("<div>").addClass("stats-days");
const days = ["일", "월", "화", "수", "목", "금", "토"];
$.each(days, function(i, day) {
$daysRow.append($("<div>").text(day));
});
$monthContainer.append($daysRow);
console.log('[마일리지 요약 페이지] 요일 행 추가 완료');
// 날짜 그리드 추가
const $datesGrid = $("<div>").addClass("dates-grid");
// 월 시작 전 빈 셀 추가
for (let i = 0; i < firstDay; i++) {
$datesGrid.append($("<div>"));
}
console.log(`[마일리지 요약 페이지] 빈 셀 ${firstDay}개 추가 완료`);
// 날짜 셀 추가
for (let date = 1; date <= lastDate; date++) {
const $dateElement = $("<div>");
const $dateSpan = $("<span>").text(date);
$dateElement.append($dateSpan);
// 날짜 형식화: YYYY-MM-DD
const fullDate = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(date).padStart(2, '0')}`;
// 해당 날짜의 상태 클래스가 있으면 추가
if (dateClasses[fullDate]) {
$dateElement.addClass(dateClasses[fullDate]);
console.log(`[마일리지 요약 페이지] 날짜 ${fullDate}에 클래스 ${dateClasses[fullDate]} 추가`);
}
// 오늘 날짜 표시
if (isCurrentMonth && date === todayDate) {
$dateElement.addClass("today");
console.log(`[마일리지 요약 페이지] 오늘 날짜 ${fullDate}에 today 클래스 추가`);
}
$datesGrid.append($dateElement);
}
console.log(`[마일리지 요약 페이지] ${lastDate}일까지 날짜 셀 추가 완료`);
$monthContainer.append($datesGrid);
// 위치에 따라 추가
if (position === 'top') {
$statsDatesElement.prepend($monthContainer);
console.log('[마일리지 요약 페이지] 달력을 상단에 추가');
} else {
$statsDatesElement.append($monthContainer);
console.log('[마일리지 요약 페이지] 달력을 하단에 추가');
}
// 섭취 완료 후 미완료 날짜 스타일 처리
hideAfterStatsDone($datesGrid);
console.log('[마일리지 요약 페이지] hideAfterStatsDone 처리 완료');
} else {
console.log(`[마일리지 요약 페이지] ${monthYearText} 달력이 이미 렌더링되어 있음`);
}
}
/**
* 이전 달을 로드하는 함수
*/
function loadPreviousMonth() {
console.log('[마일리지 요약 페이지] 이전 달 로드 시작');
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
console.log(`[마일리지 요약 페이지] 이전 달: ${newDate.getFullYear()}년 ${newDate.getMonth() + 1}월`);
renderCalendar(newDate, 'top');
currentDate = newDate;
}
/**
* 다음 달을 로드하는 함수
*/
function loadNextMonth() {
console.log('[마일리지 요약 페이지] 다음 달 로드 시작');
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1);
console.log(`[마일리지 요약 페이지] 다음 달: ${newDate.getFullYear()}년 ${newDate.getMonth() + 1}월`);
renderCalendar(newDate, 'bottom');
currentDate = newDate;
}
/**
* 스크롤 이벤트 핸들러
* 스크롤 끝에 도달하면 이전/다음 달 로드
*/
function handleScroll() {
if (isLoading) {
console.log('[마일리지 요약 페이지] 로딩 중이므로 스크롤 이벤트 무시');
return;
}
const $container = $(this);
const scrollTop = $container.scrollTop();
const innerHeight = $container.innerHeight();
const scrollHeight = $container[0].scrollHeight;
console.log(`[마일리지 요약 페이지] 스크롤 위치: ${scrollTop}, 컨테이너 높이: ${innerHeight}, 스크롤 전체 높이: ${scrollHeight}`);
if (scrollTop === 0) {
// 맨 위에 도달하면 이전 달 로드
console.log('[마일리지 요약 페이지] 스크롤 상단 도달, 이전 달 로드');
isLoading = true;
const previousScrollHeight = $container[0].scrollHeight;
loadPreviousMonth();
$container.scrollTop($container[0].scrollHeight - previousScrollHeight);
console.log(`[마일리지 요약 페이지] 스크롤 위치 조정: ${$container[0].scrollHeight - previousScrollHeight}`);
isLoading = false;
} else if (scrollTop + innerHeight >= scrollHeight - 10) {
// 맨 아래에 도달하면 다음 달 로드
console.log('[마일리지 요약 페이지] 스크롤 하단 도달, 다음 달 로드');
isLoading = true;
loadNextMonth();
isLoading = false;
}
}
/**
* 섭취 완료 후 미완료 날짜 스타일 처리 함수
* @param {jQuery} $datesGrid - 날짜 그리드 요소
*/
function hideAfterStatsDone($datesGrid) {
console.log('[마일리지 요약 페이지] hideAfterStatsDone 함수 실행');
const statsDoneElements = $datesGrid.find(".stats_done");
console.log(`[마일리지 요약 페이지] stats_done 클래스 요소 수: ${statsDoneElements.length}`);
statsDoneElements.each(function(index) {
const $nextElement = $(this).next();
console.log(`[마일리지 요약 페이지] stats_done 요소 ${index + 1}, 다음 요소 존재: ${$nextElement.length > 0}`);
if ($nextElement.length &&
!$nextElement.hasClass("stats_done") &&
!$nextElement.hasClass("stats_30done")) {
$(this).addClass("hide-after");
console.log(`[마일리지 요약 페이지] stats_done 요소 ${index + 1}에 hide-after 클래스 추가`);
}
});
}
// 이벤트 리스너 등록
$calendarContainer.on("scroll", handleScroll);
console.log('[마일리지 요약 페이지] 스크롤 이벤트 리스너 등록 완료');
// 섭취 완료 버튼 클릭 이벤트 (동적으로 추가된 요소에 대한 이벤트 바인딩)
$(document).on("click", ".drug_off", function(e) {
e.preventDefault();
const $this = $(this);
console.log('[마일리지 요약 페이지] drug_off 클릭 이벤트 발생');
$this.removeClass("drug_off").addClass("drug_on");
$this.text("섭취완료");
console.log('[마일리지 요약 페이지] 클래스 drug_off에서 drug_on으로 변경, 텍스트 "섭취완료"로 변경');
// 섭취 완료 후 클릭 이벤트 제거
$this.css("pointer-events", "none");
console.log('[마일리지 요약 페이지] 클릭 이벤트 제거 (pointer-events: none)');
// 여기에 섭취 완료 API 호출 로직 추가 가능
console.log('[마일리지 요약 페이지] 섭취 완료 API 호출 위치 (아직 구현되지 않음)');
});
// 달력 초기화: 이전 달과 현재 달 모두 렌더링
function initializeCalendar() {
console.log('[마일리지 요약 페이지] 달력 초기화 시작');
// 이전 달 렌더링
const prevMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
console.log(`[마일리지 요약 페이지] 이전 달 렌더링: ${prevMonth.getFullYear()}년 ${prevMonth.getMonth() + 1}월`);
renderCalendar(prevMonth, 'top');
// 현재 달 렌더링
console.log(`[마일리지 요약 페이지] 현재 달 렌더링: ${currentDate.getFullYear()}년 ${currentDate.getMonth() + 1}월`);
renderCalendar(currentDate, 'bottom');
// 현재 달로 스크롤 포지션 설정 (약간의 지연을 두어 렌더링 완료 후 스크롤)
console.log('[마일리지 요약 페이지] 스크롤 위치 설정 지연 시작 (100ms)');
setTimeout(function() {
// 이전 달 높이를 찾아서 그 위치로 스크롤
const $monthContainers = $(".month-container");
console.log(`[마일리지 요약 페이지] 월 컨테이너 수: ${$monthContainers.length}`);
if ($monthContainers.length > 1) {
const firstMonthHeight = $($monthContainers[0]).outerHeight();
console.log(`[마일리지 요약 페이지] 첫 번째 월 높이: ${firstMonthHeight}px`);
$calendarContainer.scrollTop(firstMonthHeight);
console.log(`[마일리지 요약 페이지] 스크롤 위치 설정: ${firstMonthHeight}px`);
} else {
console.log('[마일리지 요약 페이지] 월 컨테이너가 충분하지 않아 스크롤 위치 설정 생략');
}
}, 100);
}
// 달력 초기화 함수 호출
initializeCalendar();
console.log('[마일리지 요약 페이지] 초기화 완료');
});

View File

@@ -0,0 +1,296 @@
/**
* 섭취챌린지 제품 상세 페이지 관련 자바스크립트 (jQuery 버전)
*
* 제품 섭취 상태, 섭취 횟수, 메모 정보 관리 및 저장 기능 구현
*/
$(document).ready(function() {
// 초기 설정 및 이벤트 바인딩
initializeEvents();
// 페이지 로드 시 초기 상태 확인 및 UI 업데이트
updateIntakeCounterState();
checkInitialState(); // 초기 상태 확인 추가
// 삭제 버튼(data-micromodal-trigger 속성이 있는 버튼)만 선택
$(".header__toBack__btn[data-micromodal-trigger='modal-1']").on('click', function() {
// 모달 표시
MicroModal.show('modal-1');
});
// 뒤로가기 버튼(data-micromodal-trigger 속성이 없는 버튼) 이벤트
$(".header__toBack__btn:not([data-micromodal-trigger])").on('click', function() {
goBackToPreviousPage();
});
// 모달 확인 버튼 클릭 이벤트
$("#modal-1 .microModal__btn-primary").on('click', function() {
deleteProduct();
});
});
/**
* 초기 이벤트 설정 함수
*/
function initializeEvents() {
// 카운터 버튼 이벤트 설정
$('#decrease').on('click', function() {
let count = parseInt($('#count').text());
if (count > 1) {
$('#count').text(--count);
}
});
// 증가 버튼 클릭 이벤트
$('#increase').on('click', function() {
let count = parseInt($('#count').text());
$('#count').text(++count);
});
// 저장 버튼 클릭 이벤트
$('#save_toast').on('click', saveProductDetails);
// 섭취 상태 변경 이벤트
$('input[name="intakeStatus"]').on('change', function() {
updateIntakeCounterState();
});
}
/**
* 섭취 상태에 따른 섭취설정 영역 활성화/비활성화 처리
*/
function updateIntakeCounterState() {
const intakeStatus = $('input[name="intakeStatus"]:checked').val();
const isDisabled = intakeStatus === '030'; // 종료 상태인 경우
// 섭취 횟수 영역 UI 요소들
const $counterArea = $('.ci_counter');
const $decreaseBtn = $('#decrease');
const $increaseBtn = $('#increase');
if (isDisabled) {
// 종료 상태일 때 비활성화
$counterArea.addClass('disabled');
$decreaseBtn.prop('disabled', true);
$increaseBtn.prop('disabled', true);
$counterArea.css('opacity', '0.5');
$counterArea.css('pointer-events', 'none');
} else {
// 다른 상태일 때 활성화
$counterArea.removeClass('disabled');
$decreaseBtn.prop('disabled', false);
$increaseBtn.prop('disabled', false);
$counterArea.css('opacity', '1');
$counterArea.css('pointer-events', 'auto');
}
}
/**
* 제품 상세 정보 저장 함수
*/
function saveProductDetails() {
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb'); // 파라미터에서 값 가져오기
// 데이터 수집
// const intakeStatus = $('input[name="intakeStatus"]:checked').val();
const intakeStatus = '010'; // 무조건 섭취중 코드로 설정
const intakeCount = parseInt($('#count').text());
const memo = $('.white_textarea').val();
const bnfNtkInnb = $('#bnfNtkInnb').val(); // 히든 필드에서 값 가져오기
// 데이터 객체 생성
const requestData = {
bnfNtkSttusDvCd: intakeStatus,
ntkCnt: intakeCount,
memo: memo,
bnfNtkInnb: bnfNtkInnb,
bnfInnb: bnfInnb
};
// API 호출
sendDataToServer(requestData);
}
/**
* URL에서 bnfNtkInnb 파라미터 추출
* @returns {number} bnfNtkInnb 값 (없으면 null)
*/
function getBnfNtkInnbFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
const bnfNtkInnb = urlParams.get('bnfNtkInnb');
return bnfNtkInnb ? Number(bnfNtkInnb) : null;
}
/**
* 서버에 데이터 전송
* @param {Object} data - 전송할 데이터 객체
*/
function sendDataToServer(data) {
$.ajax({
url: '/sopmal/bnft/updateDailyIntakeAndMemo',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(response) {
try {
// resBody.result가 문자열로 되어 있으므로 파싱
const resultData = JSON.parse(response.resBody.result);
if (resultData.resultCode === '0000') {
showToast(resultData.message || '저장되었습니다.');
// 종료 상태인 경우 전체 폼 비활성화
if (data.bnfNtkSttusDvCd === '030') {
disableEntireForm();
}
} else {
showToast('저장 중 오류가 발생했습니다.');
}
} catch (e) {
console.error('Error parsing result:', e);
showToast('저장 중 오류가 발생했습니다.');
}
},
error: function(xhr, status, error) {
console.error('Error:', error);
showToast('저장 중 오류가 발생했습니다.');
}
});
}
/**
* 토스트 메시지 표시
* @param {string} message - 표시할 메시지
*/
function showToast(message) {
$('#toastPop p').text(message);
$('#toastPop').addClass('show');
// 2초 후 토스트 메시지 숨김
setTimeout(function() {
$('#toastPop').removeClass('show');
}, 2000);
}
// 페이지 로드 시 종료 상태인지 확인하여 폼 비활성화
function checkInitialState() {
const currentStatus = $('input[name="intakeStatus"]:checked').val();
if (currentStatus === '030') {
disableEntireForm();
}
}
// 전체 폼 비활성화 함수
function disableEntireForm() {
// 라디오 버튼, 카운터, 메모, 저장 버튼 모두 비활성화
$('input[name="intakeStatus"]').prop('disabled', true);
$('.ci_counter').css({'opacity': '0.5', 'pointer-events': 'none'});
$('.white_textarea').prop('readonly', true);
$('#save_toast').prop('disabled', true).css('opacity', '0.5');
// 종료 알림 표시
$('.challengeSet').append('<div class="notice">종료된 챌린지는 수정할 수 없습니다.</div>');
}
/**
* 이전 페이지로 돌아가기 함수
*/
function goBackToPreviousPage() {
const urlParams = new URLSearchParams(window.location.search);
const bnfInnb = urlParams.get('bnfInnb');
const returnPage = urlParams.get('returnPage');
console.log('이전 페이지로 돌아가기:', returnPage);
switch (returnPage) {
case 'dailyRecord':
// 일일 기록 페이지에서 왔을 경우
if (bnfInnb) {
window.location.href = `/sopmal/bnft/intakchlDailyRecordMain?bnfInnb=${bnfInnb}`;
} else {
window.location.href = '/sopmal/bnft/intakchlDailyRecordMain';
}
break;
case 'mileageSummary':
// 마일리지 요약 페이지에서 왔을 경우
if (bnfInnb) {
window.location.href = `/sopmal/bnft/intakchlMileageSummary?bnfInnb=${bnfInnb}`;
} else {
window.location.href = '/sopmal/bnft/intakchlMileageSummary';
}
break;
case 'productManagement':
// 제품 관리 페이지에서 왔을 경우
if (bnfInnb) {
window.location.href = `/sopmal/bnft/intakchlChallengeProductManagement?bnfInnb=${bnfInnb}`;
} else {
window.location.href = '/sopmal/bnft/intakchlChallengeProductManagement';
}
break;
case 'main':
// 메인 페이지에서 왔을 경우
window.location.href = '/sopmal/bnft/bnftMain';
break;
default:
// 기본값은 제품 관리 페이지
if (bnfInnb) {
window.location.href = `/sopmal/bnft/intakchlChallengeProductManagement?bnfInnb=${bnfInnb}`;
} else {
window.location.href = '/sopmal/bnft/intakchlChallengeProductManagement';
}
break;
}
}
/**
* 제품 삭제 함수
*/
function deleteProduct() {
// 삭제할 제품의 고유번호 가져오기
const bnfNtkInnb = $('#bnfNtkInnb').val();
// 삭제 요청 데이터 객체 생성
const requestData = {
bnfNtkInnb: bnfNtkInnb
};
// 삭제 API 호출
$.ajax({
url: '/sopmal/bnft/deleteIntakeChallengeProduct',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(requestData),
success: function(response) {
// resBody.result가 문자열로 오므로 JSON으로 파싱해야 함
if (response && response.resBody && response.resBody.result) {
const result = JSON.parse(response.resBody.result);
if (result.success === true) {
// 삭제 성공
showToast(result.message || '제품이 삭제되었습니다.');
// 1.5초 후 이전 페이지로 이동
setTimeout(function() {
goBackToPreviousPage();
}, 1500);
} else {
// 삭제 실패
showToast(result.message || '삭제 중 오류가 발생했습니다.');
MicroModal.close('modal-1');
}
} else {
// 응답 구조가 예상과 다른 경우
showToast('삭제 중 오류가 발생했습니다.');
MicroModal.close('modal-1');
}
},
error: function(xhr, status, error) {
console.error('Error:', error);
showToast('삭제 중 오류가 발생했습니다.');
MicroModal.close('modal-1');
}
});
}

View File

@@ -0,0 +1,487 @@
/**
* Lucklott.js
*
* 행운 복권(스크래치) 기능을 구현한 모듈
* 사용자가 화면을 스크래치하여 당첨 여부를 확인하는 인터랙티브 기능 제공
*
* 주요 기능:
* 1. 스크래치 캔버스 구현 및 관리
* 2. 당첨 결과 확인 및 표시
* 3. 사용자 인터랙션 처리
* 4. 모달 팝업을 통한 결과 안내
*/
$(document).ready(function() {
// 기본 변수 초기화
let canvas = $('.scrachMask')[0]; // 스크래치 마스크 캔버스 요소
let ctx = canvas.getContext('2d'); // 캔버스 컨텍스트
let isDrawing = false; // 그리기 상태 플래그
let totalPixels; // 전체 픽셀 수
let clearedPixels = 0; // 지워진 픽셀 수
let isPopupShown = false; // 팝업 표시 상태
let isPrizeRequested = false; // 당첨 결과 요청 여부 플래그 추가
let prize; // 당첨 결과 저장 변수
let prizeResult = null; // 미리 받아온 당첨 결과
let bnfInnb = $('#bnfInnb').val(); // 혜택 고유 번호
// bnfInnb 값 확인 및 유효성 검사
console.log('bnfInnb value:', bnfInnb);
if (!bnfInnb || bnfInnb === '' || bnfInnb === 'null' || bnfInnb === 'undefined') {
console.error('bnfInnb 값이 올바르지 않습니다:', bnfInnb);
// 에러 모달 표시
$('#modal-1 .microModal__title').html('오류');
$('#modal-1 .modal-icon-present').hide();
$('#modal-1 .sub_tit').html('혜택 정보를 불러올 수 없습니다.');
$('#modal-1 p:last-child').html('페이지를 새로고침 해주세요.');
MicroModal.show('modal-1');
return;
}
// 화면 로딩 시 재고 상태 체크 (추가)
checkStockStatus();
/**
* 화면 로딩시 재고 상태 체크
*/
function checkStockStatus() {
$.ajax({
url: '/sopmal/bnft/checkRemainingGoodsQuantity',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: bnfInnb
}),
success: function(response) {
console.log('checkStockStatus response:', response);
// 응답 결과 파싱
let result = JSON.parse(response.resBody.result);
// 재고가 없는 경우 처리
if (!result.hasAnyStock && result.totalRewards > 0) {
// 품절 모달 표시
MicroModal.show('modal-sold-out');
return;
}
// 재고가 있는 경우 정상 진행
console.log('재고 상태:', result);
},
error: function() {
console.error('재고 상태 확인 실패');
// 에러 발생 시에도 품절 모달 표시
MicroModal.show('modal-sold-out');
}
});
}
/**
* 캔버스 크기 조정 및 초기화 함수
* 화면 크기에 맞게 캔버스를 조정하고 그라데이션 배경 생성
*/
function resizeCanvas() {
// 캔버스 크기를 컨테이너에 맞게 설정
canvas.width = $('.luckyScrachBox').width();
canvas.height = $('.luckyScrachBox').height();
// 그라데이션 배경 생성
let gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#d3d3d3'); // 시작 색상
gradient.addColorStop(0.25, '#e9e9e9'); // 25% 지점 색상
gradient.addColorStop(0.5, '#ddd'); // 50% 지점 색상
gradient.addColorStop(0.75, '#eee'); // 75% 지점 색상
gradient.addColorStop(1, '#ddd'); // 끝 색상
// 그라데이션으로 캔버스 채우기
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 전체 픽셀 수 계산
totalPixels = canvas.width * canvas.height;
}
/**
* 마우스/터치 위치 계산 함수
* 이벤트 객체에서 캔버스 내 상대 좌표를 계산
* @param {Event} e - 마우스 또는 터치 이벤트 객체
* @return {Object} - x, y 좌표를 포함한 객체
*/
function getOffset(e) {
let offset = $(canvas).offset();
// 마우스 또는 터치 이벤트의 좌표 추출
let x = (e.pageX || e.originalEvent.touches[0].pageX) - offset.left;
let y = (e.pageY || e.originalEvent.touches[0].pageY) - offset.top;
return { x: x, y: y };
}
/**
* 스크래치 진행률 확인 함수
* 캔버스에서 지워진 픽셀의 비율을 계산하고 진행 상태에 따라 처리
*/
function checkClearedPercentage() {
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let pixels = imageData.data;
clearedPixels = 0;
for (let i = 3; i < pixels.length; i += 4) {
if (pixels[i] === 0) {
clearedPixels++;
}
}
let clearedPercentage = (clearedPixels / totalPixels) * 100;
// 80% 이상 지워졌고 팝업이 아직 표시되지 않았다면 실제 참여 처리
if (clearedPercentage > 80 && !isPopupShown && prizeResult) {
isPopupShown = true;
// 실제 참여 처리
confirmParticipation();
}
if (clearedPercentage > 80) {
isDrawing = false;
}
}
/**
* 당첨 결과 확인 함수
* 서버에 API 요청을 보내 당첨 결과를 받아옴
* @return {Promise} - 당첨 결과를 담은 Promise 객체
*/
function determinePrize() {
return new Promise((resolve, reject) => {
$.ajax({
url: '/sopmal/bnft/participateLuckyLottery',
type: 'POST',
data: JSON.stringify({
bnfInnb: bnfInnb
}),
contentType: 'application/json',
success: function(response) {
let result = JSON.parse(response.resBody.result);
if (!result.success) {
reject(new Error(response.message));
return;
}
resolve(result);
},
error: function(error) {
reject(error);
}
});
});
}
/**
* 화면 로딩 시 당첨 결과를 미리 받아와서 이미지 설정 (실제 참여 안함)
*/
function preloadPrizeAndSetImages() {
if (isPrizeRequested) return;
isPrizeRequested = true;
// bnfInnb를 숫자로 변환하여 전송
let bnfInnbNumber = parseInt(bnfInnb);
if (isNaN(bnfInnbNumber)) {
console.error('bnfInnb를 숫자로 변환할 수 없습니다:', bnfInnb);
$('#modal-1 .microModal__title').html('오류');
$('#modal-1 .modal-icon-present').hide();
$('#modal-1 .sub_tit').html('혜택 정보가 올바르지 않습니다.');
$('#modal-1 p:last-child').html('페이지를 새로고침 해주세요.');
MicroModal.show('modal-1');
return;
}
// 미리보기 API 호출 (실제 참여 안함)
$.ajax({
url: '/sopmal/bnft/previewLuckyLotteryResult',
type: 'POST',
data: JSON.stringify({
bnfInnb: bnfInnbNumber // 숫자로 전송
}),
contentType: 'application/json',
success: function(response) {
let result = JSON.parse(response.resBody.result);
if (!result.success) {
// 에러 처리
$('#modal-1 .microModal__title').html('안내');
$('#modal-1 .modal-icon-present').hide();
$('#modal-1 .sub_tit').html(result.message || '참여할 수 없습니다.');
$('#modal-1 p:last-child').html('내일 다시 도전해주세요.');
MicroModal.show('modal-1');
return;
}
// 미리보기 결과 저장
prizeResult = result;
// 당첨 등수에 따른 이미지 설정
let rank = result.rank;
if (rank === 0) {
// 행운상일 때는 기존 이미지 유지 (서로 다른 이미지)
$('.scrachGift').html(`
<li><img src="/mobile/images/bnft/lucky_award1.svg" alt="1등"></li>
<li><img src="/mobile/images/bnft/lucky_award2.svg" alt="2등"></li>
<li><img src="/mobile/images/bnft/lucky_award3.svg" alt="3등"></li>
`);
} else {
// 일반 당첨일 때는 같은 이미지 3개
let imagePath = `/mobile/images/bnft/lucky_award${rank}.svg`;
let altText = rank + '등';
$('.scrachGift').html(`
<li><img src="${imagePath}" alt="${altText}"></li>
<li><img src="${imagePath}" alt="${altText}"></li>
<li><img src="${imagePath}" alt="${altText}"></li>
`);
}
},
error: function(error) {
console.error('Error in preview:', error);
isPrizeRequested = false;
}
});
}
/**
* 당첨 결과 표시 함수
* 당첨 결과에 따라 적절한 모달 팝업을 표시
* @param {string} prize - JSON 형식의 당첨 결과 문자열
*/
function showPrize(prize) {
let result = JSON.parse(prize);
if (result.success) {
let modalTitle, iconText, subTitle, bottomText;
// 행운상 (rank: 0)인 경우 - 응답 데이터에 따라 구분 처리
if (result.rank === 0) {
modalTitle = '축하합니다!<br>행운상 당첨!';
// 쿠폰 정보가 있는 경우
if (result.couponName && result.couponName.trim() !== '') {
iconText = '쿠폰';
subTitle = result.couponName;
bottomText = 'MY혜택 > 쿠폰에서 확인하세요.';
}
// 마일리지가 있는 경우
else if (result.mileageAmount && result.mileageAmount > 0) {
iconText = '포인트';
subTitle = '마일리지 ' + result.mileageAmount + '점 적립';
bottomText = 'MY혜택 > 포인트에서 확인하세요.';
}
// bnfRwdCateDvCd로 판단
else {
switch (result.bnfRwdCateDvCd) {
case '03010': // 쿠폰
iconText = '쿠폰';
subTitle = result.couponName || '할인 쿠폰';
bottomText = 'MY혜택 > 쿠폰에서 확인하세요.';
break;
case '03020': // 마일리지
iconText = '포인트';
subTitle = result.mileageAmount > 0 ?
'마일리지 ' + result.mileageAmount + '점 적립' : '마일리지 10점 적립';
bottomText = 'MY혜택 > 포인트에서 확인하세요.';
break;
case '03030': // 상품
iconText = '선물';
subTitle = result.couponName || '경품';
bottomText = 'MY혜택에서 확인하세요.';
break;
case '03040': // 쿠폰 (응답의 bnfRwdCateDvCd가 03040인 경우)
iconText = '쿠폰';
subTitle = result.couponName || '할인 쿠폰';
bottomText = 'MY혜택 > 쿠폰에서 확인하세요.';
break;
default:
// 기본값으로 마일리지 처리
iconText = '포인트';
subTitle = '마일리지 10점 적립';
bottomText = 'MY혜택 > 포인트에서 확인하세요.';
break;
}
}
}
// 실제 데이터를 보고 판단 (쿠폰 이름이 있으면 쿠폰으로 처리)
else if (result.couponName && result.couponName.trim() !== '') {
iconText = '쿠폰';
modalTitle = '축하합니다!<br>' + result.rank + '등 당첨!';
subTitle = result.couponName;
bottomText = 'MY혜택 > 쿠폰에서 확인하세요.';
}
// 마일리지가 있으면 마일리지로 처리
else if (result.mileageAmount && result.mileageAmount > 0) {
iconText = '포인트';
modalTitle = '축하합니다!<br>' + result.rank + '등 당첨!';
subTitle = '마일리지 ' + result.mileageAmount + '점 적립';
bottomText = 'MY혜택 > 포인트에서 확인하세요.';
}
// bnfRwdCateDvCd 코드를 보고 판단
else {
switch (result.bnfRwdCateDvCd) {
case '03020': // 마일리지
iconText = '포인트';
modalTitle = '축하합니다!<br>' + result.rank + '등 당첨!';
subTitle = result.mileageAmount > 0 ?
'마일리지 ' + result.mileageAmount + '점 적립' : '마일리지가 적립되었습니다!';
bottomText = 'MY혜택 > 포인트에서 확인하세요.';
break;
case '03010': // 쿠폰
iconText = '쿠폰';
modalTitle = '축하합니다!<br>' + result.rank + '등 당첨!';
subTitle = result.couponName || '할인 쿠폰';
bottomText = 'MY혜택 > 쿠폰에서 확인하세요.';
break;
case '03030': // 상품
iconText = '선물';
modalTitle = '축하합니다!<br>' + result.rank + '등 당첨!';
subTitle = result.couponName || '경품';
bottomText = 'MY혜택에서 확인하세요.';
break;
default:
// 기본값으로 마일리지 처리
iconText = '포인트';
modalTitle = '축하합니다!<br>' + result.rank + '등 당첨!';
subTitle = '마일리지가 적립되었습니다!';
bottomText = 'MY혜택 > 포인트에서 확인하세요.';
break;
}
}
// 모달 내용 설정 및 표시
$('#modal-1 .microModal__title').html(modalTitle);
$('#modal-1 .modal-icon-present').html(iconText);
$('#modal-1 .sub_tit').html(subTitle);
$('#modal-1 p:last-child').html(bottomText);
MicroModal.show('modal-1');
} else {
// 실패 시 기본 모달 표시
MicroModal.show('modal-2');
}
}
/**
* 실제 게임 참여 확정 처리
*/
function confirmParticipation() {
let bnfInnbNumber = parseInt(bnfInnb);
if (isNaN(bnfInnbNumber)) {
console.error('bnfInnb를 숫자로 변환할 수 없습니다:', bnfInnb);
return;
}
$.ajax({
url: '/sopmal/bnft/confirmLuckyLotteryParticipation',
type: 'POST',
data: JSON.stringify({
bnfInnb: bnfInnbNumber, // 숫자로 전송
previewResult: prizeResult
}),
contentType: 'application/json',
success: function(response) {
let result = JSON.parse(response.resBody.result);
if (result.success) {
// 실제 참여 완료 후 결과 표시
showPrize(JSON.stringify(result));
} else {
// 실패 처리
$('#modal-1 .microModal__title').html('안내');
$('#modal-1 .modal-icon-present').hide();
$('#modal-1 .sub_tit').html(result.message || '참여 처리 중 오류가 발생했습니다.');
$('#modal-1 p:last-child').html('다시 시도해주세요.');
MicroModal.show('modal-1');
}
},
error: function(error) {
console.error('Error confirming participation:', error);
// 에러 처리
$('#modal-1 .microModal__title').html('오류');
$('#modal-1 .modal-icon-present').hide();
$('#modal-1 .sub_tit').html('참여 처리 중 오류가 발생했습니다.');
$('#modal-1 p:last-child').html('다시 시도해주세요.');
MicroModal.show('modal-1');
}
});
}
// 윈도우 크기 변경 시 캔버스 크기 조정
$(window).resize(resizeCanvas);
// 초기 캔버스 설정
resizeCanvas();
// 화면 로딩 시 당첨 결과 미리 받아오기
preloadPrizeAndSetImages();
// 품절 모달의 메인 이동 버튼 이벤트 설정
$('#btn-go-main').on('click', function() {
window.location.href = '/sopmal/bnft/bnftMain';
});
/**
* 모달 닫기 버튼 클릭 이벤트 핸들러
* 모달을 닫고 캔버스를 초기화
*/
$('.microModal__btnContainer').click(function() {
MicroModal.close(); // 모달 닫기
isPopupShown = false; // 팝업 상태 초기화
resizeCanvas(); // 캔버스 초기화
isDrawing = false; // 그리기 상태 초기화
});
/**
* 마우스/터치 시작 이벤트 핸들러
* 스크래치 시작 시 그리기 상태 설정
*/
$(canvas).on('mousedown touchstart', function(e) {
// 이미 그리는 중이거나 80% 이상 지워진 경우 무시
if (isDrawing || clearedPixels / totalPixels * 100 > 80) return;
isDrawing = true;
});
/**
* 마우스/터치 이동 이벤트 핸들러
* 스크래치 효과를 구현하고 진행률 확인
*/
$(canvas).on('mousemove touchmove', function(e) {
if (!isDrawing) return;
e.preventDefault();
// 현재 마우스/터치 위치 계산
let pos = getOffset(e);
// 지우기 모드로 설정 (스크래치 효과)
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
// 원형 브러시로 지우기
ctx.arc(pos.x, pos.y, 20, 0, Math.PI * 2, false);
ctx.fill();
// 스크래치 진행률 확인
checkClearedPercentage();
});
/**
* 마우스/터치 종료 이벤트 핸들러
* 그리기 상태 플래그 리셋
*/
$(canvas).on('mouseup touchend', function() {
isDrawing = false;
});
/**
* 모달 닫기 버튼 클릭 이벤트 핸들러
* 모달을 닫고 캔버스를 초기화 (품절 상황이 아닐 때만)
*/
// 기존의 전역 이벤트 핸들러는 제거하고, 필요시에만 등록하도록 변경
function setupNormalModalHandler() {
$('.microModal__btnContainer').off('click').on('click', function() {
MicroModal.close(); // 모달 닫기
isPopupShown = false; // 팝업 상태 초기화
resizeCanvas(); // 캔버스 초기화
isDrawing = false; // 그리기 상태 초기화
});
}
});

View File

@@ -0,0 +1,431 @@
$(document).ready(function() {
/* 시간 관련 코드 주석 처리
// 경과 시간 업데이트
const elapsedTimer = $('p.live_time[data-elapsed-seconds]');
if (elapsedTimer.length > 0) {
let elapsedSeconds = parseInt(elapsedTimer.data('elapsed-seconds'), 10);
elapsedTimer.text(formatElapsedTime(elapsedSeconds)); // 초기 표시
setInterval(function() {
elapsedSeconds++;
elapsedTimer.text(formatElapsedTime(elapsedSeconds));
}, 1000);
}
// 남은 시간 업데이트
const remainingTimers = $('[data-remaining-seconds]');
remainingTimers.each(function() {
const timerElement = $(this);
let remainingSeconds = parseInt(timerElement.data('remaining-seconds'), 10);
const updateRemainingTime = () => {
if (remainingSeconds > 0) {
timerElement.text(formatRemainingTime(remainingSeconds));
remainingSeconds--;
} else {
timerElement.text("방송 시작!"); // 또는 페이지 새로고침 등
clearInterval(intervalId); // 타이머 중지
// 필요시 페이지 새로고침 또는 상태 변경 로직 추가
// location.reload();
}
};
updateRemainingTime(); // 초기 표시
const intervalId = setInterval(updateRemainingTime, 1000); // 1초마다 업데이트
// 요소 제거 시 인터벌 정리 (옵션)
// timerElement.on('remove', function() {
// clearInterval(intervalId);
// });
});
*/
// 경과 시간 업데이트
const elapsedTimer = $('p.live_time[data-elapsed-seconds]');
if (elapsedTimer.length > 0) {
let elapsedSeconds = parseInt(elapsedTimer.data('elapsed-seconds'), 10);
elapsedTimer.text(formatElapsedTime(elapsedSeconds)); // 초기 표시
setInterval(function() {
elapsedSeconds++;
elapsedTimer.text(formatElapsedTime(elapsedSeconds));
}, 1000);
}
// 남은 시간 업데이트
const remainingTimers = $('[data-remaining-seconds]');
remainingTimers.each(function() {
const timerElement = $(this);
let remainingSeconds = parseInt(timerElement.data('remaining-seconds'), 10);
const updateRemainingTime = () => {
if (remainingSeconds > 0) {
timerElement.text(formatRemainingTime(remainingSeconds));
remainingSeconds--;
} else {
timerElement.text("방송 시작!"); // 또는 페이지 새로고침 등
clearInterval(intervalId); // 타이머 중지
// 필요시 페이지 새로고침 또는 상태 변경 로직 추가
// location.reload();
}
};
updateRemainingTime(); // 초기 표시
const intervalId = setInterval(updateRemainingTime, 1000); // 1초마다 업데이트
});
});
// 알림 신청/취소 토글 함수
function toggleNotification(buttonElement) {
const button = $(buttonElement);
const isApplying = button.hasClass('outline'); // outline 클래스가 있으면 신청하려는 상태
const url = $('#toggleNotificationUrl').val();
const requestData = {
bnfDvCd: '070',
bnfAgreDvCd: '010',
agreAt: isApplying ? 'Y' : 'N' // 서버에 보낼 동의 여부 값만 전송
};
// bnfInnb 값이 있는 경우에만 추가 (선택적)
const bnfInnb = button.data('bnf-innb');
if (bnfInnb && bnfInnb !== "0") {
requestData.bnfInnb = bnfInnb;
}
$.ajax({
url: url,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(requestData),
dataType: 'json',
success: function(response) {
// 응답 구조에 맞게 처리
if (response && response.resBody) {
try {
// resBody.result가 문자열로 오는 경우 파싱
const resultData = typeof response.resBody.result === 'string'
? JSON.parse(response.resBody.result)
: response.resBody.result;
if (resultData && resultData.success) {
// 버튼 상태 업데이트
button.toggleClass('outline'); // 클래스 토글
const newText = isApplying ? '알림신청 완료' : '알림신청';
// 아이콘은 유지하고 텍스트만 변경
button.contents().filter(function() {
return this.nodeType === 3; // 텍스트 노드만 선택
}).last().replaceWith(" " + newText); // 마지막 텍스트 노드 변경 (아이콘 뒤 공백 유지)
// 동일한 bnfInnb를 가진 다른 버튼들도 동기화 (예: 메인 알림 버튼과 목록 내 알림 버튼)
if (bnfInnb && bnfInnb !== "0") {
$(`.btn.live_btn[data-bnf-innb='${bnfInnb}']`).not(button).each(function() {
const otherButton = $(this);
otherButton.toggleClass('outline', !isApplying); // 현재 버튼과 반대로 클래스 설정
otherButton.contents().filter(function() {
return this.nodeType === 3;
}).last().replaceWith(" " + newText);
});
}
// 성공 메시지 표시 (옵션)
// showCustomAlert(resultData.message || (isApplying ? "알림 신청이 완료되었습니다." : "알림 신청이 취소되었습니다."));
} else {
// 실패 메시지 표시
showCustomAlert(resultData.message || "알림 상태 변경에 실패했습니다.");
}
} catch (e) {
console.error("Error parsing response:", e);
showCustomAlert("응답 처리 중 오류가 발생했습니다.");
}
} else {
showCustomAlert("알림 상태 변경에 실패했습니다.");
}
},
error: function(xhr, status, error) {
console.error("Error toggling notification:", status, error);
showCustomAlert("오류가 발생했습니다. 다시 시도해주세요.");
}
});
}
/* 앱 환경에서 URL 열기 함수 주석 처리
function _app_open_blank_url(url, requiredWatchTime) {
// 문자열을 숫자로 변환
const watchTimeNumber = parseInt(requiredWatchTime, 10);
// 앱 환경 확인
if (window.app && typeof window.app.open_blank_url === 'function') {
// 앱 함수 호출 (숫자 전달)
window.app.open_blank_url(url, watchTimeNumber);
} else {
// 웹 환경에서 대체 구현
const newWindow = window.open(url, '_blank');
// 콜백 타이머 설정
setTimeout(() => {
if (currentMileageReward && currentBnfInnb) {
awardReward(currentMileageReward, currentBnfInnb);
}
}, watchTimeNumber * 1000);
}
}
*/
/*
Web -> App 외부 브라우저(기본 브라우저)를 호출
*/
function _openExternalBrowser(url){
// URL 공백 제거 및 검증
if (!url) {
console.error('URL이 없습니다.');
return;
}
// 앞뒤 공백 제거
url = url.trim();
console.log('원본 URL:', url);
// URL 프로토콜 검사 및 보정
if (!url.startsWith('http://') && !url.startsWith('https://')) {
// http나 https가 없으면 https를 기본으로 추가
url = 'https://' + url;
console.log('URL에 프로토콜 추가 후:', url);
} else {
console.log('URL에 이미 프로토콜 있음:', url);
}
// 앱 - 웹 인터페이스 확인
if(window.app){
console.info("window.app, _openExternalBrowser, 최종 URL:", url);
window.app.openExternalBrowser(url);
}
else {
console.log('window.app is undefined, opening in current window, 최종 URL:', url);
window.location.href = url;
}
}
// 페이지 이동 URL을 저장할 전역 변수
let pendingRedirectUrl = null;
// 라이브 참여 처리 함수 - 페이지를 먼저 이동하고 나중에 모달 표시
function handleLiveParticipation(bnfInnb, mileageReward, participationUrl) {
const mileageNumber = parseFloat(mileageReward);
console.log("라이브 참여 처리, bnfInnb:", bnfInnb, "mileageReward:", mileageReward);
// 1. 먼저 페이지 이동 (모달 표시 없이)
_openExternalBrowser(participationUrl);
// 2. 보상이 있는 경우에만 페이지 이동 후 API 호출하고 모달 표시
if (mileageNumber > 0) {
setTimeout(function() {
const recordAndRewardUrl = $('#recordAndRewardUrl').val();
const requestData = {
bnfInnb: bnfInnb,
mlgRwdAmt: mileageReward
};
$.ajax({
url: recordAndRewardUrl,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(requestData),
dataType: 'json',
success: function(response) {
console.log("API 응답:", response);
// 응답 구조 확인 및 안전한 파싱
if (response && response.resBody) {
try {
let resultData;
// resBody.result가 문자열인 경우 JSON 파싱
if (typeof response.resBody.result === 'string') {
try {
resultData = JSON.parse(response.resBody.result);
} catch (parseError) {
console.error("JSON 파싱 오류:", parseError);
resultData = { success: false, message: "응답 처리 중 오류가 발생했습니다." };
}
} else if (typeof response.resBody.result === 'object') {
resultData = response.resBody.result;
} else {
resultData = { success: false, message: "예상치 못한 응답 형식입니다." };
}
console.log("파싱된 결과 데이터:", resultData);
if (resultData && resultData.success) {
console.log("참여 기록 및 마일리지 지급 완료");
// 성공 시 모달 표시 (페이지 이동 후)
showCustomAlert(resultData.message || "라이브 쇼핑 참여 및 마일리지 적립이 완료되었습니다.");
} else {
console.error("참여 기록 및 마일리지 지급 실패:", resultData);
showCustomAlert(resultData.message || "라이브 참여 처리에 실패했습니다.");
}
} catch (e) {
console.error("전체 응답 처리 오류:", e);
showCustomAlert("응답 처리 중 오류가 발생했습니다. 다시 시도해주세요.");
}
} else if (response && response.content) {
console.log("content 형식 응답:", response.content);
showCustomAlert(response.content.message || "처리가 완료되었습니다.");
} else {
console.error("잘못된 응답 형식:", response);
showCustomAlert("서버 응답 형식이 올바르지 않습니다. 다시 시도해주세요.");
}
},
error: function(xhr, status, error) {
console.error("참여 처리 오류:", status, error);
console.error("상세 오류:", xhr.responseText);
showCustomAlert("참여 처리 중 오류가 발생했습니다. 다시 시도해주세요.");
}
});
}, 2000); // 2초 후 API 호출 - 페이지 이동이 완료된 후 모달 표시
}
}
// 마일리지 지급 함수
function awardReward(mlgRwdAmt, bnfInnb) {
console.log("마일리지 지급 처리. 금액:", mlgRwdAmt, "bnfInnb:", bnfInnb);
const awardUrl = $('#awardRewardUrl').val();
const requestData = {
mlgRwdAmt: mlgRwdAmt,
bnfInnb: bnfInnb
};
$.ajax({
url: awardUrl,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(requestData),
dataType: 'json',
success: function(response) {
if (response && response.content) {
console.log("마일리지 지급 응답:", response.content);
// 성공/실패 메시지 표시
showCustomAlert(response.content.message || "마일리지 지급 처리 결과");
} else {
console.error("마일리지 지급 API 응답 형식 오류");
showCustomAlert("마일리지 지급 처리 중 응답 오류가 발생했습니다.");
}
},
error: function(xhr, status, error) {
console.error("마일리지 지급 오류:", status, error);
showCustomAlert("마일리지 지급 중 오류가 발생했습니다. 다시 시도해주세요.");
}
});
}
// 시간 포맷 함수 복원
// 초를 "X일 Y시간 Z분" 등으로 변환하는 함수
function formatRemainingTime(seconds) {
if (seconds <= 0) return "잠시 후 시작";
const days = Math.floor(seconds / (3600 * 24));
seconds %= (3600 * 24);
const hours = Math.floor(seconds / 3600);
seconds %= 3600;
const minutes = Math.floor(seconds / 60);
const remainingSecs = seconds % 60;
let result = "";
if (days > 0) result += days + "일 ";
if (hours > 0) result += hours + "시간 ";
if (days > 0 || hours > 0) {
if (minutes > 0) result += minutes + "분 ";
} else if (minutes > 0) {
result += minutes + "분 ";
} else if (remainingSecs > 0) {
result += remainingSecs + "초 ";
}
if (days === 0 && hours === 0 && minutes > 0 && remainingSecs > 0) {
result = `${minutes}분 ${remainingSecs}초 `;
} else if (days === 0 && hours === 0 && minutes === 0 && remainingSecs > 0) {
result = `${remainingSecs}초 `;
}
return result.trim() + " 남음";
}
// 초를 HH:MM:SS 형식으로 변환하는 함수
function formatElapsedTime(seconds) {
if (seconds < 0) seconds = 0;
const hours = String(Math.floor(seconds / 3600)).padStart(2, '0');
const minutes = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
const secs = String(seconds % 60).padStart(2, '0');
return `${hours}:${minutes}:${secs}`;
}
/* 앱 콜백 함수 주석 처리
function complete_open_blank_url(url) {
console.log("URL 열기 완료:", url);
alert("URL 열기 완료:" + url);
awardReward(currentMileageReward, currentBnfInnb);
}
*/
// 알림용 모달 표시 함수 - 닫기 기능 강화
function showCustomAlert(message) {
$('#modal-alert .microModal__content p').text(message);
// 기존 이벤트 모두 제거
$('#modal-alert .microModal__btn-primary').off('click');
$('#modal-alert .microModal__overlay').off('click');
// 확인 버튼 클릭 이벤트
$('#modal-alert .microModal__btn-primary').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log("모달 확인 버튼 클릭");
MicroModal.close('modal-alert');
});
// 오버레이 클릭 시 닫기
$('#modal-alert .microModal__overlay').on('click', function(e) {
if (e.target === this) {
console.log("모달 오버레이 클릭");
MicroModal.close('modal-alert');
}
});
// ESC 키로 닫기
$(document).off('keydown.modalAlert').on('keydown.modalAlert', function(e) {
if (e.keyCode === 27) { // ESC 키
console.log("ESC 키로 모달 닫기");
MicroModal.close('modal-alert');
$(document).off('keydown.modalAlert');
}
});
console.log("모달 표시:", message);
MicroModal.show('modal-alert');
}
// 모달 표시 후 닫힐 때 페이지 이동
function showAlertThenRedirect(message, url) {
// 모달 메시지 설정
$('#modal-alert .microModal__content p').text(message);
// 기존 이벤트 제거 후 새로운 클릭 이벤트 설정
$('#modal-alert .microModal__btn-primary').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log("확인 버튼 클릭, 이동할 URL:", url);
// 수동으로 모달 닫기
MicroModal.close('modal-alert');
// 앱이면 외부 브라우저로, 웹이면 현재 창에서 이동
setTimeout(function() {
_openExternalBrowser(url);
}, 200);
});
// 모달 표시 (콜백 없이)
MicroModal.show('modal-alert');
}

View File

@@ -0,0 +1,432 @@
/**
* OXQuiz.js
*
* OX 퀴즈 기능을 구현한 모듈
* 사용자가 OX 퀴즈에 참여하고 결과를 확인하는 기능 제공
*
* 주요 기능:
* 1. OX 퀴즈 참여 및 결과 처리
* 2. 퀴즈 UI 업데이트 및 상태 관리
* 3. 알림 설정 관리
* 4. 모달 팝업을 통한 결과 안내
*/
const OXQuiz = (function() {
/**
* 모듈 초기화 함수
* 이벤트 리스너 등록 및 알림 상태 조회
*/
function init() {
addEventListeners();
getNotificationStatus();
}
/**
* 이벤트 리스너 등록 함수
* 퀴즈 결과 확인, 답변 선택, 알림 설정 버튼에 이벤트 리스너 등록
*/
function addEventListeners() {
$('#quizResult').off('click').on('click', handleQuizResult);
$('.qzSelect a').off('click').on('click', handleAnswerSelection);
$('#notificationBtn').off('click').on('click', handleNotification);
// 힌트 버튼에 이벤트 리스너 추가
$('#hintButton').off('click').on('click', function() {
const hintUrl = screenInfo.quizInfoList[screenInfo.currentQuiz - 1].hintUrl;
openExternalUrl(hintUrl);
});
}
/**
* 답변 선택 처리 함수
* 사용자가 OX 중 하나를 선택했을 때 UI 업데이트
* @param {Event} e - 클릭 이벤트 객체
*/
function handleAnswerSelection(e) {
e.preventDefault();
// 현재 퀴즈가 이미 완료된 상태인지 확인
const currentQuiz = screenInfo.quizInfoList[screenInfo.currentQuiz - 1];
if (currentQuiz.userAnswer !== null) {
showCustomAlert('이미 완료된 퀴즈입니다.');
return;
}
$('.qzSelect a').removeClass('on');
$(this).addClass('on');
}
/**
* 퀴즈 결과 확인 처리 함수
* 사용자가 선택한 답변을 서버에 전송하고 결과 처리
*/
function handleQuizResult() {
const $resultButton = $(this);
// 버튼이 비활성화 상태면 함수 실행 중지
if ($resultButton.prop('disabled')) return;
// 선택된 답변 확인
const selectedAnswer = $('.qzSelect a.on').data('answer');
if (!selectedAnswer) {
showCustomAlert('OX 중 한 가지를 선택해 주세요.');
return;
}
// 중복 클릭 방지를 위한 버튼 비활성화
$resultButton.prop('disabled', true);
// 서버에 퀴즈 참여 요청 전송
$.ajax({
url: '/sopmal/bnft/participate',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: screenInfo.basicInfo.bnfInnb, // 혜택 고유 번호
bnfQuizInnb: screenInfo.quizInfoList[screenInfo.currentQuiz - 1].bnfQuizInnb, // 퀴즈 고유 번호
answCn: selectedAnswer // 선택한 답변(O/X)
}),
success: handleQuizResponse,
error: function(xhr, status, error) {
$resultButton.prop('disabled', false);
handleQuizError(xhr, status, error);
}
});
}
/**
* 퀴즈 응답 처리 함수
* 서버로부터 받은 퀴즈 결과를 처리하고 UI 업데이트
* @param {Object} response - 서버 응답 객체
*/
function handleQuizResponse(response) {
if (response.success) {
updateQuizState(response);
showResultModal(response);
} else {
showCustomAlert(response.message || '퀴즈 참여에 실패했습니다.');
}
}
/**
* 퀴즈 오류 처리 함수
* 서버 요청 중 발생한 오류를 처리하고 사용자에게 안내
* @param {Object} xhr - XMLHttpRequest 객체
* @param {string} status - 요청 상태
* @param {string} error - 오류 메시지
*/
function handleQuizError(xhr, status, error) {
console.error('퀴즈 참여 실패:', error);
let errorMessage = '퀴즈 참여 중 오류가 발생했습니다. ';
if (xhr.status === 404) {
errorMessage += '퀴즈를 찾을 수 없습니다. 페이지를 새로고침 해주세요.';
} else if (xhr.status === 403) {
errorMessage += '권한이 없습니다. 로그인 상태를 확인해주세요.';
} else {
errorMessage += '잠시 후 다시 시도해주세요.';
}
showCustomAlert(errorMessage);
}
/**
* 결과 모달 표시 함수
* 퀴즈 결과에 따라 적절한 모달 팝업을 표시
* @param {Object} response - 서버 응답 객체
*/
function showResultModal(response) {
let modalId;
// 보상 코드에 따른 모달 선택 (수정)
if (response.rewardCodes && response.rewardCodes.includes('13030')) {
modalId = 'modal-3'; // 모든 퀴즈 정답
} else if (response.isCorrect) {
modalId = 'modal-1'; // 정답
} else {
modalId = 'modal-2'; // 오답
}
// 모달 내용 업데이트
const $modal = $(`#${modalId}`);
const $content = $modal.find('.microModal__content');
// 퀴즈 번호와 정답 업데이트
$content.find('.sub_tit:first').html(
`${screenInfo.currentQuiz}번째 퀴즈, 정답은 <b class="c_blue">${response.correctAnswer}</b>입니다.`
);
// 마일리지 금액 업데이트 (수정)
if (response.rewardInfos && response.rewardInfos.length > 0) {
// 모든 보상의 마일리지 합계 계산
const totalMileage = response.rewardInfos.reduce((total, reward) => {
return total + (reward.mlgRwdAmt || 0);
}, 0);
if (totalMileage > 0) {
$content.find('.sub_tit:eq(1)').html(
`마일리지 ${totalMileage.toLocaleString()}원 획득`
);
}
}
// 다음 퀴즈 안내 메시지 업데이트
const lastMessage = screenInfo.currentQuiz < screenInfo.quizCount
? `${screenInfo.currentQuiz + 1}번째 퀴즈도 내일 꼭! 도전하세요.`
: '모든 퀴즈에 도전해 주셔서 감사합니다!';
$content.find('p:last').text(lastMessage);
// 모달 표시 및 닫힐 때 UI 업데이트
MicroModal.show(modalId, {
onClose: function() {
updateQuizUI();
}
});
}
/**
* 알림 설정 토글 함수
* 퀴즈 알림 신청/취소 처리
*/
function handleNotification() {
const $btn = $(this);
const currentStatus = $btn.data('status') === 'on';
const newStatus = !currentStatus;
// 알림 상태 변경 API 호출
$.ajax({
url: '/sopmal/bnft/updateoxquizNotificationStatus',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: screenInfo.basicInfo.bnfInnb, // 혜택 고유 번호
agreAt: newStatus ? 'Y' : 'N' // 알림 동의 여부(Y/N)
}),
success: function(response) {
if (response.success) {
updateNotificationButton(response.notificationStatus);
showCustomAlert(response.notificationStatus ?
"알림 신청이 완료되었습니다." :
"알림 신청이 해제되었습니다.");
} else {
showCustomAlert("알림 상태 변경에 실패했습니다.\n잠시 후 다시 시도해주세요.");
}
},
error: function(xhr, status, error) {
console.error('알림 상태 변경 실패:', error);
showCustomAlert('알림 상태 변경 중 오류가 발생했습니다.');
}
});
}
/**
* 알림 상태 조회 함수
* 서버에서 현재 알림 설정 상태를 조회하여 UI 업데이트
*/
function getNotificationStatus() {
$.ajax({
url: '/sopmal/bnft/getoxquizNotificationStatus',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ bnfInnb: screenInfo.basicInfo.bnfInnb }),
success: function(response) {
updateNotificationButton(response.notificationStatus);
},
error: function(xhr, status, error) {
console.error('알림 상태 조회 실패:', error);
}
});
}
/**
* 알림 버튼 UI 업데이트 함수
* 알림 상태에 따라 버튼 스타일과 텍스트 변경
* @param {boolean} isNotificationOn - 알림 활성화 여부
*/
function updateNotificationButton(isNotificationOn) {
const $notificationBtn = $('#notificationBtn');
const $notificationText = $notificationBtn.find('#notificationText');
// 아이콘을 항상 업데이트
const iconClass = isNotificationOn ? 'ico_alarm2' : 'ico_alarm2';
$notificationBtn.find('i').remove(); // 기존 아이콘 제거
$notificationBtn.prepend('<i class="' + iconClass + '"></i>'); // 새로운 아이콘 추가
// 버튼 텍스트와 스타일 업데이트
if (isNotificationOn) {
$notificationText.text('알림 신청 완료');
$notificationBtn
.data('status', 'on')
.removeClass('outline');
} else {
$notificationText.text('알림 신청');
$notificationBtn
.data('status', 'off')
.addClass('outline');
}
}
/**
* 퀴즈 상태 업데이트 함수
* 퀴즈 결과에 따라 내부 상태 데이터 업데이트
* @param {Object} quizResult - 퀴즈 결과 객체
*/
function updateQuizState(quizResult) {
screenInfo.quizInfoList[screenInfo.currentQuiz - 1].userAnswer = quizResult.userAnswer;
screenInfo.quizInfoList[screenInfo.currentQuiz - 1].isCorrect = quizResult.isCorrect ? 'O' : 'X';
updateQuizUI();
}
/**
* 퀴즈 UI 업데이트 함수
* 현재 퀴즈 상태에 맞게 화면 요소 업데이트
*/
function updateQuizUI() {
// 퀴즈 스탬프 업데이트
$('.quizStamp li').each(function(index) {
const quizInfo = screenInfo.quizInfoList[index];
$(this).removeClass('on fail location');
if (quizInfo.isCorrect === 'O') {
$(this).addClass('on');
} else if (quizInfo.isCorrect === 'X') {
$(this).addClass('on fail');
}
if (index + 1 === screenInfo.currentQuiz) {
$(this).addClass('location');
}
});
// 현재 퀴즈 내용 업데이트
const currentQuiz = screenInfo.quizInfoList[screenInfo.currentQuiz - 1];
$('.quizBox .txt').text(currentQuiz.quizCn);
// 선택 초기화
resetQuizSelection();
// OX 버튼 상태 업데이트 (추가)
const $oxButtons = $('.qzSelect a');
if (currentQuiz.userAnswer !== null) {
// 이미 완료된 퀴즈: OX 버튼 비활성화 및 스타일 변경
$oxButtons.addClass('disabled')
.css({
'opacity': '0.5',
'pointer-events': 'none',
'cursor': 'not-allowed'
});
// 사용자가 선택한 답변 표시
$oxButtons.filter(`[data-answer="${currentQuiz.userAnswer}"]`).addClass('on');
} else {
// 미완료 퀴즈: OX 버튼 활성화
$oxButtons.removeClass('disabled')
.css({
'opacity': '1',
'pointer-events': 'auto',
'cursor': 'pointer'
});
}
// 결과확인 버튼 상태 업데이트
const $resultButton = $('.bottomBtnControls button:last-child');
if (currentQuiz.userAnswer !== null) {
$resultButton.text('도전완료')
.prop('disabled', true)
.removeClass('theme')
.addClass('gray');
} else {
$resultButton.text('결과확인')
.prop('disabled', false)
.removeClass('gray')
.addClass('theme');
}
// 힌트 버튼 이벤트 리스너 재설정
$('#hintButton').off('click').on('click', function() {
const hintUrl = screenInfo.quizInfoList[screenInfo.currentQuiz - 1].hintUrl;
openExternalUrl(hintUrl);
});
}
/**
* 다음 퀴즈로 이동 함수
* 현재 퀴즈에서 다음 퀴즈로 이동
*/
function moveToNextQuiz() {
if (screenInfo.currentQuiz < screenInfo.quizCount) {
screenInfo.currentQuiz++;
updateQuizUI();
} else {
showCustomAlert('모든 퀴즈를 완료하셨습니다!');
}
}
/**
* 퀴즈 선택 초기화 함수
* OX 선택 상태를 초기화
*/
function resetQuizSelection() {
$('.qzSelect a').removeClass('on');
}
/**
* 힌트 모달 표시 함수
* 퀴즈 힌트를 모달 팝업으로 표시
* @param {string} hintUrl - 힌트 내용을 가져올 URL
*/
function showHintModal(hintUrl) {
window.location.href = hintUrl;
}
/**
* 힌트 페이지로 이동하는 함수
* 힌트 URL이 상대경로인지 절대경로인지 확인하고 적절히 처리
* @param {string} hintUrl - 힌트 URL
*/
function goToHintPage(hintUrl) {
// URL이 http:// 또는 https://로 시작하지 않으면 절대 경로로 변환
if (hintUrl && !hintUrl.match(/^https?:\/\//i)) {
// URL이 /로 시작하지 않으면 /를 추가
if (!hintUrl.startsWith('/')) {
hintUrl = '/' + hintUrl;
}
}
// 새 창으로 URL 열기 (웹뷰에서도 작동)
window.open(hintUrl, '_blank');
}
/**
* 외부 URL을 여는 함수
* a 태그를 동적으로 생성하여 새 창에서 URL 열기
* @param {string} url - 열고자 하는 URL
*/
function openExternalUrl(url) {
// URL이 http나 https로 시작하지 않으면 https 프로토콜 추가
if (url && !url.match(/^https?:\/\//i)) {
url = 'https://' + url;
}
console.log('Opening URL:', url);
// a 태그 동적 생성
const link = document.createElement('a');
link.href = url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
// DOM에 추가하고 클릭 이벤트 발생
document.body.appendChild(link);
link.click();
// DOM에서 제거
setTimeout(() => {
document.body.removeChild(link);
}, 100);
}
// 공개 API 반환
return {
init: init,
openExternalUrl: openExternalUrl
};
})();
// 문서 로드 완료 시 OXQuiz 모듈 초기화
$(document).ready(function() {
OXQuiz.init();
});

View File

@@ -0,0 +1,558 @@
/**
* PurstmpStamp.js
*
* 구매 스탬프 기능을 구현한 모듈
* 사용자가 스탬프를 적립하고 교환권을 발급받는 기능 제공
*
* 주요 기능:
* 1. 스탬프 카드 슬라이더 UI 구현
* 2. 스탬프 상태 확인 및 업데이트
* 3. 모달 팝업 관리
* 4. 스탬프 새로고침 기능
* 5. 이달의 선물 정보 표시
*/
// 전역 변수 제거 - window.screenInfo 사용
// let screenInfo = null; // 이 줄 삭제
// 문서가 로드되면 초기화 함수 실행
document.addEventListener('DOMContentLoaded', function() {
// window.screenInfo가 JSP에서 설정되었는지 확인
if (window.screenInfo) {
init(); // JSP에서 데이터가 이미 설정된 경우 바로 초기화
} else {
loadScreenData(); // Ajax로 데이터 로드
}
});
/**
* 화면 데이터 로드 함수
* Ajax로 서버에서 스탬프 정보를 가져옴
*/
function loadScreenData() {
// 로딩 표시
showLoading();
$.ajax({
url: '/sopmal/bnft/getStampData',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({}), // 빈 객체 전송 (인증은 세션으로 처리)
dataType: 'json',
success: function(response) {
try {
if (response.success) {
window.screenInfo = response.data;
// stampList 데이터 추출 (BO 서버 응답 구조에 맞춰)
if (window.screenInfo && window.screenInfo.stampList) {
window.screenInfo = window.screenInfo.stampList;
// couponList를 coupons로 매핑 (기존 코드와의 호환성 유지)
if (window.screenInfo.couponList) {
window.screenInfo.coupons = window.screenInfo.couponList;
}
}
init(); // 데이터 로드 완료 후 초기화
} else {
showError(response.message || '데이터를 불러오는데 실패했습니다.');
}
} catch (error) {
console.error('데이터 처리 중 오류:', error);
showError('데이터 처리 중 오류가 발생했습니다.');
}
},
error: function(xhr, status, error) {
console.error('Ajax 오류:', error);
showError('서버와의 통신 중 오류가 발생했습니다.');
},
complete: function() {
hideLoading();
}
});
}
/**
* 로딩 표시 함수
*/
function showLoading() {
$('.stampCardWrap').html('<div class="loading-spinner">로딩중...</div>');
}
/**
* 로딩 숨김 함수
*/
function hideLoading() {
$('.loading-spinner').remove();
}
/**
* 에러 표시 함수
*/
function showError(message) {
$('.stampCardWrap').html(`<div class="error-message">${message}</div>`);
}
/**
* 모듈 초기화 함수
* 필요한 모든 컴포넌트와 이벤트 리스너 초기화
*/
function init() {
if (!window.screenInfo) {
console.error('screenInfo가 없습니다.');
return;
}
initStampCardSlider(); // 스탬프 카드 슬라이더 초기화
initModal(); // 모달 팝업 초기화
updateTotalStamps(); // 전체 스탬프 수 업데이트
checkStampStatus(); // 스탬프 상태 확인
updateRewardInfo(); // 이달의 선물 정보 업데이트
// 새로고침 버튼 클릭 이벤트 처리
// init() 함수가 여러 번 호출될 수 있으므로, 이벤트 중복 등록을 방지하기 위해 .off()를 추가합니다.
$('.stampCardWrap').off('click.reply').on('click.reply', '.btn_reply', function(e) {
e.preventDefault();
e.stopPropagation(); // 슬라이드 이벤트와 충돌 방지
var couponIndex = $(this).closest('.stampCard').data('coupon-index');
refreshStamps(couponIndex);
});
}
/**
* 스탬프 상태 확인 함수
* 스탬프가 없거나 교환권이 모두 사용된 경우 UI 변경
*/
function checkStampStatus() {
if (!window.screenInfo) return;
// 데이터가 없는 경우 또는 교환권이 모두 사용된 경우
if (!window.screenInfo.coupons || window.screenInfo.coupons.length === 0) {
// 예시에 맞는 UI로 변경: 스탬프 카드 형태 유지하되 빈 상태 표시
$('.stampCardWrap').html(`
<div class="stampCard">
<div class="stampCardTop">
<p class="shopName_none">
<span>오프라인 매장 구매시<br>자동으로 스탬프가 적립됩니다.</span>
</p>
</div>
<ul class="stampArea">
<li><span>1</span></li>
<li><span>2</span></li>
<li><span>3</span></li>
<li><span>4</span></li>
<li><span>5</span></li>
<li><span>6</span></li>
<li><span>7</span></li>
<li><span>8</span></li>
<li><span>9</span></li>
<li><span>10</span></li>
</ul>
<div class="stampCardbm">
<p class="giftInfo">스탬프 <b>10개 적립</b>시 이달의 <b>선물 교환권</b>을 드려요.</p>
</div>
</div>
`);
}
}
/**
* 스탬프 카드 슬라이더 초기화 함수
* 스탬프 카드 UI 생성 및 슬라이드 기능 구현 (중복 생성 방지)
*/
function initStampCardSlider() {
if (!window.screenInfo || !window.screenInfo.coupons) {
console.warn('쿠폰 데이터가 없습니다.');
return;
}
const $stampCardWrap = $('.stampCardWrap');
const couponCount = window.screenInfo.coupons.length;
if (couponCount === 0) {
return;
}
// ✨ 가장 중요한 부분: 이미 카드가 생성되었다면, 함수를 즉시 종료하여 다시 그리지 않습니다.
if ($stampCardWrap.find('.stampCard').length > 0) {
console.log('카드가 이미 존재하므로 재생성하지 않습니다.');
return;
}
let currentCouponIndex = 0;
let startX, moveX;
/**
* 모든 쿠폰 카드를 미리 생성하는 함수
*/
function createAllCouponCards() {
// 이 함수는 최초 1회만 호출되므로, 이전처럼 .empty()를 호출할 필요가 없습니다.
window.screenInfo.coupons.forEach((coupon, index) => {
const $card = createCouponCard(coupon, index);
$stampCardWrap.append($card);
});
// 페이지네이션 생성
if (couponCount > 1) {
const $pagination = $('<div class="pagination"></div>');
for (let i = 0; i < couponCount; i++) {
const $button = $(`<button data-index="${i}"></button>`);
$pagination.append($button);
}
$stampCardWrap.append($pagination);
}
}
/**
* 쿠폰 카드 생성 함수
*/
function createCouponCard(coupon, index) {
// 교환권 완료 상태에 따른 메시지 결정
let giftMessage = '';
if (coupon.extiketStmpCnt === 10 && coupon.exchngAt === 'Y') {
giftMessage = '<span class="exchange-completed">✅ 교환권 발급 완료! 쿠폰함에서 확인하세요</span>';
} else if (coupon.extiketStmpCnt === 10) {
giftMessage = '<span class="exchange-ready">🎁 교환권 발급 준비완료!</span>';
} else {
giftMessage = `스탬프 <b>10개 적립</b>시 이달의 <b>선물 교환권</b>을 드려요.`;
}
return $(`
<div class="stampCard" data-coupon-index="${index}" style="display: none;">
<div class="stampCardTop">
<p class="shopName">
<span>${coupon.branchName || coupon.storeName || '매장명 미확인'}</span>
<a href="javascript:void(0);" class="btn_reply">새로고침</a>
</p>
<p class="cardCnt"><b>${coupon.extiketStmpCnt || 0}</b> / 10</p>
</div>
<ul class="stampArea">
${Array(10).fill().map((_, i) => `<li class="${i < (coupon.extiketStmpCnt || 0) ? 'on' : ''}"><span>${i + 1}</span></li>`).join('')}
</ul>
<div class="stampCardbm">
<p class="useDate">유효기간 ${formatDate(coupon.stmpValidDt)} 까지</p>
<p class="giftInfo">${giftMessage}</p>
</div>
</div>
`);
}
/**
* 스탬프 카드 표시 함수 (참조 코드 방식 적용)
*/
function showSlide(index) {
$('.stampCard').hide();
$('.stampCard').eq(index).fadeIn();
$('.pagination button').removeClass('active');
$('.pagination button').eq(index).addClass('active');
}
/**
* 페이지네이션 생성 함수
*/
function createPagination() {
if (couponCount <= 1) return;
const $pagination = $('.pagination');
if ($pagination.length === 0) {
const $newPagination = $('<div class="pagination"></div>');
for (let i = 0; i < couponCount; i++) {
$newPagination.append(`<button data-index="${i}"></button>`);
}
$stampCardWrap.append($newPagination);
}
}
/**
* 다음 슬라이드 표시
*/
function nextSlide() {
currentCouponIndex = (currentCouponIndex + 1) % couponCount;
showSlide(currentCouponIndex);
}
/**
* 이전 슬라이드 표시
*/
function previousSlide() {
currentCouponIndex = (currentCouponIndex - 1 + couponCount) % couponCount;
showSlide(currentCouponIndex);
}
// 페이지네이션 버튼 클릭 이벤트
$stampCardWrap.on('click', '.pagination button', function(e) {
e.stopPropagation();
currentCouponIndex = $(this).data('index');
showSlide(currentCouponIndex);
});
// 카드 클릭 이벤트 (참조 코드 방식)
$stampCardWrap.on('click', function(e) {
if (couponCount <= 1) return;
// 새로고침 버튼 클릭인 경우 슬라이드 이벤트 무시
if ($(e.target).hasClass('btn_reply') || $(e.target).closest('.btn_reply').length > 0) {
return;
}
const clickX = e.clientX;
const wrapWidth = $stampCardWrap.width();
const halfWidth = wrapWidth / 2;
if (clickX < halfWidth) {
previousSlide();
} else {
nextSlide();
}
});
// 터치 이벤트
$stampCardWrap.on('touchstart', function(e) {
startX = e.touches[0].clientX;
});
$stampCardWrap.on('touchmove', function(e) {
moveX = e.touches[0].clientX;
});
$stampCardWrap.on('touchend', function(e) {
if (couponCount <= 1) return;
if (startX - moveX > 50) {
nextSlide();
} else if (moveX - startX > 50) {
previousSlide();
}
});
// 슬라이더 초기화
if (couponCount > 0) {
createAllCouponCards();
createPagination();
showSlide(currentCouponIndex);
}
}
/**
* 날짜 포맷 변환 함수
* 날짜 문자열을 YYYY.MM.DD 형식으로 변환
*
* @param {string} dateString - 변환할 날짜 문자열
* @returns {string} 포맷된 날짜 문자열
*/
function formatDate(dateString) {
const date = new Date(dateString);
return `${date.getFullYear()}.${(date.getMonth() + 1).toString().padStart(2, '0')}.${date.getDate().toString().padStart(2, '0')}`;
}
/**
* 모달 초기화 함수
* MicroModal 라이브러리를 사용한 모달 팝업 초기화
*/
function initModal() {
MicroModal.init({
disableScroll: true,
awaitCloseAnimation: true,
});
// 모달 트리거 버튼 클릭 이벤트
$('.fullscreen-trigger').on('click', function(e) {
e.preventDefault();
var modalId = $(this).attr('data-micromodal-trigger');
// 전역 openModal 함수 사용
if (typeof window.openModal === 'function') {
window.openModal(modalId);
} else {
MicroModal.show(modalId);
}
});
// 모달 닫기 버튼 클릭 이벤트
$('#modal-close-btn').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('모달 닫기 버튼 클릭됨'); // 디버깅용
window.location.href = '/sopmal/bnft/bnftMain';
});
// 브라우저 뒤로가기 버튼 처리
window.addEventListener('popstate', function(event) {
var modalId = window.location.hash.replace('#', '');
if (modalId) {
// 전역 closeModal 함수 사용
if (typeof window.closeModal === 'function') {
window.closeModal(modalId);
} else {
MicroModal.close(modalId);
}
} else {
$('.microModal').each(function() {
MicroModal.close($(this).attr('id'));
});
}
document.body.style.overflow = '';
});
// URL 해시가 있는 경우 해당 모달 열기
if (window.location.hash) {
var modalId = window.location.hash.replace('#', '');
// 전역 openModal 함수 사용
if (typeof window.openModal === 'function') {
window.openModal(modalId);
} else {
MicroModal.show(modalId);
}
}
}
/**
* 토스트 메시지 표시 함수
*
* @param {string} message - 표시할 메시지
*/
function showToast(message) {
const $toastPop = $('#modal_exchange_ticket_toast');
$toastPop.text(message);
$toastPop.addClass('show');
setTimeout(() => {
$toastPop.removeClass('show');
}, 2000);
}
/**
* 스탬프 새로고침 함수 (토스트 제거)
*/
function refreshStamps(couponIndex) {
if (!window.screenInfo || !window.screenInfo.coupons || !window.screenInfo.coupons[couponIndex]) {
showToast('쿠폰 정보를 찾을 수 없습니다.');
return;
}
const coupon = window.screenInfo.coupons[couponIndex];
$.ajax({
url: '/sopmal/bnft/refreshStamps',
method: 'POST',
data: JSON.stringify({
bnfInnb: window.screenInfo.bnfInnb || '',
strInnb: coupon.strInnb || '',
bnfExtiketInnb: coupon.bnfExtiketInnb || '',
mbrInnb: window.screenInfo.mbrInnb || '',
mbrDvCd: window.screenInfo.mbrDvCd || '',
sopmalCd: window.screenInfo.sopmalCd || ''
}),
contentType: 'application/json',
success: function(response) {
try {
if (response && response.resBody && response.resBody.result) {
const responseData = JSON.parse(response.resBody.result);
if (responseData.success) {
// ⚠️ 매장명이 null인 경우 기존 매장명 유지
if (!responseData.cardInfo.branchName) {
responseData.cardInfo.branchName = coupon.storeName || coupon.branchName || '매장명 미확인';
}
updateStampCard(couponIndex, responseData);
// ❌ 토스트 제거: showToast('스탬프 정보가 새로고침되었습니다.');
} else {
showToast(responseData.message || '스탬프 정보를 새로고침하는데 실패했습니다.');
}
} else {
showToast('응답 형식이 올바르지 않습니다.');
}
} catch (error) {
console.error('응답 처리 중 오류:', error);
showToast('응답을 처리하는 동안 오류가 발생했습니다.');
}
},
error: function(xhr, status, error) {
console.error('새로고침 Ajax 오류:', error);
showToast('스탬프 정보를 새로고침하는데 실패했습니다.');
}
});
}
/**
* 스탬프 카드 업데이트 함수 - 카드 틀은 유지하고 내용만 업데이트
*/
function updateStampCard(couponIndex, data) {
// 데이터 업데이트
Object.assign(window.screenInfo.coupons[couponIndex], data.cardInfo);
const coupon = window.screenInfo.coupons[couponIndex];
const $card = $(`.stampCard[data-coupon-index="${couponIndex}"]`);
// 1. 스탬프 개수 및 시각적 표시 업데이트
const newStampCount = coupon.extiketStmpCnt || 0;
$card.find('.cardCnt b').text(newStampCount);
$card.find('.stampArea li').each((index, el) => {
$(el).toggleClass('on', index < newStampCount);
});
// 2. 지점명 업데이트
const branchName = coupon.branchName || coupon.storeName || '매장명 미확인';
$card.find('.shopName span').text(branchName);
// 3. 유효기간 업데이트
if (coupon.stmpValidDt) {
$card.find('.useDate').text(`유효기간 ${formatDate(coupon.stmpValidDt)} 까지`);
}
// 4. 교환권 상태 메시지 업데이트
let giftMessage = '';
if (coupon.extiketStmpCnt === 10 && coupon.exchngAt === 'Y') {
giftMessage = '<span class="exchange-completed">✅ 교환권 발급 완료! 쿠폰함에서 확인하세요</span>';
} else if (coupon.extiketStmpCnt === 10) {
giftMessage = '<span class="exchange-ready">🎁 교환권 발급 준비완료!</span>';
} else {
giftMessage = `스탬프 <b>10개 적립</b>시 이달의 <b>선물 교환권</b>을 드려요.`;
}
$card.find('.giftInfo').html(giftMessage);
// 5. 부드러운 업데이트 효과 (CSS 애니메이션 필요)
$card.find('.stampArea').addClass('updated');
setTimeout(() => {
$card.find('.stampArea').removeClass('updated');
}, 300);
updateTotalStamps();
}
/**
* 전체 스탬프 수 업데이트 함수
* 화면에 표시된 전체 쿠폰 수 업데이트
*/
function updateTotalStamps() {
const totalCoupons = window.screenInfo.coupons.length;
$('.card_title b').text(totalCoupons);
window.screenInfo.totalStamps = totalCoupons;
}
/**
* 이달의 선물 정보 업데이트 함수
* 서버에서 받은 purstmpScreenInfo 데이터로 UI 업데이트
*/
function updateRewardInfo() {
if (!window.screenInfo || !window.screenInfo.purstmpScreenInfo || !window.screenInfo.purstmpScreenInfo[0]) {
console.warn('이달의 선물 정보가 없습니다.');
return;
}
const rewardInfo = window.screenInfo.purstmpScreenInfo[0];
// 이달의 선물 이름 설정 (cnSj)
if (rewardInfo.cnSj) {
$('#rewardName').text(rewardInfo.cnSj);
}
// 이달의 선물 이미지 설정 (imgFilCours)
if (rewardInfo.imgFilCours) {
const imageSrc = window.cdnUrl + rewardInfo.imgFilCours;
$('#rewardImage').attr('src', imageSrc);
}
// 이벤트 설명 설정 (wordsCn) - HTML 형태로 삽입
if (rewardInfo.wordsCn) {
$('#eventDescription').html(rewardInfo.wordsCn);
}
}

View File

@@ -0,0 +1,184 @@
/**
* QREvntMain.js - QR 이벤트 모듈 (기존 모달 시스템 사용)
*/
const QREvent = (function() {
let participationId = null;
/**
* 초기화 함수
*/
function init() {
console.log('QREvent init');
// 서버에서 넘어온 참여 결과에 따라 모달 표시
showResultModal();
}
/**
* 서버 결과에 따른 모달 표시
*/
function showResultModal() {
// participationResult는 JSP에서 생성된 전역 변수
if (typeof participationResult === 'undefined') {
console.log('참여 결과 데이터가 없습니다.');
return;
}
console.log('참여 결과:', participationResult);
// errorCode 확인해서 적절한 모달 표시
if (typeof qrEventErrorCode !== 'undefined' && qrEventErrorCode === 'NO_EVENT') {
// 진행중인 이벤트 없음
showNoEventModal();
} else if (!participationResult.success) {
// 참여 실패 (이미 참여했거나, 참여 기간이 아니거나 등)
showMessageModal(participationResult.message);
} else if (participationResult.isWinner) {
// 당첨
participationId = participationResult.participationId;
showWinningModal();
} else {
// 미당첨
showNotWinningModal();
}
}
/**
* 쿠폰 상세 보기 (유일한 Ajax 호출)
*/
function viewCoupon() {
if (!participationId) {
showMessageModal('참여 정보를 찾을 수 없습니다.');
return;
}
$.ajax({
url: '/sopmal/bnft/getWinningDetail',
method: 'POST',
contentType: 'application/json; charset=UTF-8',
data: JSON.stringify({
bnfPrtInnb: participationId
}),
success: function(response) {
console.log('쿠폰 상세 정보:', response);
if (response) {
showCouponModal(response);
} else {
showMessageModal('쿠폰 정보를 조회하지 못했습니다.');
}
},
error: function(xhr, status, error) {
console.error('쿠폰 정보 조회 실패:', error);
showMessageModal('쿠폰 정보를 불러오는 중 오류가 발생했습니다.');
}
});
}
// --- 모달 표시 헬퍼 함수 ---
/**
* 범용 메시지 모달 표시
* @param {string} message - 표시할 메시지
*/
function showMessageModal(message) {
$('#modal-message #modalMessage').text(message || '오류가 발생했습니다.');
openModal('modal-message'); // 기존 시스템 함수 사용
}
/**
* 당첨 모달 표시
*/
function showWinningModal() {
// storeInfo는 JSP에서 생성된 전역 변수
if (typeof storeInfo !== 'undefined' && storeInfo.strNm) {
$('#modal-winning #winningStoreName').text(storeInfo.strNm);
}
openModal('modal-winning'); // 기존 시스템 함수 사용
}
/**
* 미당첨 모달 표시
*/
function showNotWinningModal() {
// 필요 시 서버 메시지로 교체 가능
$('#modal-notwinning #notWinningMessage').text('이번에는 당첨되지 않았네요.');
openModal('modal-notwinning'); // 기존 시스템 함수 사용
}
/**
* 쿠폰 상세 모달 표시
* @param {object} data - 쿠폰 상세 정보
*/
function showCouponModal(data) {
// 쿠폰 번호, 매장명 설정
$('#modal-coupon #couponNo').text(data.couponNb || '');
$('#modal-coupon #couponStoreName').text(data.strNm || '');
// 사용 조건 (하드코딩)
$('#modal-coupon #couponTerms').text('10,000원 이상 구매시 사용 가능');
// 유효 기간 설정
if (data.expiryDateTime) {
$('#modal-coupon #couponValidity').text(`${data.expiryDateTime} 까지`);
} else {
$('#modal-coupon #couponValidity').text('유효기간 없음');
}
// 할인 정보 또는 상품 정보 처리
const contentEl = $('#modal-coupon #couponContent');
const discountTypeEl = $('#modal-coupon #couponDiscountType');
if (data.dscntAmt || data.dscntRt) {
// 금액 또는 비율 할인
const discountInfo = data.dscntAmt ?
`${data.dscntAmt.toLocaleString()}` : // 1,000
`${data.dscntRt}`; // 10
const discountUnit = data.dscntAmt ? '원' : '%';
contentEl.html(`${discountInfo}<span class="unit">${discountUnit}</span>`);
discountTypeEl.text('할인쿠폰').show();
} else {
// 상품 증정
contentEl.text(data.couponName || '증정품');
contentEl.removeClass('price'); // 금액 표시 클래스 제거
discountTypeEl.hide(); // '할인쿠폰' 텍스트 숨김
}
// 당첨 모달 닫고 쿠폰 모달 표시
closeModal('modal-winning'); // 기존 시스템 함수 사용
openModal('modal-coupon'); // 기존 시스템 함수 사용
}
/**
* 진행중인 이벤트 없음 모달 표시
*/
function showNoEventModal() {
// 전역 변수에서 메시지 가져오기
if (typeof qrEventMessage !== 'undefined') {
$('#modal-no-event #noEventMessage').text(qrEventMessage);
}
if (typeof qrEventDetailMessage !== 'undefined') {
$('#modal-no-event #noEventDetailMessage').text(qrEventDetailMessage);
}
openModal('modal-no-event'); // 기존 시스템 함수 사용
}
// 공개 메서드
return {
init: init,
viewCoupon: viewCoupon
};
})();
// DOM 로드 시 초기화
$(document).ready(function() {
QREvent.init();
// 모든 모달의 닫기 버튼 클릭 시 /sopmal/bnft/bnftMain 으로 이동
$('.microModal__close').on('click', function() {
window.location.href = '/sopmal/bnft/bnftMain';
});
});

View File

@@ -0,0 +1,437 @@
/**
* Rougame.js
*
* 룰렛 게임 기능을 구현한 모듈
* 사용자가 룰렛을 돌려 다양한 보상을 획득할 수 있는 기능 제공
*
* 주요 기능:
* 1. 캔버스 기반 룰렛 UI 구현
* 2. 룰렛 회전 애니메이션 및 결과 처리
* 3. 서버와의 통신을 통한 결과 확인
* 4. 남은 게임 횟수 관리
*/
document.addEventListener('DOMContentLoaded', function() {
// 룰렛 세그먼트 데이터 가져오기 (각 칸의 정보)
const segmentElements = document.querySelectorAll('.segment-data');
const segments = Array.from(segmentElements).map(el => ({
rank: parseInt(el.dataset.rank), // 순위 정보
code: el.dataset.code, // 코드 정보
label: el.dataset.label // 표시될 텍스트
}));
// 화면 정보 및 설정값 가져오기
const screenInfo = {
scrRwdLinkUrl: document.getElementById('scrRwdLinkUrl').value, // 보상 링크 URL
bnfInnb: document.getElementById('bnfInnb').value // 혜택 고유 번호
};
/**
* RouletteGame 객체
* 룰렛 게임의 모든 기능을 구현한 객체
*/
const RouletteGame = {
canvas: document.getElementById('rouletteCanvas'), // 룰렛을 그릴 캔버스 요소
ctx: null, // 캔버스 컨텍스트
colors: ['#FDBE01', '#ffffff', '#FDBE01', '#ffffff', '#FDBE01', '#ffffff', '#FDBE01', '#ffffff'], // 룰렛 색상 배열
angle: 0, // 현재 룰렛 각도
segmentAngle: 2 * Math.PI / segments.length, // 각 세그먼트의 각도
diameterPercentage: 0.76, // 캔버스 대비 룰렛 지름 비율
canvasCenterX: 0, // 캔버스 중심 X 좌표
canvasCenterY: 0, // 캔버스 중심 Y 좌표
radius: 0, // 룰렛 반지름
isSpinning: false, // 회전 중 여부
rewardResult: null, // 서버에서 받은 보상 결과 저장
/**
* 초기화 함수
* 캔버스 설정 및 이벤트 리스너 등록
*/
init() {
this.ctx = this.canvas.getContext('2d');
this.canvasCenterX = this.canvas.width / 2;
this.canvasCenterY = this.canvas.height / 2;
this.radius = (Math.min(this.canvas.width, this.canvas.height) * this.diameterPercentage) / 2;
this.addEventListeners(); // 이벤트 리스너 등록
this.drawRoulette(); // 초기 룰렛 그리기
this.checkRewardAvailability(); // 보상 가능 여부 체크 추가
},
/**
* 이벤트 리스너 등록 함수
* 버튼 클릭 이벤트 처리
*/
addEventListeners() {
// 회전 버튼 이벤트 등록
const spinButton = document.getElementById('spinButton');
spinButton.addEventListener('click', this.spinRoulette.bind(this));
// 결과 팝업 닫기 버튼 이벤트 등록
const closeButton = document.querySelector('.microModal__btn');
closeButton.addEventListener('click', () => MicroModal.close('resultPopup'));
// 품절 모달의 메인 이동 버튼 이벤트 설정 (행운복권과 동일한 방식)
$('#btn-go-main').on('click', function() {
window.location.href = '/sopmal/bnft/bnftMain';
});
},
/**
* 룰렛 그리기 함수
* 캔버스에 룰렛과 세그먼트를 그림
*/
drawRoulette() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 캔버스 초기화
let angle = this.angle;
// 각 세그먼트 그리기
segments.forEach((seg, index) => {
this.drawSegment(seg, index, angle);
angle += this.segmentAngle;
});
// 화살표 그리기
this.drawArrow();
},
/**
* 세그먼트 그리기 함수
* @param {Object} seg - 세그먼트 정보
* @param {number} index - 세그먼트 인덱스
* @param {number} angle - 시작 각도
*/
drawSegment(seg, index, angle) {
// 세그먼트 배경 그리기
this.ctx.beginPath();
this.ctx.fillStyle = this.colors[index % this.colors.length];
this.ctx.moveTo(this.canvasCenterX, this.canvasCenterY);
this.ctx.arc(this.canvasCenterX, this.canvasCenterY, this.radius, angle, angle + this.segmentAngle);
this.ctx.lineTo(this.canvasCenterX, this.canvasCenterY);
this.ctx.fill();
// 세그먼트 텍스트 그리기
this.ctx.save();
this.ctx.translate(this.canvasCenterX, this.canvasCenterY);
this.ctx.rotate(angle + this.segmentAngle / 2);
this.ctx.textAlign = 'right';
this.ctx.fillStyle = '#000000';
this.ctx.font = 'bold 20px "GmarketSans"';
this.ctx.fillText(seg.label, this.radius - 20, 10);
this.ctx.restore();
},
/**
* 화살표 그리기 함수
* 룰렛 결과를 가리키는 화살표 표시
*/
drawArrow() {
this.ctx.fillStyle = 'transparent';
this.ctx.beginPath();
this.ctx.moveTo(this.canvasCenterX, this.canvasCenterY - this.radius - 15);
this.ctx.lineTo(this.canvasCenterX - 10, this.canvasCenterY - this.radius + 5);
this.ctx.lineTo(this.canvasCenterX + 10, this.canvasCenterY - this.radius + 5);
this.ctx.closePath();
this.ctx.fill();
},
/**
* 화면 로딩시 보상 가능 여부 체크
*/
checkRewardAvailability() {
$.ajax({
url: '/sopmal/bnft/checkRewardAvailability',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
bnfInnb: screenInfo.bnfInnb
}),
success: (response) => {
console.log('checkRewardAvailability response:', response);
// 응답 결과 파싱
let result = response.resBody.result;
// 보상이 없는 경우 처리
if (result === "N" || result === false) {
// 게임 버튼 비활성화
this.disableGameButton();
// 품절 안내 모달 표시
this.showNoRewardModal();
return;
}
// 보상이 있는 경우 정상 진행
console.log('보상 가능 상태:', result);
},
error: (xhr, status, error) => {
console.error('보상 가능 여부 확인 실패:', error);
// 에러 발생 시에도 게임 버튼 비활성화
this.disableGameButton();
this.showErrorModal();
}
});
},
/**
* 게임 버튼 비활성화
*/
disableGameButton() {
const spinButton = document.getElementById('spinButton');
if (spinButton) {
spinButton.disabled = true;
spinButton.style.opacity = '0.5';
spinButton.style.cursor = 'not-allowed';
spinButton.textContent = '품절';
}
},
/**
* 보상 품절 안내 모달 표시
*/
showNoRewardModal() {
// 품절 전용 모달 표시
MicroModal.show('modal-no-reward');
},
/**
* 에러 안내 모달 표시
*/
showErrorModal() {
if (typeof showCustomAlert === 'function') {
showCustomAlert('보상 상태 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
} else {
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent =
'보상 상태 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
}
},
/**
* 룰렛 회전 시작 함수 (수정된 버전)
*/
spinRoulette() {
// 이미 회전 중이면 중복 실행 방지
if (this.isSpinning) return;
// 버튼이 비활성화되어 있으면 실행 방지
const spinButton = document.getElementById('spinButton');
if (spinButton && spinButton.disabled) {
this.showNoRewardModal();
return;
}
// 남은 게임 횟수 확인
const remainingChancesElement = document.querySelector('.rouletteCnt b');
const remainingChances = parseInt(remainingChancesElement.textContent, 10);
if (isNaN(remainingChances) || remainingChances <= 0) {
// alert 대신 커스텀 모달 사용
if (typeof showCustomAlert === 'function') {
showCustomAlert('남은 횟수가 없습니다.');
} else {
// showCustomAlert 함수가 없는 경우 fallback
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent = '남은 횟수가 없습니다.';
}
return;
}
this.isSpinning = true;
// 서버에 게임 결과 요청
$.ajax({
url: '/sopmal/bnft/playRougame',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
"bnfInnb": screenInfo.bnfInnb
}),
success: (response) => {
const result = JSON.parse(response.resBody.result);
// 에러 응답 처리
if (result.success === false && result.error) {
this.isSpinning = false;
console.log('Server error:', result.error);
// 모든 에러를 showCustomAlert로 처리
if (typeof showCustomAlert === 'function') {
showCustomAlert(result.error);
} else {
// showCustomAlert 함수가 없는 경우 fallback
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent = result.error;
}
return;
}
// 정상 응답 처리
this.rewardResult = result;
const targetRank = result.rank;
const targetIndex = segments.findIndex(segment => segment.rank === targetRank);
console.log('Reward Result:', result);
console.log('Target Rank:', targetRank);
console.log('Target Index:', targetIndex);
console.log('Segments:', segments);
if (targetIndex !== -1) {
const adjustedIndex = targetIndex; // 12시 방향을 기준으로 인덱스 조정
this.startSpin(adjustedIndex); // 회전 애니메이션 시작
this.updateRemainingChances(); // 남은 횟수 업데이트
}
},
error: (xhr, status, error) => {
console.error("AJAX request failed:", error);
this.isSpinning = false;
// alert 대신 커스텀 모달 사용
if (typeof showCustomAlert === 'function') {
showCustomAlert('룰렛 회전 중 오류가 발생했습니다. 다시 시도해주세요.');
} else {
// showCustomAlert 함수가 없는 경우 fallback
MicroModal.show('modal-alert');
document.querySelector('#modal-alert .microModal__content p').textContent = '룰렛 회전 중 오류가 발생했습니다. 다시 시도해주세요.';
}
}
});
},
/**
* 회전 애니메이션 시작 함수
* @param {number} targetIndex - 목표 세그먼트 인덱스
*/
startSpin(targetIndex) {
const segmentsCount = segments.length;
const segmentAngleDeg = 360 / segmentsCount;
// 수정된 목표 각도 계산: 12시 방향이 0도가 되도록 조정
const targetAngleDeg = -(targetIndex * segmentAngleDeg) + 255; // 270도는 12시 방향
const initialAngle = this.angle % (2 * Math.PI);
const finalAngle = (targetAngleDeg * Math.PI / 180) + (2 * Math.PI * 5); // 5바퀴 추가 회전
console.log('Segments Count:', segmentsCount);
console.log('Segment Angle (deg):', segmentAngleDeg);
console.log('Target Angle (deg):', targetAngleDeg);
console.log('Initial Angle (rad):', initialAngle);
console.log('Final Angle (rad):', finalAngle);
// 회전 시간 및 속도 설정
const spinTimeTotal = Math.random() * 1000 + 4000; // 4~5초 사이 랜덤 시간
let spinTime = 0;
// 회전 애니메이션 함수
const rotateWheel = () => {
spinTime += 30; // 30ms마다 업데이트
if (spinTime >= spinTimeTotal) {
// 회전 완료
this.angle = targetAngleDeg * Math.PI / 180;
console.log('Final Position Angle (rad):', this.angle);
this.drawRoulette();
this.stopRotateWheel();
return;
}
// easeOut 함수로 감속 효과 적용
const spinAngle = this.easeOut(spinTime, 0, finalAngle - initialAngle, spinTimeTotal);
this.angle = initialAngle + spinAngle;
this.drawRoulette();
requestAnimationFrame(rotateWheel); // 다음 프레임 요청
};
rotateWheel(); // 애니메이션 시작
},
/**
* 회전 정지 및 결과 처리 함수
* 최종 위치에서 결과를 계산하고 팝업 표시
*/
stopRotateWheel() {
let degrees = (this.angle * 180 / Math.PI) % 360;
console.log('Current Angle in Degrees:', degrees);
if (degrees < 0) degrees += 360;
console.log('Normalized Angle in Degrees:', degrees);
const arcd = 360 / segments.length;
console.log('Arc Degree per Segment:', arcd);
// 수정된 인덱스 계산: 12시 방향을 기준으로 계산
const index = (Math.floor(((360 - degrees + 255) % 360) / arcd)) % segments.length;
console.log('Calculated Segment Index:', index);
// 보상 유형에 따라 결과 텍스트와 안내 문구 설정
let resultText = '';
let guideText = '';
if (this.rewardResult) {
if (this.rewardResult.rewardType === 'MILEAGE') {
// 마일리지인 경우
resultText = `마일리지 ${this.rewardResult.rewardAmount}원 획득하셨습니다.`;
guideText = 'MY혜택 > 마일리지에서 확인하세요.';
} else if (this.rewardResult.rewardType === 'COUPON') {
// 쿠폰인 경우
resultText = this.rewardResult.itemName;
guideText = 'MY혜택 > 쿠폰에서 확인하세요.';
} else {
// 기타 경우
resultText = segments[index] ? segments[index].label : '보상';
guideText = 'MY혜택에서 확인하세요.';
}
} else {
// rewardResult가 없는 경우 기본값
resultText = segments[index] ? segments[index].label : '보상';
guideText = 'MY혜택에서 확인하세요.';
}
console.log('Final Result Text:', resultText);
console.log('Guide Text:', guideText);
// 결과 텍스트와 안내 문구 업데이트
document.getElementById('resultText').textContent = resultText;
// 안내 문구도 동적으로 변경
const guideElement = document.querySelector('#resultPopup .microModal__content p:last-child');
if (guideElement) {
guideElement.textContent = guideText;
}
// 결과 팝업 표시
MicroModal.show('resultPopup');
this.isSpinning = false; // 회전 상태 초기화
this.rewardResult = null; // 결과 초기화
},
/**
* easeOut 애니메이션 함수
* 점점 느려지는 효과를 위한 계산
* @param {number} t - 현재 시간
* @param {number} b - 시작 값
* @param {number} c - 변화량
* @param {number} d - 지속 시간
* @returns {number} - 계산된 값
*/
easeOut(t, b, c, d) {
const ts = (t /= d) * t;
const tc = ts * t;
const result = b + c * (tc + -3 * ts + 3 * t);
console.log('EaseOut Calculation:', { t, b, c, d, result });
return result;
},
/**
* 남은 게임 횟수 업데이트 함수
* 게임 후 남은 횟수를 감소시킴
*/
updateRemainingChances() {
const remainingChancesElement = document.querySelector('.rouletteCnt b');
if (remainingChancesElement) {
let remainingChances = parseInt(remainingChancesElement.textContent, 10);
console.log('Current Remaining Chances:', remainingChances);
if (!isNaN(remainingChances) && remainingChances > 0) {
remainingChancesElement.textContent = remainingChances - 1;
console.log('Updated Remaining Chances:', remainingChances - 1);
}
}
}
};
// 룰렛 게임 초기화
RouletteGame.init();
});

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,216 @@
let bnfInnb = "";
// StepcntSettings.js - 만보기 설정 관련 스크립트
$(document).ready(function() {
// bnfInnb 값 가져오기
bnfInnb = $("#bnfInnb").val();
console.log("bnfInnb ::: ", bnfInnb);
// 1. 초기 로딩 시 만보기 동의 여부 확인
checkStepcntAgreement();
// 2. 이벤트 설명 로딩
loadEventDescription();
// 3. 토글 스위치 이벤트 리스너 등록
$("#stepcntUseYn").on("change", function() {
updateStepcntAgreement($(this).is(":checked") ? "Y" : "N");
});
});
/**
* 현재 만보기 동의 상태 확인
*/
function checkStepcntAgreement() {
$.ajax({
url: "/sopmal/bnft/checkCurrentStepcntAgreement",
type: "POST",
contentType: "application/json",
data: JSON.stringify({
bnfInnb: bnfInnb
}),
success: function(response) {
try {
// 응답 파싱
const resultData = response.resBody ? JSON.parse(response.resBody.result) : response;
// 응답이 정상이고 데이터가 존재하는 경우
if (response.statusCode === 0 && resultData) {
// 동의 상태에 따라 토글 스위치 설정
const useAt = resultData.useAt === "Y";
$("#stepcntUseYn").prop("checked", useAt);
} else {
// 기본값으로 체크 해제
$("#stepcntUseYn").prop("checked", false);
console.error("만보기 동의 상태 조회 실패:", response);
showToast("만보기 동의 상태 조회 중 오류가 발생했습니다.");
}
} catch (e) {
console.error("응답 파싱 오류:", e);
$("#stepcntUseYn").prop("checked", false);
showToast("만보기 동의 상태 조회 중 오류가 발생했습니다.");
}
},
error: function(xhr, status, error) {
console.error("AJAX 오류:", error);
showToast("만보기 동의 상태 조회 중 오류가 발생했습니다.");
}
});
}
/**
* 만보기 동의 상태 업데이트
* @param {string} agreeYn - 동의 여부 (Y/N)
*/
function updateStepcntAgreement(agreeYn) {
$.ajax({
url: "/sopmal/bnft/updateStepcntAgreement",
type: "POST",
contentType: "application/json",
data: JSON.stringify({
agreeYn: agreeYn,
bnfInnb: bnfInnb
}),
success: function(response) {
try {
// 응답 파싱
const resultData = response.resBody ? JSON.parse(response.resBody.result) : response;
// 응답이 정상인 경우
if (response.statusCode === 0) {
// 성공 메시지 표시
if (agreeYn === "Y") {
showToast("만보기 기능이 활성화되었습니다.");
} else {
showToast("만보기 기능이 비활성화되었습니다.");
}
} else {
// 실패 시 토글 상태 되돌리기
$("#stepcntUseYn").prop("checked", agreeYn !== "Y");
console.error("만보기 동의 상태 업데이트 실패:", response);
showToast("만보기 설정 변경 중 오류가 발생했습니다.");
}
} catch (e) {
// 오류 발생 시 토글 상태 되돌리기
$("#stepcntUseYn").prop("checked", agreeYn !== "Y");
console.error("응답 파싱 오류:", e);
showToast("만보기 설정 변경 중 오류가 발생했습니다.");
}
},
error: function(xhr, status, error) {
// 오류 발생 시 토글 상태 되돌리기
$("#stepcntUseYn").prop("checked", agreeYn !== "Y");
console.error("AJAX 오류:", error);
showToast("만보기 설정 변경 중 오류가 발생했습니다.");
}
});
}
/**
* 토스트 메시지 표시
* @param {string} message - 표시할 메시지
*/
function showToast(message) {
// 토스트 요소가 없으면 동적으로 생성
if ($("#toastPop").length === 0) {
$("body").append('<div id="toastPop" class="toastPop"><p></p></div>');
}
$('#toastPop p').text(message);
$('#toastPop').addClass('show');
// 2초 후 토스트 메시지 숨김
setTimeout(function() {
$('#toastPop').removeClass('show');
}, 2000);
}
/**
* 이벤트 설명만 별도로 로딩하는 함수
*/
function loadEventDescription() {
console.log("loadEventDescription 함수 시작");
$.ajax({
url: "/sopmal/bnft/selectStepcntGuide",
type: "POST",
contentType: "application/json",
data: JSON.stringify({
bnfInnb: bnfInnb
}),
success: function(response) {
console.log("loadEventDescription 응답:", response);
try {
// 응답이 정상이고 resBody와 result가 존재하는 경우
if (response.statusCode === 0 && response.resBody && response.resBody.result) {
console.log("응답 상태 정상, result 파싱 시작");
// result를 JSON으로 파싱
const resultData = JSON.parse(response.resBody.result);
console.log("파싱된 resultData:", resultData);
if (resultData && resultData.bnfWordsCn) {
console.log("bnfWordsCn 데이터 존재:", resultData.bnfWordsCn);
// comment-box 요소 확인
const commentBox = $(".comment-box");
console.log("comment-box 요소 찾기:", commentBox.length);
if (commentBox.length > 0) {
// comment-box에 이벤트 설명 표시
const htmlContent = `
<p style="font-size:14px; font-weight:bold; margin-bottom:5px;">이벤트 설명</p>
<p style="color:rgba(0,0,0,0.6); font-size:14px;">${resultData.bnfWordsCn}</p>
`;
console.log("설정할 HTML 내용:", htmlContent);
commentBox.html(htmlContent);
console.log("comment-box 내용 설정 완료");
} else {
console.error("comment-box 요소를 찾을 수 없습니다");
}
} else {
console.log("bnfWordsCn 데이터가 없어서 기본 설명 표시");
showDefaultDescription();
}
} else {
console.log("응답 상태 오류 또는 데이터 없음, 기본 설명 표시");
showDefaultDescription();
}
} catch (e) {
console.error("이벤트 설명 파싱 오류:", e);
showDefaultDescription();
}
},
error: function(xhr, status, error) {
console.error("이벤트 설명 조회 오류:", error);
showDefaultDescription();
}
});
}
/**
* 기본 이벤트 설명 표시
*/
function showDefaultDescription() {
$(".comment-box").html(`
<p style="font-size:14px; font-weight:bold; margin-bottom:5px;">이벤트 설명</p>
<div style="font-size:13px; line-height:1.4; color:#666;">
만보기 이벤트에 참여하여 다양한 혜택을 받아보세요!<br>
매일 만보를 달성하시면 마일리지와 깜짝선물을 받을 수 있습니다.
</div>
`);
}
/**
* 세션에서 bnfInnb 값을 가져오는 함수 (필요시)
*/
function getBnfInnb() {
// 세션 저장소나 다른 방법으로 bnfInnb 값을 가져오는 로직
// 현재는 서버에서 자동으로 세션 값을 사용하므로 빈 값으로 처리
return null;
}

View File

@@ -0,0 +1,185 @@
const openSheetButtons = document.querySelectorAll(".open-sheet");
const sheets = document.querySelectorAll(".sheet");
let sheetHeight;
let currentSheet = null;
const sheetsMap = new Map();
let vh = window.innerHeight / 100;
const setSheetHeight = (value) => {
if (!currentSheet) return;
const sheetContents = currentSheet.querySelector(".contents");
sheetHeight = Math.max(0, Math.min(90, value));
sheetContents.style.height = `${sheetHeight * vh}px`;
};
const setIsSheetShown = (isShown) => {
if (!currentSheet) return;
currentSheet.setAttribute("aria-hidden", String(!isShown));
document.body.style.overflow = isShown ? "hidden" : "";
document.querySelector(".screen-wrap").classList.toggle("is-fixed", isShown);
if (!isShown) {
currentSheet = null;
}
};
const throttle = (func, limit) => {
let lastFunc;
let lastRan;
return function (...args) {
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function () {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
};
const initializeSheet = (sheet, openButtons) => {
const sheetContents = sheet.querySelector(".contents");
const draggableArea = sheet.querySelector(".draggable-area");
// 버튼 클릭 시 시트 열기
openButtons.forEach((openButton) => {
openButton.addEventListener("click", () => {
currentSheet = sheet;
setSheetHeight(Math.min(50, (720 / window.innerHeight) * 100));
setIsSheetShown(true);
});
});
// 닫기 버튼 클릭 시 시트 닫기
sheet.querySelector(".close-sheet").addEventListener("click", () => {
setIsSheetShown(false);
});
// 배경 클릭 시 시트 닫기
sheet.querySelector(".overlay").addEventListener("click", () => {
setIsSheetShown(false);
});
// 드래그 이벤트
const touchPosition = (event) => (event.touches ? event.touches[0] : event);
let dragPosition;
const onDragStart = (event) => {
currentSheet = sheet;
dragPosition = touchPosition(event).pageY;
sheetContents.classList.add("not-selectable");
draggableArea.style.cursor = document.body.style.cursor = "grabbing";
window.addEventListener("mousemove", onDragMove);
window.addEventListener("touchmove", onDragMove, { passive: false });
window.addEventListener("mouseup", onDragEnd);
window.addEventListener("touchend", onDragEnd);
};
// const onDragMove = (event) => {
// if (dragPosition === undefined || !currentSheet) return;
// event.preventDefault();
// const y = touchPosition(event).pageY;
// const deltaY = dragPosition - y;
// const deltaHeight = (deltaY / window.innerHeight) * 100;
// setSheetHeight(sheetHeight + deltaHeight);
// dragPosition = y;
// };
const onDragMove = throttle((event) => {
if (dragPosition === undefined || !currentSheet) return;
event.preventDefault();
const y = touchPosition(event).pageY;
const deltaY = dragPosition - y;
const deltaHeight = (deltaY / window.innerHeight) * 100;
setSheetHeight(sheetHeight + deltaHeight);
dragPosition = y;
}, 25);
const onDragEnd = () => {
dragPosition = undefined;
if (currentSheet) {
const sheetContents = currentSheet.querySelector(".contents");
sheetContents.classList.remove("not-selectable");
draggableArea.style.cursor = document.body.style.cursor = "";
if (sheetHeight < 45) {
setIsSheetShown(false);
return;
} else if (
sheetHeight > 55 &&
sheetHeight < 85 &&
!sheetContents.classList.contains("fullscreen")
) {
setSheetHeight(90);
sheetContents.classList.add("fullscreen");
return;
} else if (sheetHeight < 85 && sheetContents.classList.contains("fullscreen")) {
setSheetHeight(50);
sheetContents.classList.remove("fullscreen");
return;
} else if (sheetHeight === 90) {
return;
} else if (sheetHeight > 85) {
setSheetHeight(90);
sheetContents.classList.add("fullscreen");
return;
} else if (sheetHeight < 85) {
setSheetHeight(50);
sheetContents.classList.remove("fullscreen");
}
}
window.removeEventListener("mousemove", onDragMove);
window.removeEventListener("touchmove", onDragMove);
window.removeEventListener("mouseup", onDragEnd);
window.removeEventListener("touchend", onDragEnd);
};
draggableArea.addEventListener("mousedown", onDragStart);
draggableArea.addEventListener("touchstart", onDragStart, { passive: false });
};
// 버튼의 data-sheet-target와 일치하는 시트를 찾아서 sheetsMap에 추가
openSheetButtons.forEach((button) => {
const sheetId = button.dataset.sheetTarget;
const sheet = document.getElementById(sheetId);
if (sheet) {
if (!sheetsMap.has(sheet)) {
sheetsMap.set(sheet, []);
}
sheetsMap.get(sheet).push(button);
}
});
// 각 시트를 초기화
sheetsMap.forEach((buttons, sheet) => {
initializeSheet(sheet, buttons);
});
// esc 키로 바텀시트 닫기
window.addEventListener("keyup", (event) => {
if (currentSheet && document.activeElement !== currentSheet) {
const isSheetElementFocused =
currentSheet.contains(event.target) && document.activeElement === event.target;
if (event.key === "Escape" && !isSheetElementFocused) {
setIsSheetShown(false);
}
}
});

View File

@@ -0,0 +1,225 @@
// 스크롤 시 헤더 최상단 숨김
let isScroll = false;
let lastScrollTop = 0,
delta = 1;
$(window).scroll(function () {
if ($(this).scrollTop() < 80) {
return;
}
let nowScrollTop = $(this).scrollTop();
if (Math.abs(lastScrollTop - nowScrollTop) >= delta) {
if (nowScrollTop > lastScrollTop) {
isScroll = true;
} else {
isScroll = false;
}
}
lastScrollTop = nowScrollTop;
});
setInterval(function () {
if ($(".header").hasClass("header--sub")) {
return;
}
if (isScroll) {
$(".header").addClass("scroll");
} else {
$(".header").removeClass("scroll");
}
}, 200);
// 상단 이동 스크롤 버튼
let scrollToTop = $(".quick-menu__to-top > button");
let isButtonVisible = false;
scrollToTop.hide();
scrollToTop.click(function () {
$("html, body").stop().animate({ scrollTop: 0 }, 500);
});
function checkScroll() {
let scrollTop = $(window).scrollTop();
if (scrollTop > 100) {
if (!isButtonVisible) {
scrollToTop.fadeIn();
isButtonVisible = true;
}
} else {
if (isButtonVisible) {
scrollToTop.fadeOut();
isButtonVisible = false;
}
}
}
function scrollIntervalFunction() {
checkScroll();
window.requestAnimationFrame(scrollIntervalFunction);
}
window.requestAnimationFrame(scrollIntervalFunction);
//푸터 사업자 정보 토글
$(".footerHide").hide();
$(".footer__second__detailBtn").click(function () {
$(".footerHide").toggle();
});
// 푸터 패밀리 사이트 링크
$(".family-site-link").niceSelect();
$(".family-site-link").on("change", function () {
var url = $(this).val();
if (url) {
window.open(url, "_blank");
$(this).closest("div").find(".nice-select li").removeClass("selected");
$(this).closest("div").find(".nice-select .current").text("FAMILY SITE");
}
return false;
});
// 커스텀 셀렉트박스
if ($(".select-box > select").length > 0) {
$(".select-box > select").niceSelect();
}
// 인풋 텍스트 숫자만 입력 (콤마 X, 휴대폰번호 등)
$(".input-text.number").on("input", function () {
this.value = this.value.replace(/[^0-9]/g, "");
});
// 인풋 텍스트 숫자만 입력 (콤마 O, 금액 등)
$(".input-text.numberComma").on("input", function () {
var input = $(this).val();
$(this).val(input.replace(/[^0-9]/g, "").replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"));
});
//해당 클래스를 가진 요소 3자리 마다 콤마
$(".comma").each(function () {
var text = $(this).text();
$(this).text(text.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,"));
});
// 데이트 피커
//
$(".datepicker").each(function () {
$(this).datepicker({
dateFormat: "yy-mm-dd",
prevText: "이전 달",
nextText: "다음 달",
changeMonth: "true",
changeYear: "true",
dayNames: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"],
dayNamesMin: ["일", "월", "화", "수", "목", "금", "토"],
monthNames: [
"1월",
"2월",
"3월",
"4월",
"5월",
"6월",
"7월",
"8월",
"9월",
"10월",
"11월",
"12월",
],
monthNamesShort: [
"1월",
"2월",
"3월",
"4월",
"5월",
"6월",
"7월",
"8월",
"9월",
"10월",
"11월",
"12월",
],
showMonthAfterYear: true,
yearSuffix: "년",
firstDay: 0,
maxDate: 0, // 오늘로부터 1년 전까지의 날짜까지 선택 가능
// beforeShowDay: function (date) {
// // 오늘로부터 1년 전까지의 날짜를 비활성화
// let currentDate = new Date();
// currentDate.setDate(currentDate.getDate() - 366);
// return [date.getTime() > currentDate.getTime() - 1, ""];
// },
});
});
// 기간 선택 탭버튼
var dateRangeBtn = $(".dateRange-btn");
dateRangeBtn.click(function (e) {
if (e.detail === 0) {
return;
}
e.preventDefault();
var otherBtn = $(this).siblings();
otherBtn.removeClass("selected");
$(this).addClass("selected");
});
// 체크박스 주변 박스 클릭 시, 체크박스 체크/해제
$(".product-option__item").click(function (e) {
e.stopPropagation();
$(this)
.find("input[type=checkbox]")
.prop("checked", !$(this).find("input[type=checkbox]").prop("checked"));
});
// 2401016 햄버거 메뉴 수정
$('.menu_navigation_wrap .depth').click(function (e) {
e.preventDefault();
if ($(this).parent().hasClass('on')) {
$(this).parent().next().slideUp();
$(this).parent().removeClass('on');
return false;
}
$('.menu_navigation_wrap .depth').parent().removeClass('on');
$('.menu_navigation_wrap .view').slideUp();
$('.menu_navigation_wrap .view .subview').slideUp();
$('.menu_navigation_wrap .view .on').removeClass('on');
$(this).parent().next().slideToggle();
$(this).parent().toggleClass('on');
return false;
});
$('.menu_navigation_wrap .sublist .hasSub').click(function (e) {
if ($(this).parent().hasClass('on')) {
$(this).parent().next().slideUp();
$(this).parent().removeClass('on');
return false;
}
e.preventDefault();
$('.menu_navigation_wrap .sublist .hasSub').parent().removeClass('on');
$('.menu_navigation_wrap .sublist .subview').slideUp();
$(this).parent().next().slideToggle();
$(this).parent().toggleClass('on');
return false;
});
const recentSideSwiper = new Swiper('.recentlySide-swiper', {
slidesPerView: 3,
slidesPerGroup: 3,
spaceBetween: 20,
loop: true,
pagination: {
el: '.recentlySide-swiper .swiper-pagination',
clickable: true,
},
});