feat: 이미지 교체 기능 추가
- URL 입력으로 이미지 수동 교체 - 다양한 User-Agent로 다운로드 시도 (차단 우회) - base64 변환 + 썸네일 자동 생성 - status를 'manual'로 표시
This commit is contained in:
@@ -410,6 +410,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 이미지 교체 모달 -->
|
||||
<div class="modal" id="replaceModal">
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<h3>🔄 이미지 교체</h3>
|
||||
<p style="color: #9ca3af; margin-bottom: 8px; font-size: 13px;">
|
||||
구글 이미지 등에서 찾은 URL을 붙여넣으세요
|
||||
</p>
|
||||
<div id="replaceProductInfo" style="background: rgba(139,92,246,0.1); border-radius: 8px; padding: 12px; margin-bottom: 16px;">
|
||||
<div style="font-weight: 600;" id="replaceProductName"></div>
|
||||
<div style="font-size: 12px; color: #a855f7; font-family: monospace;" id="replaceBarcode"></div>
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label style="display: block; margin-bottom: 4px; font-size: 13px;">이미지 URL *</label>
|
||||
<input type="text" id="replaceImageUrl" class="search-box" style="width: 100%;"
|
||||
placeholder="https://example.com/image.jpg">
|
||||
<div style="font-size: 11px; color: #6b7280; margin-top: 4px;">
|
||||
💡 이미지 우클릭 → "이미지 주소 복사"로 URL을 가져오세요
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<button class="btn btn-secondary" onclick="closeReplaceModal()">취소</button>
|
||||
<button class="btn btn-primary" onclick="submitReplace()">교체하기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 상세 모달 -->
|
||||
<div class="modal" id="detailModal">
|
||||
<div class="modal-content">
|
||||
@@ -478,6 +504,7 @@
|
||||
<span class="status ${item.status}">${getStatusText(item.status)}</span>
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary btn-sm" onclick="viewDetail('${item.barcode}')">상세</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="openReplaceModal('${item.barcode}', '${escapeHtml(item.product_name)}')">교체</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteImage('${item.barcode}')">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -712,11 +739,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 이미지 교체
|
||||
let replaceTargetBarcode = null;
|
||||
|
||||
function openReplaceModal(barcode, productName) {
|
||||
replaceTargetBarcode = barcode;
|
||||
document.getElementById('replaceProductName').textContent = productName || barcode;
|
||||
document.getElementById('replaceBarcode').textContent = barcode;
|
||||
document.getElementById('replaceImageUrl').value = '';
|
||||
document.getElementById('replaceModal').classList.add('show');
|
||||
document.getElementById('replaceImageUrl').focus();
|
||||
}
|
||||
|
||||
function closeReplaceModal() {
|
||||
document.getElementById('replaceModal').classList.remove('show');
|
||||
replaceTargetBarcode = null;
|
||||
}
|
||||
|
||||
async function submitReplace() {
|
||||
const imageUrl = document.getElementById('replaceImageUrl').value.trim();
|
||||
|
||||
if (!imageUrl) {
|
||||
showToast('이미지 URL을 입력하세요', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!imageUrl.startsWith('http')) {
|
||||
showToast('올바른 URL을 입력하세요 (http:// 또는 https://)', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
closeReplaceModal();
|
||||
showToast('이미지 다운로드 중...', 'info');
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/admin/product-images/${replaceTargetBarcode}/replace`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ image_url: imageUrl })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('✅ 이미지 교체 완료!', 'success');
|
||||
loadStats();
|
||||
loadImages();
|
||||
} else {
|
||||
showToast(data.error || '교체 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('오류: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ESC로 모달 닫기
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
closeManualCrawl();
|
||||
closeReplaceModal();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -728,6 +809,10 @@
|
||||
document.getElementById('manualCrawlModal').addEventListener('click', (e) => {
|
||||
if (e.target.id === 'manualCrawlModal') closeManualCrawl();
|
||||
});
|
||||
|
||||
document.getElementById('replaceModal').addEventListener('click', (e) => {
|
||||
if (e.target.id === 'replaceModal') closeReplaceModal();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user