merge master into develop - CscListRForm.jsp FAQ 아코디언 버그 수정
This commit is contained in:
578
build/resources/main/static/mobile/js/bnft/atend/Atend.js
Normal file
578
build/resources/main/static/mobile/js/bnft/atend/Atend.js
Normal 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);
|
||||
}
|
||||
343
build/resources/main/static/mobile/js/bnft/bnftMain/BnftMain.js
Normal file
343
build/resources/main/static/mobile/js/bnft/bnftMain/BnftMain.js
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
344
build/resources/main/static/mobile/js/bnft/common/Common_app.js
Normal file
344
build/resources/main/static/mobile/js/bnft/common/Common_app.js
Normal 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); // 애니메이션 완료 후 제거
|
||||
}
|
||||
650
build/resources/main/static/mobile/js/bnft/conrev/Conrev.js
Normal file
650
build/resources/main/static/mobile/js/bnft/conrev/Conrev.js
Normal 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();
|
||||
});
|
||||
214
build/resources/main/static/mobile/js/bnft/cpslmkn/Cpslmkn.js
Normal file
214
build/resources/main/static/mobile/js/bnft/cpslmkn/Cpslmkn.js
Normal 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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(); // 특별 이벤트 슬라이더 초기화
|
||||
});
|
||||
@@ -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('재시작 요청 중 오류가 발생했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
});
|
||||
269
build/resources/main/static/mobile/js/bnft/discshar/Discshar.js
Normal file
269
build/resources/main/static/mobile/js/bnft/discshar/Discshar.js
Normal 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();
|
||||
});
|
||||
351
build/resources/main/static/mobile/js/bnft/evntview/Evntview.js
Normal file
351
build/resources/main/static/mobile/js/bnft/evntview/Evntview.js
Normal 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 // 페이지 초기화 함수 노출
|
||||
};
|
||||
})();
|
||||
355
build/resources/main/static/mobile/js/bnft/hlthset/Hlthset.js
Normal file
355
build/resources/main/static/mobile/js/bnft/hlthset/Hlthset.js
Normal 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);
|
||||
}
|
||||
});
|
||||
200
build/resources/main/static/mobile/js/bnft/intakchl/Intakchl.js
Normal file
200
build/resources/main/static/mobile/js/bnft/intakchl/Intakchl.js
Normal 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');
|
||||
}
|
||||
});
|
||||
@@ -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에서 데이터 설정 후 호출됨
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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('[마일리지 요약 페이지] 초기화 완료');
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
487
build/resources/main/static/mobile/js/bnft/lucklott/Lucklott.js
Normal file
487
build/resources/main/static/mobile/js/bnft/lucklott/Lucklott.js
Normal 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; // 그리기 상태 초기화
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
431
build/resources/main/static/mobile/js/bnft/lvshop/Lvshop.js
Normal file
431
build/resources/main/static/mobile/js/bnft/lvshop/Lvshop.js
Normal 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');
|
||||
}
|
||||
432
build/resources/main/static/mobile/js/bnft/oxquiz/Oxquiz.js
Normal file
432
build/resources/main/static/mobile/js/bnft/oxquiz/Oxquiz.js
Normal 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();
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
184
build/resources/main/static/mobile/js/bnft/qrevnt/QREvntMain.js
Normal file
184
build/resources/main/static/mobile/js/bnft/qrevnt/QREvntMain.js
Normal 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';
|
||||
});
|
||||
});
|
||||
437
build/resources/main/static/mobile/js/bnft/rougame/Rougame.js
Normal file
437
build/resources/main/static/mobile/js/bnft/rougame/Rougame.js
Normal 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();
|
||||
});
|
||||
21
build/resources/main/static/mobile/js/bnft/stepcnt/Chart.bundle.min.js
vendored
Normal file
21
build/resources/main/static/mobile/js/bnft/stepcnt/Chart.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1011
build/resources/main/static/mobile/js/bnft/stepcnt/Stepcnt.js
Normal file
1011
build/resources/main/static/mobile/js/bnft/stepcnt/Stepcnt.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
185
build/resources/main/static/mobile/js/bottom-sheet.js
Normal file
185
build/resources/main/static/mobile/js/bottom-sheet.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
225
build/resources/main/static/mobile/js/script.js
Normal file
225
build/resources/main/static/mobile/js/script.js
Normal 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,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user