feat: 동물약 APC 이미지 지원 (CD_ITEM_UNIT_MEMBER 연동)
This commit is contained in:
@@ -48,11 +48,224 @@
|
||||
|
||||
/* ── 컨텐츠 ── */
|
||||
.content {
|
||||
max-width: 1200px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 20px 60px;
|
||||
}
|
||||
|
||||
/* ── 플로팅 챗봇 ── */
|
||||
.chatbot-panel {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
bottom: 90px;
|
||||
width: 370px;
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 8px 40px rgba(0,0,0,0.15);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
height: 520px;
|
||||
max-height: calc(100vh - 120px);
|
||||
z-index: 998;
|
||||
animation: chatSlideUp 0.3s ease;
|
||||
}
|
||||
.chatbot-panel.open {
|
||||
display: flex;
|
||||
}
|
||||
@keyframes chatSlideUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.chatbot-header {
|
||||
padding: 16px 20px;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
color: #fff;
|
||||
}
|
||||
.chatbot-header h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.chatbot-header p {
|
||||
font-size: 12px;
|
||||
opacity: 0.85;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.chatbot-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.chat-message {
|
||||
max-width: 85%;
|
||||
padding: 12px 16px;
|
||||
border-radius: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
.chat-message.user {
|
||||
background: #8b5cf6;
|
||||
color: #fff;
|
||||
align-self: flex-end;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.chat-message.assistant {
|
||||
background: #f1f5f9;
|
||||
color: #334155;
|
||||
align-self: flex-start;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.chat-message.system {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
align-self: center;
|
||||
font-size: 12px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.chat-message .products-mentioned {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed #cbd5e1;
|
||||
}
|
||||
.chat-message .product-chip {
|
||||
display: inline-block;
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
margin: 2px 4px 2px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.chat-message .product-chip:hover {
|
||||
background: #059669;
|
||||
}
|
||||
.chatbot-input {
|
||||
padding: 16px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.chatbot-input input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 24px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.chatbot-input input:focus {
|
||||
outline: none;
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
.chatbot-input button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.chatbot-input button:hover { background: #059669; }
|
||||
.chatbot-input button:disabled { background: #94a3b8; cursor: not-allowed; }
|
||||
.chatbot-suggestions {
|
||||
padding: 12px 16px;
|
||||
background: #f8fafc;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.suggestion-chip {
|
||||
background: #e0e7ff;
|
||||
color: #4338ca;
|
||||
padding: 6px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.suggestion-chip:hover {
|
||||
background: #c7d2fe;
|
||||
}
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 12px 16px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.typing-indicator span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #94a3b8;
|
||||
border-radius: 50%;
|
||||
animation: typing 1.4s infinite;
|
||||
}
|
||||
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
||||
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
||||
@keyframes typing {
|
||||
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
||||
30% { transform: translateY(-6px); opacity: 1; }
|
||||
}
|
||||
|
||||
/* ── 챗봇 토글 버튼 (항상 표시) ── */
|
||||
.chatbot-toggle {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 20px rgba(16, 185, 129, 0.4);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.chatbot-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 28px rgba(16, 185, 129, 0.5);
|
||||
}
|
||||
.chatbot-toggle.active {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
box-shadow: 0 4px 20px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
/* 모바일 */
|
||||
@media (max-width: 640px) {
|
||||
.chatbot-panel {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 75vh;
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
.chatbot-toggle { bottom: 16px; right: 16px; }
|
||||
}
|
||||
|
||||
/* ── 검색 영역 ── */
|
||||
.search-section {
|
||||
background: #fff;
|
||||
@@ -344,7 +557,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h1>🔍 제품 검색</h1>
|
||||
<p>전체 제품 검색 · QR 라벨 인쇄</p>
|
||||
<p>전체 제품 검색 · QR 라벨 인쇄 · 🐾 동물약 AI 상담</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
@@ -395,8 +608,36 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div><!-- /.content -->
|
||||
|
||||
<!-- 동물약 챗봇 패널 -->
|
||||
<div class="chatbot-panel" id="chatbotPanel">
|
||||
<div class="chatbot-header">
|
||||
<h2>🐾 동물약 AI 상담</h2>
|
||||
<p>심장사상충, 외부기생충, 구충제 등 무엇이든 물어보세요</p>
|
||||
</div>
|
||||
<div class="chatbot-suggestions">
|
||||
<button class="suggestion-chip" onclick="sendSuggestion('강아지 심장사상충 약 추천해줘')">🐕 심장사상충약</button>
|
||||
<button class="suggestion-chip" onclick="sendSuggestion('넥스가드랑 브라벡토 차이점')">넥스가드 vs 브라벡토</button>
|
||||
<button class="suggestion-chip" onclick="sendSuggestion('고양이 벼룩 약 뭐가 좋아?')">🐱 고양이 벼룩약</button>
|
||||
<button class="suggestion-chip" onclick="sendSuggestion('강아지 구충제 추천')">🪱 구충제</button>
|
||||
</div>
|
||||
<div class="chatbot-messages" id="chatMessages">
|
||||
<div class="chat-message assistant">
|
||||
안녕하세요! 🐾 동물약 상담 AI입니다.<br><br>
|
||||
반려동물의 <strong>심장사상충 예방</strong>, <strong>벼룩/진드기 예방</strong>, <strong>구충제</strong> 등에 대해 무엇이든 물어보세요!
|
||||
</div>
|
||||
</div>
|
||||
<div class="chatbot-input">
|
||||
<input type="text" id="chatInput" placeholder="예: 5kg 강아지 심장사상충 약 추천해줘"
|
||||
onkeypress="if(event.key==='Enter')sendChat()">
|
||||
<button onclick="sendChat()" id="chatSendBtn">➤</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 챗봇 토글 버튼 -->
|
||||
<button class="chatbot-toggle" id="chatbotToggle" onclick="toggleChatbot()">🐾</button>
|
||||
|
||||
<!-- QR 인쇄 모달 -->
|
||||
<div class="modal-overlay" id="qrModal" onclick="if(event.target===this)closeQRModal()">
|
||||
<div class="modal-box">
|
||||
@@ -614,6 +855,171 @@
|
||||
|
||||
// 페이지 로드 시 검색창 포커스
|
||||
document.getElementById('searchInput').focus();
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// 동물약 챗봇
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
let chatHistory = [];
|
||||
let isChatLoading = false;
|
||||
|
||||
function toggleChatbot() {
|
||||
const panel = document.getElementById('chatbotPanel');
|
||||
const btn = document.getElementById('chatbotToggle');
|
||||
const isOpen = panel.classList.toggle('open');
|
||||
btn.classList.toggle('active', isOpen);
|
||||
btn.innerHTML = isOpen ? '✕' : '🐾';
|
||||
if (isOpen) {
|
||||
document.getElementById('chatInput').focus();
|
||||
}
|
||||
}
|
||||
|
||||
function sendSuggestion(text) {
|
||||
document.getElementById('chatInput').value = text;
|
||||
sendChat();
|
||||
}
|
||||
|
||||
async function sendChat() {
|
||||
const input = document.getElementById('chatInput');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message || isChatLoading) return;
|
||||
|
||||
// 사용자 메시지 표시
|
||||
addChatMessage('user', message);
|
||||
input.value = '';
|
||||
|
||||
// 히스토리에 추가
|
||||
chatHistory.push({ role: 'user', content: message });
|
||||
|
||||
// 로딩 표시
|
||||
isChatLoading = true;
|
||||
document.getElementById('chatSendBtn').disabled = true;
|
||||
showTypingIndicator();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/animal-chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ messages: chatHistory })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
hideTypingIndicator();
|
||||
|
||||
if (data.success) {
|
||||
// AI 응답 표시
|
||||
addChatMessage('assistant', data.message, data.products);
|
||||
|
||||
// 히스토리에 추가
|
||||
chatHistory.push({ role: 'assistant', content: data.message });
|
||||
|
||||
// 히스토리 길이 제한 (최근 20개)
|
||||
if (chatHistory.length > 20) {
|
||||
chatHistory = chatHistory.slice(-20);
|
||||
}
|
||||
} else {
|
||||
addChatMessage('system', '⚠️ ' + (data.message || '오류가 발생했습니다'));
|
||||
}
|
||||
} catch (error) {
|
||||
hideTypingIndicator();
|
||||
addChatMessage('system', '⚠️ 네트워크 오류가 발생했습니다');
|
||||
}
|
||||
|
||||
isChatLoading = false;
|
||||
document.getElementById('chatSendBtn').disabled = false;
|
||||
}
|
||||
|
||||
function addChatMessage(role, content, products) {
|
||||
const container = document.getElementById('chatMessages');
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.className = `chat-message ${role}`;
|
||||
|
||||
// 줄바꿈 처리
|
||||
let htmlContent = escapeHtml(content).replace(/\n/g, '<br>');
|
||||
|
||||
// 마크다운 굵게 처리
|
||||
htmlContent = htmlContent.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
msgDiv.innerHTML = htmlContent;
|
||||
|
||||
// 언급된 제품 표시 (이미지 포함)
|
||||
if (products && products.length > 0) {
|
||||
const productsDiv = document.createElement('div');
|
||||
productsDiv.className = 'products-mentioned';
|
||||
productsDiv.innerHTML = '<small style="color:#64748b;">📦 관련 제품:</small>';
|
||||
|
||||
const productsGrid = document.createElement('div');
|
||||
productsGrid.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;';
|
||||
|
||||
products.forEach(p => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'product-card-mini';
|
||||
card.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px;background:#f8fafc;border-radius:8px;cursor:pointer;border:1px solid #e2e8f0;';
|
||||
card.onclick = () => searchProductFromChat(p.name);
|
||||
|
||||
// 이미지 컨테이너
|
||||
const imgContainer = document.createElement('div');
|
||||
imgContainer.style.cssText = 'width:40px;height:40px;flex-shrink:0;';
|
||||
|
||||
if (p.image_url) {
|
||||
const img = document.createElement('img');
|
||||
img.style.cssText = 'width:40px;height:40px;object-fit:cover;border-radius:4px;background:#e2e8f0;';
|
||||
img.src = p.image_url;
|
||||
img.alt = p.name;
|
||||
img.onerror = function() {
|
||||
// 이미지 로드 실패 시 아이콘으로 대체
|
||||
imgContainer.innerHTML = '<div style="width:40px;height:40px;background:#f1f5f9;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:20px;">💊</div>';
|
||||
};
|
||||
imgContainer.appendChild(img);
|
||||
} else {
|
||||
// 이미지 없으면 아이콘
|
||||
imgContainer.innerHTML = '<div style="width:40px;height:40px;background:#f1f5f9;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:20px;">💊</div>';
|
||||
}
|
||||
|
||||
// 텍스트
|
||||
const textDiv = document.createElement('div');
|
||||
textDiv.innerHTML = `<div style="font-size:13px;font-weight:500;color:#334155;">${p.name}</div><div style="font-size:12px;color:#10b981;">${formatPrice(p.price)}</div>`;
|
||||
|
||||
card.appendChild(imgContainer);
|
||||
card.appendChild(textDiv);
|
||||
productsGrid.appendChild(card);
|
||||
});
|
||||
|
||||
productsDiv.appendChild(productsGrid);
|
||||
msgDiv.appendChild(productsDiv);
|
||||
}
|
||||
|
||||
container.appendChild(msgDiv);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function showTypingIndicator() {
|
||||
const container = document.getElementById('chatMessages');
|
||||
const indicator = document.createElement('div');
|
||||
indicator.className = 'typing-indicator';
|
||||
indicator.id = 'typingIndicator';
|
||||
indicator.innerHTML = '<span></span><span></span><span></span>';
|
||||
container.appendChild(indicator);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function hideTypingIndicator() {
|
||||
const indicator = document.getElementById('typingIndicator');
|
||||
if (indicator) indicator.remove();
|
||||
}
|
||||
|
||||
function searchProductFromChat(productName) {
|
||||
// 챗봇에서 제품 클릭 시 검색창에 입력하고 검색
|
||||
document.getElementById('searchInput').value = productName;
|
||||
document.getElementById('animalOnly').checked = true;
|
||||
searchProducts();
|
||||
|
||||
// 모바일에서 챗봇 닫기
|
||||
if (window.innerWidth <= 1100) {
|
||||
document.getElementById('chatbotPanel').classList.remove('open');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user