- 카드 전환 방식 변경
- 필터 조건 선택 가능하게 변경 등
This commit is contained in:
@@ -67,6 +67,7 @@ export function renderProducts(page = 1) {
|
||||
if (state.viewMode === 'grid') {
|
||||
grid.innerHTML = '';
|
||||
pagedProducts.forEach((product) => {
|
||||
|
||||
// 1. 상태 판별
|
||||
const isSold = STATUS_META[product.status]?.soldOut === true;
|
||||
const isNonSale = product.status === '미판매'; // 상태값이 '미판매'일 때
|
||||
@@ -76,82 +77,69 @@ export function renderProducts(page = 1) {
|
||||
const conditionConfig = PRODUCT_CONDITIONS[conditionKey];
|
||||
const conditionDisplay = conditionConfig ? conditionConfig.label : (conditionKey || '');
|
||||
|
||||
grid.insertAdjacentHTML('beforeend', `
|
||||
<div class="product-card group flex flex-col gap-4 cursor-pointer"
|
||||
data-id="${product.id}"
|
||||
onclick="window.openModal('${product.id}')"
|
||||
${!isSold ? `onmousemove="window.handleThumbnailHover(event, '${product.id}')"
|
||||
onmouseleave="window.handleThumbnailLeave('${product.id}')"` : ''}>
|
||||
grid.insertAdjacentHTML('beforeend', `
|
||||
<div class="product-card group flex flex-col gap-4 cursor-pointer"
|
||||
data-id="${product.id}"
|
||||
onclick="if(!window.isDragging) window.openModal('${product.id}')"
|
||||
|
||||
${!isSold ? `
|
||||
onmousemove="window.handleThumbnailHover(event, '${product.id}')"
|
||||
onmouseleave="window.handleThumbnailLeave('${product.id}')"
|
||||
ontouchstart="window.handleTouchStart(event)"
|
||||
ontouchmove="window.handleTouchMove(event, '${product.id}')"
|
||||
ontouchend="window.handleTouchEnd(event, '${product.id}')"
|
||||
` : `
|
||||
ontouchend="window.handleTouchEnd(event, '${product.id}')"
|
||||
`}>
|
||||
|
||||
<div class="relative w-full aspect-card bg-slate-200 dark:bg-slate-800 rounded-xl overflow-hidden shadow-sm">
|
||||
<div id="thumb-${product.id}"
|
||||
class="w-full h-full bg-center bg-no-repeat bg-cover transform ${isSold ? 'grayscale opacity-60' : 'group-hover:scale-105 transition-transform duration-500'}"
|
||||
style="background-image: none; will-change: background-image;">
|
||||
</div>
|
||||
|
||||
<div class="absolute top-3 left-3">
|
||||
<span class="px-2 py-1 text-[10px] uppercase tracking-wider font-bold rounded ${STATUS_COLOR[product.status]} backdrop-blur-md border">
|
||||
${product.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
${!isSold && product.images?.length > 1 ? `
|
||||
<div id="indicator-${product.id}" class="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none">
|
||||
${product.images.map((_, i) => `
|
||||
<div class="w-1.5 h-1.5 rounded-full transition-all duration-300 shadow-sm ${i === 0 ? 'bg-white scale-125' : 'bg-white/40'}"></div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="relative w-full aspect-card bg-slate-200 dark:bg-slate-800 rounded-xl overflow-hidden shadow-sm">
|
||||
<div id="thumb-${product.id}"
|
||||
class="w-full h-full bg-center bg-no-repeat bg-cover absolute inset-0 transform transition-transform duration-500 ${isSold ? 'grayscale opacity-60' : 'group-hover:scale-105'}"
|
||||
style="background-image: none; will-change: background-image;">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-baseline gap-1">
|
||||
<h3 class="text-slate-900 dark:text-white text-base font-semibold leading-tight break-keep ${isSold ? 'line-through text-slate-400' : ''}">
|
||||
${product.title}
|
||||
</h3>
|
||||
<p class="text-base font-bold whitespace-nowrap ${isNonSale ? 'text-slate-400 font-medium text-xs uppercase' : 'text-slate-900 dark:text-white'}">
|
||||
${isNonSale ? 'Not for Sale' : `₩${product.price.toLocaleString()}`}
|
||||
</p>
|
||||
</div>
|
||||
<div id="thumb-fade-${product.id}"
|
||||
class="w-full h-full bg-center bg-no-repeat bg-cover absolute inset-0 opacity-0 transition-opacity duration-300 pointer-events-none transform transition-transform duration-500 ${isSold ? 'grayscale' : 'group-hover:scale-105'}"
|
||||
style="background-image: none; will-change: background-image, opacity;">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
${conditionDisplay ? `<span class="text-[11px] font-medium text-slate-400 mb-0.5">${conditionDisplay}</span>` : ''}
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm font-normal line-clamp-1 italic">
|
||||
${product.description}
|
||||
</p>
|
||||
<div class="absolute top-3 left-3 z-10">
|
||||
<span class="px-2 py-1 text-[10px] uppercase tracking-wider font-bold rounded ${STATUS_COLOR[product.status]} backdrop-blur-md border">
|
||||
${product.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
${!isSold && product.images?.length > 1 ? `
|
||||
<div id="indicator-${product.id}" class="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none z-10">
|
||||
${product.images.map((_, i) => `
|
||||
<div class="w-1.5 h-1.5 rounded-full transition-all duration-300 shadow-sm ${i === 0 ? 'bg-white scale-125' : 'bg-white/40'}"></div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex flex-col justify-between items-start sm:items-baseline gap-1">
|
||||
<h3 class="text-slate-900 dark:text-white text-base font-semibold leading-tight break-keep ${isSold ? 'line-through text-slate-400' : ''}">
|
||||
${product.title}
|
||||
</h3>
|
||||
<p class="text-base font-bold whitespace-nowrap ${isNonSale ? 'text-slate-400 font-medium text-xs uppercase' : 'text-slate-900 dark:text-white'}">
|
||||
${isNonSale ? 'Not for Sale' : `${product.currency || '₩'}${product.price.toLocaleString()}`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
${conditionDisplay ? `<span class="text-[11px] font-medium text-slate-400 mb-0.5">${conditionDisplay}</span>` : ''}
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm font-normal line-clamp-1 italic">
|
||||
${product.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
// 지연 로딩(Lazy Loading) 관찰자 설정
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const card = entry.target;
|
||||
const productId = card.getAttribute('data-id');
|
||||
const product = state.visibleProducts.find(p => p.id === productId);
|
||||
const thumb = document.getElementById(`thumb-${productId}`);
|
||||
|
||||
if (product && thumb) {
|
||||
// 1. 첫 번째 이미지 로드
|
||||
thumb.style.backgroundImage = `url("${product.images[0]}")`;
|
||||
|
||||
// 2. 마우스 올리기 전, 나머지 이미지들 백그라운드 프리로드
|
||||
if (!STATUS_META[product.status]?.soldOut && product.images.length > 1) {
|
||||
product.images.slice(1).forEach(url => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
}
|
||||
observer.unobserve(card);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.product-card').forEach(card => observer.observe(card));
|
||||
setupLazyLoading();
|
||||
|
||||
} else {
|
||||
// 테이블 렌더링
|
||||
@@ -204,6 +192,36 @@ export function renderProducts(page = 1) {
|
||||
}
|
||||
}
|
||||
|
||||
function setupLazyLoading() {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const card = entry.target;
|
||||
const productId = card.getAttribute('data-id');
|
||||
// state.js 등에서 가져온 데이터 활용
|
||||
const product = state.visibleProducts.find(p => p.id === productId);
|
||||
const thumb = document.getElementById(`thumb-${productId}`);
|
||||
|
||||
if (product && thumb) {
|
||||
// 1. 첫 번째 이미지 로드
|
||||
thumb.style.backgroundImage = `url("${product.images[0]}")`;
|
||||
|
||||
// 2. 나머지 이미지 프리로드 (반짝임 방지)
|
||||
if (!STATUS_META[product.status]?.soldOut && product.images.length > 1) {
|
||||
product.images.slice(1).forEach(url => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
}
|
||||
observer.unobserve(card);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.product-card').forEach(card => observer.observe(card));
|
||||
}
|
||||
|
||||
export function renderPagination() {
|
||||
const container = document.getElementById('pagination');
|
||||
if (!container) return;
|
||||
|
||||
Reference in New Issue
Block a user