id 난수화

This commit is contained in:
2026-02-04 09:20:01 +09:00
parent c185fd1cef
commit 6265a0b7c0
7 changed files with 116 additions and 139 deletions

View File

@@ -1,6 +1,6 @@
const products = [
{
id: 1,
id: 'i3zgj4zl',
title: '플로럴 플로우러브',
price: 60000,
currency: '₩',
@@ -23,7 +23,7 @@ const products = [
'',
'',
],
images: ['/images/games/20260204_002254.jpg'],
images: ['/images/games/i3zgj4zl_01.jpg','/images/games/i3zgj4zl_02.jpg','/images/games/i3zgj4zl_03.jpg','/images/games/i3zgj4zl_04.jpg'],
customTag: '완전생산한정판',
specs: {
purchaseDate: '',

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -215,8 +215,7 @@
<div class="w-2 h-2 rounded-full bg-gray-300 dark:bg-gray-700"></div>
</div>
<!-- Side Thumbnails (Visible on Large Screens) -->
<div id="modal-thumbnails" class="hidden lg:flex absolute left-4 top-1/2 -translate-y-1/2 flex-col gap-3 max-h-[70vh] overflow-y-auto no-scrollbar pr-1">
</div>
<div id="modal-thumbnails" class="hidden lg:flex absolute left-4 top-1/2 -translate-y-1/2 flex-col gap-3 max-h-[70vh] overflow-y-auto no-scrollbar pr-1"></div>
</div>
<!-- Right: Details Section -->
<div class="w-full md:w-2/5 flex flex-col h-full bg-white dark:bg-background-dark p-6 md:p-10 overflow-y-auto">
@@ -256,11 +255,11 @@
</div>
<!-- Footer / CTA -->
<div class="mt-auto pt-6 border-t border-gray-100 dark:border-gray-800">
<button class="w-full flex items-center justify-center gap-2 bg-primary hover:bg-blue-600 text-white font-bold py-4 px-6 rounded-xl transition-all shadow-lg shadow-primary/25">
<span class="material-symbols-outlined">chat_bubble</span>
<span>오픈카톡으로 문의하기</span>
<button id="copy-link-btn" class="w-full flex items-center justify-center gap-2 bg-slate-900 dark:bg-white dark:text-slate-900 text-white font-bold py-4 px-6 rounded-xl transition-all shadow-lg">
<span class="material-symbols-outlined">link</span>
<span id="copy-btn-text">상품 링크 복사하기</span>
</button>
<p class="text-center text-xs text-gray-400 dark:text-gray-500 mt-4 font-medium">입금 확인 후, 24시간 이내 발송</p>
<p class="text-center text-xs text-gray-400 dark:text-gray-500 mt-4 font-medium">링크를 복사해 문의 시 전달해주세요.</p>
</div>
</div>
</div>

View File

@@ -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;");