feat: 제품 이미지 카메라 촬영 기능 추가
- HTML5 getUserMedia로 카메라 촬영 지원 (모바일 후면 카메라 기본) - 1:1 가이드 박스 UI로 정사각형 크롭 안내 - 백엔드: PIL로 800x800 리사이즈 + 썸네일 생성 - 기존 URL 교체 기능과 탭 방식으로 통합 버그 수정: - closeReplaceModal() 호출 전 변수 복사로 null 전송 문제 해결 - None 값 방어 코드 추가 Docs: TROUBLESHOOTING-CAMERA-UPLOAD.md 추가
This commit is contained in:
@@ -5927,6 +5927,101 @@ def api_replace_product_image(barcode):
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/admin/product-images/<barcode>/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():
|
||||
"""이미지 통계"""
|
||||
|
||||
Reference in New Issue
Block a user