- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
582 lines
21 KiB
JavaScript
582 lines
21 KiB
JavaScript
/**
|
|
* 이미지 처리 관련 기능을 담당하는 모듈
|
|
* 하단마감재 이미지 업데이트, 붙여넣기, 확대 등 모든 이미지 관련 기능을 처리
|
|
*/
|
|
|
|
const ImageHandler = (function() {
|
|
'use strict';
|
|
|
|
// 전역 변수
|
|
let bottombarImages = [];
|
|
let originalImage = null;
|
|
let hoverTimeout = null;
|
|
let miniSummaryTimeout = null;
|
|
|
|
/**
|
|
* 모듈 초기화
|
|
* @param {Array} images - bottombar.json에서 로드된 이미지 배열
|
|
*/
|
|
function init(images = []) {
|
|
bottombarImages = images;
|
|
console.log('ImageHandler 초기화됨:', bottombarImages.length, '개의 이미지 로드됨');
|
|
|
|
// 이벤트 리스너 등록
|
|
attachEventListeners();
|
|
}
|
|
|
|
/**
|
|
* 이벤트 리스너 등록
|
|
*/
|
|
function attachEventListeners() {
|
|
// 이미지 호버 확대 기능
|
|
$(document).on('mouseenter', '.search-zoomable-image', handleImageHover);
|
|
$(document).on('mouseleave', '.search-zoomable-image', handleImageLeave);
|
|
|
|
// 이미지 팝업 오버레이 이벤트
|
|
$('#imagePopupOverlay').on('mouseenter', function() {
|
|
clearTimeout(hoverTimeout);
|
|
});
|
|
|
|
$('#imagePopupOverlay').on('mouseleave', function() {
|
|
$('#imagePopupOverlay').fadeOut(200);
|
|
});
|
|
|
|
// 모달 클릭 시 닫기
|
|
$('#imageModal').on('click', function() {
|
|
$(this).fadeOut(300);
|
|
});
|
|
|
|
// ESC 키로 모달 닫기
|
|
$(document).on('keydown', function(e) {
|
|
if (e.key === 'Escape' && $('#imageModal').is(':visible')) {
|
|
$('#imageModal').fadeOut(300);
|
|
}
|
|
});
|
|
|
|
// 미니 합계표 팝업 기능
|
|
$(document).on('mouseenter', '.mini-summary-trigger', handleMiniSummaryHover);
|
|
$(document).on('mouseleave', '.mini-summary-trigger', handleMiniSummaryLeave);
|
|
|
|
// 미니 합계표 팝업 이벤트
|
|
$('#miniSummaryPopup').on('mouseenter', function() {
|
|
clearTimeout(miniSummaryTimeout);
|
|
});
|
|
|
|
$('#miniSummaryPopup').on('mouseleave', function() {
|
|
$(this).fadeOut(200);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 이미지 호버 시 확대 팝업 표시
|
|
*/
|
|
function handleImageHover(e) {
|
|
e.stopPropagation();
|
|
const imgSrc = $(this).attr('data-src') || $(this).attr('src');
|
|
|
|
clearTimeout(hoverTimeout);
|
|
|
|
const mouseX = e.clientX;
|
|
const mouseY = e.clientY;
|
|
|
|
const popupOffsetX = 20;
|
|
const popupOffsetY = 20;
|
|
const popupWidth = 300;
|
|
const popupHeight = 300;
|
|
const windowWidth = $(window).width();
|
|
const windowHeight = $(window).height();
|
|
|
|
let finalX = mouseX + popupOffsetX;
|
|
let finalY = mouseY + popupOffsetY;
|
|
|
|
if (finalX + popupWidth > windowWidth) {
|
|
finalX = mouseX - popupWidth - popupOffsetX;
|
|
}
|
|
|
|
if (finalY + popupHeight > windowHeight) {
|
|
finalY = mouseY - popupHeight - popupOffsetY;
|
|
}
|
|
|
|
$('#imagePopupOverlay').css({
|
|
'position': 'fixed',
|
|
'top': finalY + 'px',
|
|
'left': finalX + 'px',
|
|
'width': popupWidth + 'px',
|
|
'height': popupHeight + 'px',
|
|
'background': 'white',
|
|
'border': '2px solid #007bff',
|
|
'border-radius': '8px',
|
|
'box-shadow': '0 4px 20px rgba(0,0,0,0.3)',
|
|
'z-index': '10000',
|
|
'display': 'none'
|
|
});
|
|
|
|
$('#popupImage').attr('src', imgSrc);
|
|
$('#imagePopupOverlay').fadeIn(200);
|
|
}
|
|
|
|
/**
|
|
* 이미지 호버 종료 시 팝업 닫기
|
|
*/
|
|
function handleImageLeave(e) {
|
|
hoverTimeout = setTimeout(function() {
|
|
$('#imagePopupOverlay').fadeOut(200);
|
|
}, 100);
|
|
}
|
|
|
|
/**
|
|
* 미니 합계표 호버 시 팝업 표시
|
|
*/
|
|
function handleMiniSummaryHover(e) {
|
|
e.stopPropagation();
|
|
const json = $(this).attr('data-summary');
|
|
if (!json) return;
|
|
|
|
let data;
|
|
try {
|
|
data = JSON.parse(json);
|
|
} catch (err) {
|
|
return;
|
|
}
|
|
|
|
// 데이터 파싱 및 검증
|
|
let inputList = parseJsonData(data.inputList);
|
|
let bendingrateList = parseJsonData(data.bendingrateList);
|
|
let sumList = parseJsonData(data.sumList);
|
|
let colorList = parseJsonData(data.colorList);
|
|
let AList = parseJsonData(data.AList);
|
|
let calcAfterList = parseJsonData(data.calcAfterList);
|
|
|
|
// 미니 테이블 HTML 생성
|
|
const html = generateMiniSummaryTable(inputList, bendingrateList, sumList, colorList, AList, calcAfterList);
|
|
|
|
clearTimeout(miniSummaryTimeout);
|
|
|
|
// 팝업 위치 설정
|
|
const mouseX = e.clientX, mouseY = e.clientY;
|
|
const popupWidth = 400, popupHeight = 200;
|
|
let left = mouseX - popupWidth - 20, top = mouseY - 20;
|
|
|
|
if(left < 10) left = mouseX + 20;
|
|
if(top + popupHeight > window.innerHeight) top = mouseY - popupHeight - 20;
|
|
if(top < 10) top = 10;
|
|
|
|
$('#miniSummaryPopup').html(html).css({
|
|
'left': left + 'px',
|
|
'top': top + 'px',
|
|
'display': 'block'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 미니 합계표 호버 종료 시 팝업 닫기
|
|
*/
|
|
function handleMiniSummaryLeave(e) {
|
|
miniSummaryTimeout = setTimeout(function() {
|
|
$('#miniSummaryPopup').fadeOut(200);
|
|
}, 100);
|
|
}
|
|
|
|
/**
|
|
* JSON 데이터 파싱 및 검증
|
|
*/
|
|
function parseJsonData(data) {
|
|
if (Array.isArray(data)) {
|
|
return data;
|
|
}
|
|
if (typeof data === 'string') {
|
|
try {
|
|
const parsed = JSON.parse(data);
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
} catch (e) {
|
|
console.warn('JSON 파싱 실패:', e);
|
|
return [];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* 미니 합계표 HTML 생성
|
|
*/
|
|
function generateMiniSummaryTable(inputList, bendingrateList, sumList, colorList, AList, calcAfterList) {
|
|
let html = '<table class="table table-bordered table-sm mb-0">';
|
|
html += '<tr><th>번호</th>';
|
|
for(let i=1; i<=inputList.length; i++) html += `<td>${i}</td>`;
|
|
html += '</tr>';
|
|
html += '<tr><th>입력</th>';
|
|
inputList.forEach(v=>html+=`<td>${v}</td>`);
|
|
html += '</tr>';
|
|
html += '<tr><th>연신율</th>';
|
|
bendingrateList.forEach(v=>html+=`<td>${v}</td>`);
|
|
html += '</tr>';
|
|
html += '<tr><th>연신율계산 후</th>';
|
|
calcAfterList.forEach(v=>html+=`<td>${v}</td>`);
|
|
html += '</tr>';
|
|
html += '<tr><th>합계</th>';
|
|
sumList.forEach(v=>html+=`<td>${v}</td>`);
|
|
html += '</tr>';
|
|
html += '<tr><th>음영</th>';
|
|
colorList.forEach(v=>html+=`<td>${v?'음영':''}</td>`);
|
|
html += '</tr>';
|
|
html += '<tr><th>A각 표시</th>';
|
|
AList.forEach(v=>html+=`<td>${v?'A각':''}</td>`);
|
|
html += '</tr>';
|
|
html += '</table>';
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* 이미지 업데이트 함수
|
|
* @param {string} model - 모델명
|
|
* @param {string} checkType - 형태
|
|
* @param {string} finishingType - 마감
|
|
*/
|
|
function updateImage(model, checkType, finishingType) {
|
|
let imagePath = '';
|
|
|
|
console.log('ImageHandler.updateImage 호출:', {model, checkType, finishingType});
|
|
console.log('bottombarImages:', bottombarImages);
|
|
|
|
const searchKeyword = $('#search_keyword').val() || '';
|
|
console.log('search_keyword:', searchKeyword);
|
|
|
|
if (Array.isArray(bottombarImages)) {
|
|
let found = false;
|
|
|
|
// 경우의 수 1: search_keyword가 있고, JSON에도 search_keyword가 있는 경우 (가장 우선)
|
|
if(searchKeyword !== ''){
|
|
found = bottombarImages.find(function(item) {
|
|
return item.search_keyword && item.search_keyword === searchKeyword;
|
|
});
|
|
if (found) {
|
|
imagePath = found.image;
|
|
}
|
|
}
|
|
|
|
// 경우의 수 2: search_keyword로 찾지 못했거나 없는 경우, model_name + check_type + finishing_type으로 검색
|
|
if(!found && model && checkType && finishingType){
|
|
found = bottombarImages.find(function(item) {
|
|
return item.model_name && item.model_name === model &&
|
|
item.check_type && item.check_type === checkType &&
|
|
item.finishing_type && item.finishing_type === finishingType;
|
|
});
|
|
if (found) {
|
|
imagePath = found.image;
|
|
}
|
|
}
|
|
|
|
// 경우의 수 3: model_name + check_type으로만 검색 (finishing_type 무시)
|
|
if(!found && model && checkType){
|
|
found = bottombarImages.find(function(item) {
|
|
return item.model_name && item.model_name === model &&
|
|
item.check_type && item.check_type === checkType;
|
|
});
|
|
if (found) {
|
|
imagePath = found.image;
|
|
}
|
|
}
|
|
|
|
// 경우의 수 4: model_name만으로 검색 (check_type, finishing_type 무시)
|
|
if(!found && model){
|
|
found = bottombarImages.find(function(item) {
|
|
return item.model_name && item.model_name === model;
|
|
});
|
|
if (found) {
|
|
imagePath = found.image;
|
|
}
|
|
}
|
|
|
|
// 경우의 수 5: check_type + finishing_type으로 검색 (model_name 무시)
|
|
if(!found && checkType && finishingType){
|
|
found = bottombarImages.find(function(item) {
|
|
return item.check_type && item.check_type === checkType &&
|
|
item.finishing_type && item.finishing_type === finishingType;
|
|
});
|
|
if (found) {
|
|
imagePath = found.image;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 이미지 경로 적용
|
|
applyImageToElements(imagePath);
|
|
}
|
|
|
|
/**
|
|
* 이미지를 여러 요소에 적용
|
|
*/
|
|
function applyImageToElements(imagePath) {
|
|
// checkImage 엘리먼트에 적용
|
|
if ($('#checkImage').length) {
|
|
$('#checkImage').attr('src', imagePath);
|
|
}
|
|
|
|
// checkImageFlat 엘리먼트에 적용
|
|
if ($('#checkImageFlat').length) {
|
|
$('#checkImageFlat').attr('src', imagePath);
|
|
}
|
|
|
|
// previewContainer 엘리먼트에 적용
|
|
if ($('#previewContainer').length && imagePath) {
|
|
$('#previewContainer').empty();
|
|
const img = $('<img>').attr({
|
|
'src': imagePath,
|
|
'alt': '하단마감재 이미지',
|
|
'style': 'max-width: 100%; height: auto;'
|
|
});
|
|
$('#previewContainer').append(img);
|
|
console.log('updateImage 함수에서 previewContainer에 이미지 적용:', imagePath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 하단마감재 데이터를 모달창에 적용
|
|
*/
|
|
function applybottombarData(bottombarData) {
|
|
console.log('하단마감재 데이터 적용:', bottombarData);
|
|
|
|
// 대분류 라디오 버튼 설정
|
|
if (bottombarData.firstitem) {
|
|
$(`input[name="firstitem"][value="${bottombarData.firstitem}"]`).prop('checked', true);
|
|
}
|
|
|
|
// 인정/비인정 라디오 버튼 설정
|
|
if (bottombarData.UA) {
|
|
$(`input[name="model_UA"][value="${bottombarData.UA}"]`).prop('checked', true);
|
|
}
|
|
|
|
// 제품모델 선택
|
|
if (bottombarData.model_name) {
|
|
$('#model_name').val(bottombarData.model_name);
|
|
}
|
|
|
|
// 형태 라디오 버튼 설정
|
|
if (bottombarData.check_type) {
|
|
$(`input[name="check_type"][value="${bottombarData.check_type}"]`).prop('checked', true);
|
|
}
|
|
|
|
// 마감 선택
|
|
if (bottombarData.finishing_type) {
|
|
$('#finishing_type').val(bottombarData.finishing_type);
|
|
}
|
|
|
|
// 품목검색어 설정
|
|
if (bottombarData.search_keyword) {
|
|
$('#search_keyword').val(bottombarData.search_keyword);
|
|
}
|
|
|
|
// 이미지 업데이트
|
|
if (bottombarData.image) {
|
|
applyImageToElements(bottombarData.image);
|
|
}
|
|
|
|
// 모델 변경 이벤트 트리거 (이미지 업데이트를 위해)
|
|
if (bottombarData.model_name || bottombarData.check_type || bottombarData.finishing_type) {
|
|
setTimeout(function() {
|
|
$('#model_name, input[name="check_type"], #finishing_type').trigger('change');
|
|
}, 100);
|
|
}
|
|
|
|
console.log('하단마감재 데이터 적용 완료');
|
|
}
|
|
|
|
/**
|
|
* 이미지 붙여넣기 기능 초기화
|
|
*/
|
|
function initPasteImage() {
|
|
setTimeout(function() {
|
|
const previewContainer = document.getElementById('previewContainer');
|
|
if (previewContainer) {
|
|
// 중복 방지: 기존 이벤트 제거
|
|
const newPreviewContainer = previewContainer.cloneNode(true);
|
|
previewContainer.parentNode.replaceChild(newPreviewContainer, previewContainer);
|
|
|
|
const container = newPreviewContainer;
|
|
|
|
// 클릭 시 포커스
|
|
container.addEventListener('click', function() {
|
|
this.focus();
|
|
console.log('previewContainer 포커스됨');
|
|
});
|
|
|
|
// Ctrl+V 붙여넣기 이벤트
|
|
container.addEventListener('keydown', function(event) {
|
|
console.log('키 이벤트 발생:', event.key, 'Ctrl:', event.ctrlKey);
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
event.preventDefault();
|
|
console.log('Ctrl+V 감지됨');
|
|
|
|
// 조회모드가 아니면 붙여넣기 허용
|
|
if ($("#mode").val() !== 'view') {
|
|
console.log('붙여넣기 시도...');
|
|
handlePasteImage();
|
|
} else {
|
|
console.log('조회 모드에서는 붙여넣기 불가');
|
|
}
|
|
}
|
|
});
|
|
|
|
// 안내 메시지
|
|
if ($("#mode").val() !== 'view') {
|
|
container.style.cursor = 'pointer';
|
|
container.title = 'Ctrl+V로 이미지를 붙여넣을 수 있습니다';
|
|
console.log('previewContainer 이벤트 바인딩 완료');
|
|
}
|
|
} else {
|
|
console.error('previewContainer를 찾을 수 없습니다.');
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
/**
|
|
* 이미지 붙여넣기 처리
|
|
*/
|
|
function handlePasteImage() {
|
|
if (navigator.clipboard && navigator.clipboard.read) {
|
|
navigator.clipboard.read().then(data => {
|
|
console.log('클립보드 데이터:', data);
|
|
for (let item of data) {
|
|
if (item.types.includes('image/png') || item.types.includes('image/jpeg') || item.types.includes('image/jpg') || item.types.includes('image/gif') || item.types.includes('image/webp')) {
|
|
console.log('이미지 타입 발견:', item.types);
|
|
item.getType('image/png').then(blob => {
|
|
const url = URL.createObjectURL(blob);
|
|
pasteImageToPreviewContainer(url);
|
|
}).catch(() => {
|
|
item.getType('image/jpeg').then(blob => {
|
|
const url = URL.createObjectURL(blob);
|
|
pasteImageToPreviewContainer(url);
|
|
}).catch(() => {
|
|
item.getType('image/jpg').then(blob => {
|
|
const url = URL.createObjectURL(blob);
|
|
pasteImageToPreviewContainer(url);
|
|
});
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
alert('클립보드에 이미지가 없습니다.');
|
|
}).catch(err => {
|
|
console.error('클립보드 접근 오류:', err);
|
|
alert('클립보드 접근 오류: ' + err);
|
|
});
|
|
} else {
|
|
alert('이 브라우저는 이미지 붙여넣기를 지원하지 않습니다.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 붙여넣은 이미지를 표시하는 함수
|
|
*/
|
|
function pasteImageToPreviewContainer(imageUrl) {
|
|
console.log('이미지 붙여넣기 실행:', imageUrl);
|
|
|
|
const previewContainer = document.getElementById('previewContainer');
|
|
if (!previewContainer) {
|
|
console.error('previewContainer를 찾을 수 없습니다.');
|
|
return;
|
|
}
|
|
|
|
// previewContainer 내의 모든 이미지 제거 (기존 이미지 + 미러링 이미지 등)
|
|
const allImages = previewContainer.querySelectorAll('img');
|
|
allImages.forEach(function(img) {
|
|
img.remove();
|
|
console.log('이미지 제거됨:', img.id || 'id없음');
|
|
});
|
|
|
|
// 새 이미지 추가
|
|
const img = document.createElement('img');
|
|
img.src = imageUrl;
|
|
img.alt = '붙여넣은 이미지';
|
|
// 그리기 캔버스와 동일한 크기로 설정 (400px x 300px)
|
|
img.style.width = '400px';
|
|
img.style.height = '300px';
|
|
img.style.maxWidth = '100%';
|
|
img.style.maxHeight = '100%';
|
|
img.style.objectFit = 'contain';
|
|
img.className = 'img-fluid';
|
|
img.id = 'currentImage';
|
|
|
|
img.onload = function() {
|
|
console.log('이미지 로드 완료');
|
|
};
|
|
|
|
img.onerror = function() {
|
|
console.error('이미지 로드 실패');
|
|
};
|
|
|
|
previewContainer.appendChild(img);
|
|
console.log('새 이미지 추가됨');
|
|
|
|
// originalImage 업데이트
|
|
originalImage = imageUrl;
|
|
}
|
|
|
|
/**
|
|
* 이미지 찾기 함수
|
|
*/
|
|
function findImageByConditions(items, conditions) {
|
|
for (const item of items) {
|
|
let matched = true;
|
|
for (const [key, value] of Object.entries(conditions)) {
|
|
if (empty(item[key]) || item[key] !== value) {
|
|
matched = false;
|
|
break;
|
|
}
|
|
}
|
|
if (matched && !empty(item.image)) {
|
|
return `<img src='${item.image}' alt='이미지' style='width:150px;height:auto;'>`;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* 값이 비어있는지 확인
|
|
*/
|
|
function empty(value) {
|
|
return value === null || value === undefined || value === '';
|
|
}
|
|
|
|
/**
|
|
* 모듈 정리
|
|
*/
|
|
function cleanup() {
|
|
// 이벤트 리스너 제거
|
|
$(document).off('mouseenter mouseleave', '.search-zoomable-image');
|
|
$(document).off('mouseenter mouseleave', '.mini-summary-trigger');
|
|
$('#imagePopupOverlay').off('mouseenter mouseleave');
|
|
$('#imageModal').off('click');
|
|
$('#miniSummaryPopup').off('mouseenter mouseleave');
|
|
|
|
// 타임아웃 클리어
|
|
if (hoverTimeout) {
|
|
clearTimeout(hoverTimeout);
|
|
hoverTimeout = null;
|
|
}
|
|
if (miniSummaryTimeout) {
|
|
clearTimeout(miniSummaryTimeout);
|
|
miniSummaryTimeout = null;
|
|
}
|
|
|
|
console.log('ImageHandler 정리 완료');
|
|
}
|
|
|
|
// 공개 API
|
|
return {
|
|
init: init,
|
|
updateImage: updateImage,
|
|
applybottombarData: applybottombarData,
|
|
initPasteImage: initPasteImage,
|
|
pasteImageToPreviewContainer: pasteImageToPreviewContainer,
|
|
findImageByConditions: findImageByConditions,
|
|
cleanup: cleanup
|
|
};
|
|
})();
|
|
|
|
// 전역 함수로 노출 (기존 코드와의 호환성을 위해)
|
|
window.pasteImageToPreviewContainer = ImageHandler.pasteImageToPreviewContainer;
|
|
window.updateImage = ImageHandler.updateImage;
|
|
window.applybottombarData = ImageHandler.applybottombarData;
|
|
window.findImageByConditions = ImageHandler.findImageByConditions;
|