id 난수화
This commit is contained in:
240
scripts/app.js
240
scripts/app.js
@@ -81,6 +81,26 @@ let activeStatuses = new Set(
|
||||
.map(([status]) => status),
|
||||
);
|
||||
|
||||
|
||||
// openModal 함수 내부에 추가하거나 전역으로 설정
|
||||
const copyBtn = document.getElementById('copy-link-btn');
|
||||
const copyBtnText = document.getElementById('copy-btn-text');
|
||||
|
||||
copyBtn.onclick = () => {
|
||||
// 현재 도메인 + 제품 ID 쿼리 조합
|
||||
const shareUrl = `${window.location.origin}${window.location.pathname}?id=${product.id}`;
|
||||
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
copyBtnText.textContent = "링크가 복사되었습니다!";
|
||||
copyBtn.classList.replace('bg-slate-900', 'bg-green-600');
|
||||
|
||||
setTimeout(() => {
|
||||
copyBtnText.textContent = "상품 링크 복사하기";
|
||||
copyBtn.classList.replace('bg-green-600', 'bg-slate-900');
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
function getStatusChipClass(status, isActive) {
|
||||
const base = STATUS_COLOR[status] ?? '';
|
||||
|
||||
@@ -200,7 +220,7 @@ export function renderProducts(page) {
|
||||
pagedProducts.forEach((product) => {
|
||||
const isSold = STATUS_META[product.status]?.soldOut === true;
|
||||
const cardHtml = `
|
||||
<div class="group flex flex-col gap-4 cursor-pointer" onclick="openModal(${product.id})">
|
||||
<div class="group flex flex-col gap-4 cursor-pointer" onclick="openModal('${product.id}')">
|
||||
<div class="relative w-full aspect-[4/5] bg-slate-50 dark:bg-slate-800 rounded-xl overflow-hidden shadow-sm group-hover:shadow-md transition-shadow">
|
||||
<div class="w-full h-full bg-center bg-no-repeat bg-cover transform ${isSold ? 'grayscale opacity-80' : 'group-hover:scale-105'} transition-transform duration-500"
|
||||
style="background-image: url('${product.images[0]}')"></div>
|
||||
@@ -235,172 +255,107 @@ window.openModal = (id) => {
|
||||
const modal = document.getElementById('product-modal');
|
||||
const images = product.images;
|
||||
|
||||
// 무한 루프를 위해 처음과 끝에 클론 추가 [마지막 이미지, ...원본 이미지..., 첫 이미지]
|
||||
// --- 1. 이미지 및 UI 초기화 로직 ---
|
||||
const loopImages = [images[images.length - 1], ...images, images[0]];
|
||||
|
||||
const mainImagesHtml = loopImages
|
||||
.map(
|
||||
(img) => `
|
||||
<div class="flex-shrink-0 w-full h-full snap-center flex items-center justify-center p-4 select-none">
|
||||
<img src="${img}" draggable="false" class="max-w-full max-h-full object-contain pointer-events-none shadow-sm rounded-lg">
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
.map(img => `
|
||||
<div class="flex-shrink-0 w-full h-full snap-center flex items-center justify-center p-4 select-none">
|
||||
<img src="${img}" draggable="false" class="max-w-full max-h-full object-contain pointer-events-none shadow-sm rounded-lg">
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// 2. 사이드 썸네일 동적 생성
|
||||
const thumbnailsHtml = product.images
|
||||
.map(
|
||||
(img, idx) => `
|
||||
<div onclick="scrollToImage(${idx})"
|
||||
class="modal-thumb-item size-16 rounded-lg border-2 ${idx === 0 ? 'border-primary' : 'border-transparent'}
|
||||
bg-cover bg-center overflow-hidden cursor-pointer ${idx === 0 ? 'opacity-100' : 'opacity-70'}
|
||||
hover:opacity-100 transition-all flex-shrink-0"
|
||||
style="background-image: url('${img}');"></div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
.map((img, idx) => `
|
||||
<div onclick="scrollToImage(${idx})"
|
||||
class="modal-thumb-item size-16 rounded-lg border-2 ${idx === 0 ? 'border-primary' : 'border-transparent'}
|
||||
bg-cover bg-center overflow-hidden cursor-pointer ${idx === 0 ? 'opacity-100' : 'opacity-70'}
|
||||
hover:opacity-100 transition-all flex-shrink-0"
|
||||
style="background-image: url('${img}');"></div>
|
||||
`).join('');
|
||||
|
||||
// 3. 페이지네이션 도트 동적 생성
|
||||
const dotsHtml = product.images
|
||||
.map(
|
||||
(_, idx) => `
|
||||
<div class="modal-dot-item ${idx === 0 ? 'w-4 bg-primary' : 'w-2 bg-gray-300 dark:bg-gray-700'} h-2 rounded-full transition-all"></div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
.map((_, idx) => `
|
||||
<div class="modal-dot-item ${idx === 0 ? 'w-4 bg-primary' : 'w-2 bg-gray-300 dark:bg-gray-700'} h-2 rounded-full transition-all"></div>
|
||||
`).join('');
|
||||
|
||||
const customTagElement = document.getElementById('modal-custom-tag');
|
||||
const tagText = product.customTag;
|
||||
|
||||
if (tagText && tagText.trim() !== '') {
|
||||
customTagElement.textContent = tagText;
|
||||
customTagElement.classList.remove('hidden');
|
||||
|
||||
// 키워드별 색상 스타일 정의
|
||||
const tagStyles = {
|
||||
완전생산한정판: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',
|
||||
특전포함: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-400',
|
||||
미개봉: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400',
|
||||
무료배송: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
||||
};
|
||||
|
||||
// 기본 스타일 (정의되지 않은 텍스트일 경우)
|
||||
const defaultStyle = 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400';
|
||||
|
||||
// 기존 스타일 클래스 초기화 후 적용
|
||||
customTagElement.className = `px-2.5 py-1 rounded-lg text-xs font-bold uppercase tracking-wider ${tagStyles[tagText] || defaultStyle}`;
|
||||
} else {
|
||||
customTagElement.classList.add('hidden');
|
||||
}
|
||||
// --- 2. 데이터 주입 ---
|
||||
document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml;
|
||||
document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml;
|
||||
document.getElementById('modal-dots').innerHTML = dotsHtml;
|
||||
document.getElementById('modal-title').textContent = product.title;
|
||||
document.getElementById('modal-price').textContent = `${product.currency}${product.price.toLocaleString()}`;
|
||||
|
||||
// 카테고리 및 상태
|
||||
const modalCategory = document.getElementById('modal-category');
|
||||
if (modalCategory) {
|
||||
modalCategory.textContent = product.category;
|
||||
}
|
||||
if (modalCategory) modalCategory.textContent = product.category;
|
||||
|
||||
// 2. 상태값 업데이트 (상태에 따른 디자인 변경 포함)
|
||||
const modalStatus = document.getElementById('modal-status');
|
||||
if (modalStatus) {
|
||||
modalStatus.textContent = product.status;
|
||||
|
||||
// 상태별 스타일 정의 (배경색, 글자색)
|
||||
const statusStyles = {
|
||||
판매중: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
|
||||
판매예정: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
||||
판매완료: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',
|
||||
미판매: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400',
|
||||
};
|
||||
|
||||
// 기존 클래스 제거 후 해당 상태의 스타일 추가
|
||||
modalStatus.className = 'px-2.5 py-1 rounded-lg text-xs font-bold uppercase tracking-wider ' + (statusStyles[product.status] || statusStyles['미판매']);
|
||||
}
|
||||
|
||||
// HTML 주입
|
||||
document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml;
|
||||
document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml;
|
||||
document.getElementById('modal-dots').innerHTML = dotsHtml;
|
||||
|
||||
// 1. 제품 상태 (specs.condition) 업데이트
|
||||
const conditionText = product.specs?.condition;
|
||||
const isVerified = product.specs?.isVerified;
|
||||
|
||||
const conditionRow = document.getElementById('modal-condition-row');
|
||||
const conditionValue = document.getElementById('modal-condition');
|
||||
const verifiedIcon = document.getElementById('modal-verified-icon');
|
||||
|
||||
// 1. 그레이드(condition) 값이 있는지 체크하여 라인 노출 결정
|
||||
if (conditionText && conditionText.trim() !== '') {
|
||||
conditionValue.textContent = conditionText;
|
||||
conditionRow.classList.remove('hidden');
|
||||
conditionRow.classList.add('flex');
|
||||
|
||||
// 2. 인증 여부에 따라 SVG 아이콘 노출 결정
|
||||
if (isVerified) {
|
||||
verifiedIcon.classList.remove('hidden');
|
||||
} else {
|
||||
verifiedIcon.classList.add('hidden');
|
||||
}
|
||||
// 커스텀 태그
|
||||
const customTagElement = document.getElementById('modal-custom-tag');
|
||||
if (product.customTag?.trim()) {
|
||||
customTagElement.textContent = product.customTag;
|
||||
customTagElement.classList.remove('hidden');
|
||||
} else {
|
||||
// 값이 없으면 라인 전체 숨김
|
||||
conditionRow.classList.add('hidden');
|
||||
conditionRow.classList.remove('flex');
|
||||
customTagElement.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 2. 제품 설명 (fullDescription) 업데이트
|
||||
// 상세 설명
|
||||
const modalDesc = document.getElementById('modal-desc');
|
||||
if (modalDesc) {
|
||||
const descData = product.fullDescription;
|
||||
|
||||
if (Array.isArray(descData)) {
|
||||
// 배열을 <br> 태그로 합쳐서 HTML로 주입
|
||||
modalDesc.innerHTML = descData.join('<br>');
|
||||
} else {
|
||||
modalDesc.textContent = descData || '';
|
||||
}
|
||||
modalDesc.innerHTML = Array.isArray(product.fullDescription) ? product.fullDescription.join('<br>') : (product.fullDescription || '');
|
||||
}
|
||||
|
||||
// 구매일자 처리 (경로 수정: product.specs?.purchaseDate)
|
||||
const modalDate = document.getElementById('modal-date');
|
||||
const modalDateRow = document.getElementById('modal-date-row');
|
||||
|
||||
// specs 객체 안의 purchaseDate를 가져옴
|
||||
const pDate = product.specs?.purchaseDate;
|
||||
|
||||
if (pDate && pDate.trim() !== '' && pDate !== 'null') {
|
||||
modalDate.textContent = pDate;
|
||||
modalDateRow.classList.remove('hidden');
|
||||
modalDateRow.classList.add('flex');
|
||||
} else {
|
||||
modalDateRow.classList.add('hidden');
|
||||
modalDateRow.classList.remove('flex');
|
||||
// --- 3. [핵심] 링크 복사 버튼 이벤트 바인딩 ---
|
||||
const copyBtn = document.getElementById('copy-link-btn');
|
||||
const copyBtnText = document.getElementById('copy-btn-text');
|
||||
if (copyBtn) {
|
||||
copyBtn.onclick = () => {
|
||||
const shareUrl = `${window.location.origin}${window.location.pathname}?id=${product.id}`;
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
if (copyBtnText) copyBtnText.textContent = "링크가 복사되었습니다!";
|
||||
copyBtn.classList.add('!bg-green-600');
|
||||
setTimeout(() => {
|
||||
if (copyBtnText) copyBtnText.textContent = "상품 링크 복사하기";
|
||||
copyBtn.classList.remove('!bg-green-600');
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 텍스트 정보 주입 (ID들 맞춰주세요)
|
||||
document.getElementById('modal-title').textContent = product.title;
|
||||
document.getElementById('modal-price').textContent = `${product.currency}${product.price.toLocaleString()}`;
|
||||
// ... 나머지 정보 주입
|
||||
|
||||
// --- 4. 모달 활성화 및 초기 위치 설정 ---
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 드래그 기능 다시 연결
|
||||
const container = document.getElementById('modal-main-carousel-container');
|
||||
const carousel = document.getElementById('modal-main-carousel');
|
||||
carousel.innerHTML = mainImagesHtml;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 초기 위치 설정 (클론된 마지막 이미지 다음인 '진짜 첫 번째' 이미지로 이동)
|
||||
// const initialIndex = 1;
|
||||
container.style.scrollBehavior = 'auto';
|
||||
container.scrollLeft = container.clientWidth;
|
||||
|
||||
// 드래그 및 무한 루프 감시 시작
|
||||
// 캐러셀 초기화
|
||||
initBetterCarousel(container, images.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* 모달 닫기 (URL 정리 기능 포함)
|
||||
*/
|
||||
window.closeModal = () => {
|
||||
document.getElementById('product-modal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
|
||||
const cleanUrl = window.location.origin + window.location.pathname;
|
||||
window.history.replaceState(null, '', cleanUrl);
|
||||
};
|
||||
|
||||
function initBetterCarousel(container, originalLength) {
|
||||
let isDragging = false;
|
||||
let startX = 0;
|
||||
@@ -530,11 +485,6 @@ window.changePage = (page) => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
window.closeModal = () => {
|
||||
document.getElementById('product-modal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
};
|
||||
|
||||
// 초기 실행
|
||||
document.addEventListener('DOMContentLoaded', () => renderProducts(currentPage));
|
||||
|
||||
@@ -670,3 +620,31 @@ renderStatusChips();
|
||||
|
||||
// 🔥 최초 필터 적용 (이게 첫 렌더)
|
||||
applyFilters();
|
||||
|
||||
// 초기 실행 시 호출
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderProducts(currentPage);
|
||||
checkUrlAndOpenModal();
|
||||
});
|
||||
|
||||
|
||||
function checkUrlAndOpenModal() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const productId = params.get('id'); // URL에서 가져온 ID (문자열)
|
||||
|
||||
if (productId) {
|
||||
// 데이터의 id와 URL의 id를 모두 문자열로 변환하여 비교
|
||||
const product = products.find((p) => String(p.id) === productId);
|
||||
|
||||
if (product) {
|
||||
// DOM 렌더링 시간을 고려해 약간의 지연 후 모달 오픈
|
||||
setTimeout(() => openModal(product.id), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// [최종 ID 생성기] 매번 완전히 새로운 8자리 난수 출력
|
||||
const newId = Math.random().toString(36).substring(2, 10);
|
||||
|
||||
console.log(`%c[NEW ID for data.js]: ${newId}`, "color: #137fec; font-weight: bold; border: 1px solid #137fec; padding: 2px 5px; border-radius: 4px;");
|
||||
Reference in New Issue
Block a user