diff --git a/backend/app.py b/backend/app.py index 4837549..6401a07 100644 --- a/backend/app.py +++ b/backend/app.py @@ -5927,6 +5927,101 @@ def api_replace_product_image(barcode): return jsonify({'success': False, 'error': str(e)}), 500 +@app.route('/api/admin/product-images//upload', methods=['POST']) +def api_upload_product_image(barcode): + """카메라 촬영 이미지 업로드 (base64 -> 1:1 크롭 -> 800x800 리사이즈)""" + import sqlite3 + import base64 + from PIL import Image + from io import BytesIO + + try: + data = request.get_json() or {} + image_data = (data.get('image_data') or '').strip() + product_name = data.get('product_name') or barcode + + if not image_data: + return jsonify({'success': False, 'error': '이미지 데이터 필요'}), 400 + + # data:image/...;base64, 접두사 제거 + if ',' in image_data: + image_data = image_data.split(',', 1)[1] + + try: + # base64 디코딩 + image_bytes = base64.b64decode(image_data) + img = Image.open(BytesIO(image_bytes)) + + # RGBA -> RGB 변환 (PNG 등 투명 배경 처리) + if img.mode == 'RGBA': + bg = Image.new('RGB', img.size, (255, 255, 255)) + bg.paste(img, mask=img.split()[3]) + img = bg + elif img.mode != 'RGB': + img = img.convert('RGB') + + # 1:1 중앙 크롭 (정사각형) + width, height = img.size + min_dim = min(width, height) + left = (width - min_dim) // 2 + top = (height - min_dim) // 2 + right = left + min_dim + bottom = top + min_dim + img = img.crop((left, top, right, bottom)) + + # 800x800 리사이즈 + target_size = 800 + img = img.resize((target_size, target_size), Image.LANCZOS) + + # base64 변환 (원본) + buffer = BytesIO() + img.save(buffer, format='JPEG', quality=90) + image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') + + # 썸네일 생성 (200x200) + thumb_size = 200 + thumb_img = img.resize((thumb_size, thumb_size), Image.LANCZOS) + thumb_buffer = BytesIO() + thumb_img.save(thumb_buffer, format='JPEG', quality=85) + thumbnail_base64 = base64.b64encode(thumb_buffer.getvalue()).decode('utf-8') + + except Exception as e: + return jsonify({'success': False, 'error': f'이미지 처리 실패: {str(e)}'}), 400 + + # SQLite 저장 + db_path = os.path.join(os.path.dirname(__file__), 'db', 'product_images.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 기존 레코드 확인 + cursor.execute("SELECT product_name, drug_code FROM product_images WHERE barcode = ?", (barcode,)) + existing = cursor.fetchone() + + if existing: + # 기존 레코드 있으면 이미지만 업데이트 + cursor.execute(""" + UPDATE product_images + SET image_base64 = ?, thumbnail_base64 = ?, image_url = NULL, + status = 'manual', error_message = NULL, updated_at = datetime('now') + WHERE barcode = ? + """, (image_base64, thumbnail_base64, barcode)) + else: + # 새 레코드 생성 + cursor.execute(""" + INSERT INTO product_images (barcode, product_name, image_base64, thumbnail_base64, status) + VALUES (?, ?, ?, ?, 'manual') + """, (barcode, product_name, image_base64, thumbnail_base64)) + + conn.commit() + conn.close() + + return jsonify({'success': True, 'message': '촬영 이미지 저장 완료'}) + + except Exception as e: + logging.error(f"이미지 업로드 오류: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + @app.route('/api/admin/product-images/stats') def api_product_images_stats(): """이미지 통계""" diff --git a/backend/templates/admin_product_images.html b/backend/templates/admin_product_images.html index b4875ea..7e79dc1 100644 --- a/backend/templates/admin_product_images.html +++ b/backend/templates/admin_product_images.html @@ -308,6 +308,30 @@ to { transform: translateX(0); opacity: 1; } } + /* 탭 스타일 */ + .tab-btn { + flex: 1; + padding: 10px 16px; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 8px; + color: #9ca3af; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s; + } + + .tab-btn:hover { + background: rgba(255,255,255,0.1); + } + + .tab-btn.active { + background: linear-gradient(135deg, #8b5cf6, #6366f1); + color: white; + border-color: transparent; + } + .empty-state { text-align: center; padding: 60px 20px; @@ -411,28 +435,80 @@ - +