(() => { const { pageHelper, EventManager, utils, constants } = ShopbySkin; const { initialize } = pageHelper.productListHelper(); const urlQueryParams = utils.getQueryParams(); // 기본 상품 개수를 40개로 설정 const defaultPageSize = 40; initialize({ ...urlQueryParams, pageSize: urlQueryParams.pageSize || defaultPageSize, }); const productListEl = document.querySelector('product-list-items'); const displayProductActionMap = { // NOTE: 좋아요버튼 클릭 PRODUCT_LIKE_BTN: ({ event, btn }) => { event?.preventDefault?.(); const productNo = Number(btn.getAttribute('shopby-product-no') ?? 0); utils.isSignIn() ? EventManager.fire('MUTATE_LIKE_PRODUCT', { productNo, onSuccess: (like) => { like ? btn.classList.add('is-active') : btn.classList.remove('is-active'); }, }) : EventManager.fire('MODAL_CONFIRM_OPEN', { message: `로그인하셔야 본 서비스를 이용하실 수 있습니다.`, noticeType: constants.NOTICE_MODAL_TYPE.WARNING, onConfirm: () => { const nextUrl = `${location.origin}${constants.PAGE.SIGN_IN}`; utils.moveTo({ url: `${nextUrl}?from=${encodeURIComponent(location.href)}`, }); }, }); }, // NOTE: 더보기 SEARCH_MORE_PRODUCT: () => { EventManager.fire('SEARCH_MORE_PRODUCT'); // 외부스크립트 등록 기능. 삭제금지 ShopbyExternalScript?.setPageScriptType(urlQueryParams.keywords ? 'PRODUCT_SEARCH' : 'PRODUCT_LIST', true); }, // NOTE: 상품터치시 상품상세로 이동전 성인상품인지 확인을 위한 이벤트 MOVE_PRODUCT_DETAIL: ({ event }) => { if (!utils.isAdultVerified(event)) { EventManager.fire('MODAL_CONFIRM_OPEN', { message: '성인인증이 필요한 상품입니다.', noticeType: 'CAUTION', onConfirm: () => { const productNo = event.target.closest('[shopby-product-no]')?.getAttribute('shopby-product-no'); const nextUrl = encodeURIComponent( `${location.origin}${constants.PAGE.PRODUCT_DETAIL}?productNo=${productNo}` ); utils.moveTo({ url: `${location.origin}${constants.PAGE.ADULT_CERTIFICATION}?go-back=true&from=${nextUrl}`, }); }, }); } }, }; const clickEventListener = (event) => { if (!event?.target) { return; } const btn = event?.target?.closest?.('[shopby-action]') || event?.target?.parentElement?.closest?.('[shopby-action]'); const action = btn?.getAttribute('shopby-action'); if (!btn) { return; } displayProductActionMap[action]?.({ btn, event, }); }; // 하위 카테고리 슬라이더 직접 초기화 (Shopby 초기화 후) EventManager.on('PAGE_LOAD_COMPLETED', () => { setTimeout(() => { const categorySliderEl = document.querySelector('.product-list-category-slider .swiper'); if (categorySliderEl && window.Swiper && !categorySliderEl.swiper) { new window.Swiper(categorySliderEl, { slidesPerView: 'auto', spaceBetween: 10, freeMode: true, grabCursor: true, resistance: true, resistanceRatio: 0.5, }); } }, 500); }); EventManager.on('PAGE_LOAD_COMPLETED', () => { // 외부스크립트 등록 기능. 삭제금지 ShopbyExternalScript?.setPageScriptType(urlQueryParams.keywords ? 'PRODUCT_SEARCH' : 'PRODUCT_LIST'); // 검색 페이지일 경우 카테고리 필터 표시 if (urlQueryParams.keywords) { initSearchCategoryFilter(); } }); // 검색 페이지용 카테고리 필터 초기화 async function initSearchCategoryFilter() { const filterContainer = document.getElementById('search-category-filter'); if (!filterContainer) return; const clientId = window.shopbyClientId || 'UUBsnIVqbs6h156TJHMiRA=='; const headers = { 'Version': '1.0', 'clientId': clientId, 'platform': 'MOBILE_WEB', 'Content-Type': 'application/json', }; try { // 카테고리 목록 조회 const categoryResponse = await fetch('https://shop-api.e-ncp.com/categories', { headers }); if (!categoryResponse.ok) return; const categoryData = await categoryResponse.json(); const topCategories = categoryData?.multiLevelCategories || []; if (topCategories.length === 0) return; // 현재 검색어로 각 카테고리별 상품 수 조회 const keywords = urlQueryParams.keywords; const selectedCategoryNo = urlQueryParams.categoryNo; // 카테고리가 선택되어 있는지 확인 let categoryItems = []; let parentCategoryLabel = null; if (selectedCategoryNo) { // 선택된 카테고리의 하위 카테고리 찾기 const findCategoryAndChildren = (categories, targetNo, parentLabel = null) => { for (const cat of categories) { if (String(cat.categoryNo) === String(targetNo)) { return { category: cat, parentLabel: parentLabel || cat.label }; } if (cat.children && cat.children.length > 0) { const found = findCategoryAndChildren(cat.children, targetNo, cat.label); if (found) return found; } } return null; }; const result = findCategoryAndChildren(topCategories, selectedCategoryNo); if (result && result.category) { parentCategoryLabel = result.parentLabel; // "in 카테고리명" 표시 const categoryLabel = document.getElementById('search-category-label'); if (categoryLabel) { categoryLabel.textContent = `in ${result.category.label}`; } // 하위 카테고리가 있으면 하위 카테고리 표시 if (result.category.children && result.category.children.length > 0) { categoryItems = result.category.children.map(child => ({ categoryNo: child.categoryNo, label: child.label, depth: child.depth || 2, children: child.children || [], })); } else { // 하위 카테고리가 없으면 같은 레벨의 형제 카테고리 표시 // 부모 카테고리에서 형제들 찾기 const findSiblings = (categories, targetNo) => { for (const cat of categories) { if (cat.children) { const found = cat.children.find(c => String(c.categoryNo) === String(targetNo)); if (found) return cat.children; const deepFound = findSiblings(cat.children, targetNo); if (deepFound) return deepFound; } } return null; }; const siblings = findSiblings(topCategories, selectedCategoryNo); if (siblings) { categoryItems = siblings.map(sibling => ({ categoryNo: sibling.categoryNo, label: sibling.label, depth: sibling.depth || 2, children: sibling.children || [], })); } } } } // 카테고리가 선택되지 않았거나 찾지 못한 경우 최상위 카테고리 표시 if (categoryItems.length === 0) { categoryItems = topCategories.map(topCat => ({ categoryNo: topCat.categoryNo, label: topCat.label, depth: 1, children: topCat.children || [], })); } // 카테고리별 상품 수 조회 (키워드 포함) const countPromises = categoryItems.map(async (cat) => { const apiUrl = `https://shop-api.e-ncp.com/products/search?filter.keywords=${encodeURIComponent(keywords)}&categoryNos=${cat.categoryNo}&pageNumber=1&pageSize=1`; try { const response = await fetch(apiUrl, { headers }); if (response.ok) { const result = await response.json(); return { ...cat, count: result?.totalCount ?? 0 }; } } catch (e) { } return { ...cat, count: 0 }; }); const categoriesWithCount = await Promise.all(countPromises); // DOM 생성 const swiperWrapper = filterContainer.querySelector('.swiper-wrapper'); if (!swiperWrapper) return; swiperWrapper.innerHTML = ''; categoriesWithCount.forEach((cat) => { if (cat.count === 0) return; // 상품 없는 카테고리 제외 const li = document.createElement('li'); li.className = 'swiper-slide category-slider__slide'; // 현재 선택된 카테고리와 비교 (하위 카테고리 선택 시 활성화 표시) const isActive = String(selectedCategoryNo) === String(cat.categoryNo); const href = `/pages/product/product-list.html?keywords=${encodeURIComponent(keywords)}&categoryNo=${cat.categoryNo}`; li.innerHTML = ` ${cat.label} (${cat.count.toLocaleString()}) `; swiperWrapper.appendChild(li); }); // Swiper 초기화 (Shopby 방식) setTimeout(() => { const swiperEl = filterContainer.querySelector('.swiper'); if (swiperEl && window.Swiper && !swiperEl.swiper) { new window.Swiper(swiperEl, { slidesPerView: 'auto', spaceBetween: 15, freeMode: true, grabCursor: true, resistance: true, resistanceRatio: 0.5, slidesOffsetBefore: 15, slidesOffsetAfter: 15, }); } }, 100); } catch (e) { // Silent fail } } EventManager.on('QUERY_SEARCH_PRODUCTS_BY', (data) => { // 외부스크립트 등록 기능. 삭제금지 ShopbyExternalScript?.setGlobalObjectSb({ searchedProduct: { ...data, items: productListEl.store.state.items, }, }); if (!urlQueryParams?.categoryNo) { ShopbyExternalScript?.setGlobalObjectSb({ currentCategory: null, }); } }); EventManager.on('PRODUCTS_SORT_TYPE', () => { // 정렬 시 페이지 스크립트 재로드 // 외부스크립트 등록 기능. 삭제금지 ShopbyExternalScript?.setPageScriptType(urlQueryParams.keywords ? 'PRODUCT_SEARCH' : 'PRODUCT_LIST', true); }); // 하위 카테고리 상품 개수 조회 EventManager.on('QUERY_DISPLAY_CATEGORY', (data) => { // 외부스크립트 등록 기능. 삭제금지 ShopbyExternalScript?.setGlobalObjectSb({ currentCategory: data?.currentCategory, }); // 하위 카테고리별 상품 개수 조회 (DOM 렌더링 후 실행) // categoryChildren 또는 다른 가능한 속성에서 하위 카테고리 추출 let categoryChildren = data?.categoryChildren || []; // categoryChildren이 비어있으면 currentCategory.children 확인 if (categoryChildren.length === 0 && data?.currentCategory?.children) { categoryChildren = data.currentCategory.children; } // 그래도 비어있으면 categories에서 현재 카테고리의 하위 항목 확인 if (categoryChildren.length === 0 && data?.categories) { const currentCategoryNo = urlQueryParams?.categoryNo; const findChildren = (categories, targetNo) => { for (const cat of categories) { if (String(cat.categoryNo) === String(targetNo)) { return cat.children || []; } if (cat.children) { const found = findChildren(cat.children, targetNo); if (found.length > 0) return found; } } return []; }; categoryChildren = findChildren(data.categories, currentCategoryNo); } if (categoryChildren.length > 0) { setTimeout(async () => { // Shopby Shop API 직접 호출을 위한 헤더 설정 const clientId = window.shopbyClientId || 'UUBsnIVqbs6h156TJHMiRA=='; const headers = { 'Version': '1.0', 'clientId': clientId, 'platform': 'MOBILE_WEB', 'Content-Type': 'application/json', }; // 병렬로 모든 카테고리 상품 개수 조회 const promises = categoryChildren.map(async (child) => { try { // Shopby Shop API /products/search 엔드포인트 호출 const apiUrl = `https://shop-api.e-ncp.com/products/search?categoryNos=${child.categoryNo}&pageNumber=1&pageSize=1`; const response = await fetch(apiUrl, { headers }); if (response.ok) { const result = await response.json(); const totalCount = result?.totalCount ?? 0; return { categoryNo: child.categoryNo, count: totalCount }; } } catch (e) { // Silent fail for API errors } return { categoryNo: child.categoryNo, count: null }; }); const results = await Promise.all(promises); // DOM 업데이트 results.forEach(({ categoryNo, count }) => { const countEl = document.querySelector(`[data-category-no="${categoryNo}"]`); if (countEl && count !== null) { countEl.textContent = `(${count.toLocaleString()})`; } }); }, 500); } else { // categoryChildren이 없으면 DOM에서 직접 카테고리 번호 추출 setTimeout(async () => { const categoryElements = document.querySelectorAll('[data-category-no]'); if (categoryElements.length === 0) return; const clientId = window.shopbyClientId || 'UUBsnIVqbs6h156TJHMiRA=='; const headers = { 'Version': '1.0', 'clientId': clientId, 'platform': 'MOBILE_WEB', 'Content-Type': 'application/json', }; const promises = Array.from(categoryElements).map(async (el) => { const categoryNo = el.getAttribute('data-category-no'); try { const apiUrl = `https://shop-api.e-ncp.com/products/search?categoryNos=${categoryNo}&pageNumber=1&pageSize=1`; const response = await fetch(apiUrl, { headers }); if (response.ok) { const result = await response.json(); const totalCount = result?.totalCount ?? 0; // 0보다 큰 경우에만 표시 if (totalCount > 0) { el.textContent = ` (${totalCount.toLocaleString()})`; el.style.display = 'inline'; } else { el.style.display = 'none'; } } } catch (e) { // Silent fail for API errors } }); await Promise.all(promises); }, 800); } }); // 무한 스크롤 구현 let isLoading = false; const handleInfiniteScroll = () => { if (isLoading) return; const scrollTop = window.scrollY || document.documentElement.scrollTop; const windowHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight; // 페이지 하단 300px 전에 다음 페이지 로드 if (scrollTop + windowHeight >= documentHeight - 300) { const state = productListEl?.store?.state; if (state && !state.hasNoMoreItems) { isLoading = true; EventManager.fire('SEARCH_MORE_PRODUCT'); // 외부스크립트 등록 기능. 삭제금지 ShopbyExternalScript?.setPageScriptType(urlQueryParams.keywords ? 'PRODUCT_SEARCH' : 'PRODUCT_LIST', true); // 로딩 완료 후 플래그 리셋 setTimeout(() => { isLoading = false; }, 1000); } } }; window.addEventListener('scroll', handleInfiniteScroll, { passive: true }); // 카테고리 리스트 드래그 스크롤 기능 (마우스 지원) const enableDragScroll = () => { let isDown = false; let startX; let scrollLeft; let isDragging = false; let currentSlider = null; // 이미지나 링크의 기본 드래그 동작 방지 document.addEventListener('dragstart', (e) => { if (e.target.closest('.category-slider__wrapper')) { e.preventDefault(); } }); document.addEventListener('mousedown', (e) => { const slider = e.target.closest('.category-slider__wrapper'); if (!slider) return; isDown = true; currentSlider = slider; slider.classList.add('active'); // offsetLeft 대신 pageX만 사용하여 이동 거리 계산 startX = e.pageX; scrollLeft = slider.scrollLeft; isDragging = false; }); document.addEventListener('mouseleave', () => { if (isDown && currentSlider) { isDown = false; currentSlider.classList.remove('active'); currentSlider = null; } }); document.addEventListener('mouseup', () => { if (isDown && currentSlider) { isDown = false; currentSlider.classList.remove('active'); currentSlider = null; // 클릭 이벤트 핸들러에서 isDragging을 확인할 수 있도록 지연 setTimeout(() => { isDragging = false; }, 0); } }); document.addEventListener('mousemove', (e) => { if (!isDown || !currentSlider) return; e.preventDefault(); const x = e.pageX; const walk = (x - startX) * 1.5; // 스크롤 속도 조절 currentSlider.scrollLeft = scrollLeft - walk; if (Math.abs(walk) > 5) { isDragging = true; } }); // 드래그 후 클릭 방지 (캡처링 단계에서 처리) document.addEventListener('click', (e) => { if (isDragging && e.target.closest('.category-slider__wrapper')) { e.preventDefault(); e.stopPropagation(); } }, true); }; enableDragScroll(); productListEl.addEventListener('click', clickEventListener); })();