tui grid 드롭다운 버튼 기능 추가

dev
박성영 4 months ago
parent 069d7c3fd1
commit 72bc009be7

@ -13,6 +13,8 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* packageName : egovframework.config
* fileName : ConfigController
@ -49,4 +51,25 @@ public class ConfigController {
.contentType(MediaType.parseMediaType("application/javascript"))
.body(js);
}
/**
*
* .do, .ajax URL
*
* @param request HttpServletRequest
* @return
*/
@Operation(summary = "컨택스트 패스 설정값 자바스크립트 반환", description = "컨택스트 패스 설정값을 자바스크립트 코드로 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "자바스크립트 반환 성공")
})
@GetMapping(value = "/context-path.do", produces = "application/javascript")
public ResponseEntity<String> getContextPathJs(HttpServletRequest request) {
// 컨택스트 패스를 가져와서 JavaScript 변수로 설정
String contextPath = request.getContextPath();
String js = "var contextPath = '" + contextPath + "';";
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/javascript"))
.body(js);
}
}

@ -79,7 +79,8 @@ interceptor:
- /login/** #로그인
- / #루트 페이지
- /.well-known/** #개발 툴
- /common/** #공통 조회, 코드 등
#- /common/** #공통 조회, 코드 등
- /common/config/** #공통 설정 조회
- /resources/** #web server 배포
- /css/** #css
- /img/** #images

@ -49,6 +49,7 @@
<script type="text/javascript" src="<c:url value='/js/bootstrap.bundle.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/popper/1.16.1/popper.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/bootstrap-datepicker.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/moment.js' />"></script>
<!-- Plugins -->
<%--<script type="text/javascript" src="<c:url value='/js/jquery.treeview.js' />"></script>--%>
<%-- 사이드바 제거로 인해 주석처리: <script type="text/javascript" src="<c:url value='/plugins/simplebar/simplebar.min.js' />"></script> --%>
@ -70,13 +71,12 @@
<%-- XIT CUSTOM --%>
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-common.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/xit/common_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-tui-grid.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-common.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-validation.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/menu-path.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/common_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/datatables_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/moment.js' />"></script>
<!-- 파일 업로드 스타일 -->
<link rel="stylesheet" href="<c:url value="/css/xit-multi-fileupload.css"/>" />

@ -211,11 +211,24 @@ $(document).ready(function() {
}
try {
var $featherElements = $('[data-feather]');
console.log('[DEBUG_LOG] 발견된 feather 아이콘 요소 수:', $featherElements.length);
if ($featherElements.length > 0) {
// 모든 data-feather 속성을 가진 요소에 아이콘 적용
feather.replace();
console.log('[DEBUG_LOG] Feather 아이콘 초기화 완료');
} else {
console.log('[DEBUG_LOG] feather 아이콘 요소를 찾을 수 없습니다.');
}
/*
// DOM이 준비될 때까지 잠시 기다린 후 실행
setTimeout(function() {
var $featherElements = $('[data-feather]');
console.log('[DEBUG_LOG] 발견된 feather 아이콘 요소 수:', $featherElements.length);
if ($featherElements.length > 0) {
// 모든 data-feather 속성을 가진 요소에 아이콘 적용
feather.replace();
@ -223,7 +236,8 @@ $(document).ready(function() {
} else {
console.log('[DEBUG_LOG] feather 아이콘 요소를 찾을 수 없습니다.');
}
}, 100);
}, 30);
*/
} catch (error) {
console.error('[DEBUG_LOG] Feather 아이콘 초기화 오류:', error);
}

@ -180,7 +180,7 @@
// 기본 설정
gridConfig.setOptDataSource(dataSource); // 데이터소스 연결
gridConfig.setOptGridId('grid'); // 그리드를 출력할 Element ID
gridConfig.setOptGridHeight('auto'); // 그리드 높이(단위: px)
gridConfig.setOptGridHeight(470); // 그리드 높이(단위: px)
gridConfig.setOptRowHeight(30); // 그리드 행 높이(단위: px)
gridConfig.setOptRowHeaderType('rowNum'); // 행 첫번째 셀 타입(rowNum: 순번, checkbox: 체크박스, '': 출력 안함)
gridConfig.setOptUseClientSort(false); // 서버사이드 정렬 false
@ -204,7 +204,7 @@
{
header: '단속현황',
name: 'dansokhyeonhwang',
childNames: ['mngYy', 'regstrMngNo', 'mngNo', 'bdongName', 'posAddr', 'addrEtc']
childNames: ['mngYy', 'regstrMngNo', 'mngNo', 'bdongName', 'posAddr', 'addrEtc', 'actions']
},
]
}
@ -252,6 +252,25 @@
width: 150,
align: 'left'
},
{
header: '작업',
name: 'actions',
width: 80,
align: 'center',
sortable: false,
resizable: false,
formatter: function(props) {
// 개발자가 직접 커스텀 메뉴 아이템 설정 (새로운 방식 사용)
return XitTuiGridDropdownMenu.createMenuHtml(props, {
menuItems: [
{ text: '보기', action: 'view', class: 'js-row-view' },
{ text: '상세정보', action: 'detail', class: 'js-row-detail' },
{ text: '수정', action: 'edit', class: 'js-row-edit' },
{ text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
]
});
}
},
];
},
@ -294,6 +313,11 @@
// 그리드 테마 설정
Grid.applyTheme('striped');
// 드롭다운 메뉴가 잘리지 않도록 overflow 설정 조정
$('#grid').css('overflow', 'visible');
$('.containers').css('overflow', 'visible');
$('.gs_booking').css('overflow', 'visible');
// 그리드 이벤트 설정
this.gridBindEvents();
},
@ -310,6 +334,42 @@
$("#currentPage").text(responseObj.data.pagination.page);
$("#totalPages").text(responseObj.data.pagination.totalPages);
});
// XIT 공통 컴포넌트를 사용하여 드롭다운 메뉴 이벤트 바인딩
var menuCallbacks = {
view: function(row, rowKey) {
Ma30List.openViewModal(row);
},
detail: function(row, rowKey) {
// 새로 추가된 상세정보 메뉴 액션
Ma30List.openDetailModal(row);
},
edit: function(row, rowKey) {
Ma30List.openEditPage(row);
},
delete: function(row, rowKey) {
if (!confirm('정말 삭제하시겠습니까?')) return;
// 삭제 요청 처리 (실제 구현시 적절한 URL로 변경 필요)
$.ajax({
url: '<c:url value="/ma30/delete.ajax"/>',
type: 'POST',
data: { regstrMngNo: row.regstrMngNo },
success: function(res) {
alert('삭제되었습니다.');
// 성공 시 현재 페이지 유지하여 새로고침
var page = self.instance.getPagination().getCurrentPage();
self.instance.readData(page);
},
error: function() {
alert('삭제 중 오류가 발생했습니다.');
}
});
}
};
// 공통 컴포넌트로 이벤트 바인딩
XitTuiGridDropdownMenu.bindEvents('#grid', menuCallbacks, this.instance);
}
},
@ -365,6 +425,84 @@
// 이벤트 핸들러 설정
this.eventBindEvents();
},
/**
* 상세보기 모달 열기
* @param {Object} row - 선택된 행 데이터
*/
openViewModal: function(row) {
// 상세 정보를 표시할 모달 구현
// 프로젝트 공통 모달 규칙에 맞춰 구현
console.log('상세보기:', row);
alert('상세보기 기능입니다.\n관리번호: ' + row.regstrMngNo + '\n연도: ' + row.mngYy);
// 실제 구현 시 아래와 같이 모달을 열어주세요
// 예시: Xit 공통 모달 활용
/*
var modalConfig = {
title: '단속자료 상세보기',
url: '<c:url value="/ma30/view.do"/>',
params: { regstrMngNo: row.regstrMngNo },
size: 'lg' // 모달 크기
};
XitModal.open(modalConfig);
*/
},
/**
* 상세정보 모달 열기 (새로 추가된 커스텀 메뉴 예제)
* @param {Object} row - 선택된 행 데이터
*/
openDetailModal: function(row) {
// 상세 정보를 더 자세히 표시하는 모달 (기본 보기와 다른 형태)
console.log('상세정보:', row);
var detailInfo =
'=== 상세 단속자료 정보 ===\n' +
'관리번호: ' + (row.regstrMngNo || '없음') + '\n' +
'연도: ' + (row.mngYy || '없음') + '\n' +
'연번: ' + (row.mngNo || '없음') + '\n' +
'동: ' + (row.bdongName || '없음') + '\n' +
'지번: ' + (row.posAddr || '없음') + '\n' +
'상세주소: ' + (row.addrEtc || '없음');
alert(detailInfo);
// 실제 구현 시 더 상세한 모달을 만들 수 있음
/*
var modalConfig = {
title: '단속자료 상세정보',
url: '<c:url value="/ma30/detailView.do"/>',
params: { regstrMngNo: row.regstrMngNo },
size: 'xl' // 큰 모달로 더 많은 정보 표시
};
XitModal.open(modalConfig);
*/
},
/**
* 수정 페이지 이동
* @param {Object} row - 선택된 행 데이터
*/
openEditPage: function(row) {
// 수정 페이지로 이동 또는 수정 모달 열기
console.log('수정:', row);
// 페이지 이동 방식
if (confirm('수정 페이지로 이동하시겠습니까?')) {
location.href = '<c:url value="/ma30/edit.do"/>' + '?regstrMngNo=' + encodeURIComponent(row.regstrMngNo);
}
// 모달 방식으로 구현하려면 아래와 같이 사용
/*
var modalConfig = {
title: '단속자료 수정',
url: '<c:url value="/ma30/editForm.do"/>',
params: { regstrMngNo: row.regstrMngNo },
size: 'xl' // 모달 크기
};
XitModal.open(modalConfig);
*/
}
};

File diff suppressed because it is too large Load Diff

@ -1,74 +0,0 @@
$.fn.dataTable.render.moment = function ( fmt ) {
return function(d, type, row, meta) {
if ( !d ) return '';
return moment(d).format(fmt);
}
};
$.fn.dataTable.render.comma = function ( val ) {
return function(d, type, row, meta) {
var str = '';
if ( !d ) return 0;
str += numberWithCommas(d);
if ( val ) {
str += val;
}
return str;
}
};
$.fn.dataTable.render.numbering = function ( orderType, totalCount ) {
return function(d, type, row, meta) {
var num = 0;
if ( !orderType ) orderType = 'asc';
console.log('totalCount : ', totalCount);
if ( orderType == 'asc' ) {
num = meta.row + 1;
} else if ( orderType == 'desc' ) {
num = totalCount - meta.row;
} else {
num = meta.row + 1;
}
return num;
}
};
var numbering = function(pagingInfo, meta) {
var no = 0;
var currentPageNo = 0;
var recordCountPerPage = 0;
var totalRecordCount = 0;
if ( pagingInfo ) {
currentPageNo = nvl(pagingInfo.currentPageNo, 0);
recordCountPerPage = nvl(pagingInfo.recordCountPerPage, 0);
totalRecordCount = nvl(pagingInfo.totalRecordCount, 0);
}
no = totalRecordCount - (((currentPageNo - 1) * recordCountPerPage) + meta.row);
var rtnHtml;
rtnHtml = numberWithCommas(no);
return rtnHtml;
}
var datatablesNumbering = function(meta, orderType, totalCount) {
var num = 0;
if ( !orderType ) orderType = 'asc';
if ( orderType == 'asc' ) {
num = meta.row + 1;
} else if ( orderType == 'desc' ) {
num = totalCount - meta.row;
} else {
num = meta.row + 1;
}
return numberWithCommas(num);
}

@ -481,3 +481,48 @@ var buildUrlWithParamCondAndId = function(paramCond, idName, idValue, baseUrl) {
return addUrlParam(idName, idValue, urlWithParams);
};
/**
* URL에 컨택스트 패스를 적용하는 유틸리티 함수
* 상대 URL을 받아서 컨택스트 패스가 적용된 절대 URL을 반환
*
* @param {string} url - 상대 URL (: '/user/list.do', '/bbs/user/post/comment/register.ajax')
* @returns {string} 컨택스트 패스가 적용된 URL
*/
var buildContextUrl = function(url) {
// contextPath 변수가 정의되지 않은 경우 기본값 사용
if (typeof contextPath === 'undefined') {
console.warn('contextPath 변수가 정의되지 않았습니다. 기본값("")을 사용합니다.');
return url;
}
// URL이 비어있거나 null인 경우 처리
if (!url || url.trim() === '') {
return url;
}
// 이미 절대 URL인 경우 (http:// 또는 https://로 시작) 그대로 반환
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
// 상대 URL인 경우 컨택스트 패스 적용
// URL이 '/'로 시작하지 않는 경우 '/' 추가
var normalizedUrl = url.startsWith('/') ? url : '/' + url;
// 컨택스트 패스가 빈 문자열이거나 '/'인 경우 처리
if (!contextPath || contextPath === '/') {
return normalizedUrl;
}
// 컨택스트 패스와 URL 결합 (중복된 '/' 제거)
var result = contextPath;
if (!contextPath.endsWith('/') && !normalizedUrl.startsWith('/')) {
result += '/';
} else if (contextPath.endsWith('/') && normalizedUrl.startsWith('/')) {
normalizedUrl = normalizedUrl.substring(1);
}
result += normalizedUrl;
return result;
};

File diff suppressed because it is too large Load Diff

@ -1,246 +0,0 @@
/**
* 댓글 관리 모듈
* 댓글 등록, 수정, 삭제 답글 기능을 제공합니다.
*/
(function(window, $) {
'use strict';
/**
* 댓글 관리 네임스페이스
*/
var CommentManager = {
/**
* 이벤트 핸들러 설정
*/
eventBindEvents: function() {
var self = this;
// 댓글 등록 버튼 클릭 이벤트
$('#btnCommentRegister').on('click', function() {
self.registerComment();
});
// 댓글/답글 수정 버튼 클릭 이벤트 (수정)
$(document).on('click', '.xit-comment-edit', function() {
var $commentItem = $(this).closest('.xit-comment-item');
var $commentBody = $commentItem.find('> .xit-comment-body'); // 직접 자식 요소만 선택
var $editForm = $commentItem.find('> .xit-comment-edit-form'); // 직접 자식 요소만 선택
$commentBody.hide();
$editForm.show();
});
// 댓글/답글 수정 취소 버튼 클릭 이벤트 (수정)
$(document).on('click', '.xit-btn-cancel', function() {
var $commentItem = $(this).closest('.xit-comment-item');
var $commentBody = $commentItem.find('> .xit-comment-body'); // 직접 자식 요소만 선택
var $editForm = $commentItem.find('> .xit-comment-edit-form'); // 직접 자식 요소만 선택
var $replyForm = $commentItem.find('> .xit-comment-reply-form'); // 직접 자식 요소만 선택
$editForm.hide();
$replyForm.hide();
$commentBody.show();
});
// 댓글/답글 수정 저장 버튼 클릭 이벤트 (수정)
$(document).on('click', '.xit-btn-register', function() {
var $commentItem = $(this).closest('.xit-comment-item');
var $editForm = $commentItem.find('.xit-comment-edit-form');
// 수정 폼이 있는 경우에만 수정 처리
if ($editForm.length > 0 && $editForm.is(':visible')) {
var commentId = $commentItem.data('comment-id');
var content = $commentItem.find('.xit-comment-edit-textarea').val();
self.updateComment(commentId, content);
}
// 답글 폼이 있는 경우 답글 등록 처리
else if ($commentItem.find('.xit-comment-reply-form').is(':visible')) {
var parentCommentId = $commentItem.data('comment-id');
var content = $commentItem.find('.xit-comment-reply-textarea').val();
self.registerChildComment(parentCommentId, content);
}
});
// 댓글/답글 삭제 버튼 클릭 이벤트
$(document).on('click', '.xit-comment-delete', function() {
var $commentItem = $(this).closest('.xit-comment-item');
var commentId = $commentItem.data('comment-id');
self.deleteComment(commentId);
});
// 댓글 답글 버튼 클릭 이벤트
$(document).on('click', '.xit-comment-reply', function() {
var $commentItem = $(this).closest('.xit-comment-item');
$commentItem.find('.xit-comment-reply-form').toggle();
});
},
/**
* URL 경로 생성 (관리자/사용자 구분)
*/
getUrlPrefix: function() {
// 현재 경로에서 관리자/사용자 구분
var currentPath = window.location.pathname;
return currentPath.includes('/bbs/manage/') ? '/bbs/manage/post/' : '/bbs/user/post/';
},
/**
* 댓글 등록
*/
registerComment: function() {
var self = this;
var content = $('#commentContent').val();
if (!content) {
alert('댓글 내용을 입력해주세요.');
return;
}
// 페이지에서 설정된 데이터 가져오기
var bbsId = $('#commentArea').data('bbs-id');
var postId = $('#commentArea').data('post-id');
var urlPrefix = this.getUrlPrefix();
$.ajax({
url: urlPrefix + bbsId + '/comment/register.ajax',
type: 'POST',
data: {
postId: postId,
content: content
},
success: function(response) {
if (response.result) {
alert('댓글이 성공적으로 등록되었습니다.');
location.reload();
} else {
alert(response.message || '댓글 등록에 실패했습니다.');
}
},
error: function(xhr, status, error) {
// 에러 처리는 xit-common.js의 ajaxError에서 처리됨
}
});
},
/**
* 댓글 수정
*/
updateComment: function(commentId, content) {
var self = this;
if (!content) {
alert('댓글 내용을 입력해주세요.');
return;
}
// 페이지에서 설정된 데이터 가져오기
var bbsId = $('#commentArea').data('bbs-id');
var urlPrefix = this.getUrlPrefix();
$.ajax({
url: urlPrefix + bbsId + '/comment/edit.ajax',
type: 'POST',
data: {
commentId: commentId,
content: content
},
success: function(response) {
if (response.result) {
alert('댓글이 성공적으로 수정되었습니다.');
location.reload();
} else {
alert(response.message || '댓글 수정에 실패했습니다.');
}
},
error: function(xhr, status, error) {
// 에러 처리는 xit-common.js의 ajaxError에서 처리됨
}
});
},
/**
* 댓글 삭제
*/
deleteComment: function(commentId) {
var self = this;
if (confirm('댓글을 삭제하시겠습니까?')) {
// 페이지에서 설정된 데이터 가져오기
var bbsId = $('#commentArea').data('bbs-id');
var urlPrefix = this.getUrlPrefix();
$.ajax({
url: urlPrefix + bbsId + '/comment/delete.ajax',
type: 'POST',
data: { commentId: commentId },
success: function(response) {
if (response.result) {
alert('댓글이 성공적으로 삭제되었습니다.');
location.reload();
} else {
alert(response.message || '댓글 삭제에 실패했습니다.');
}
},
error: function(xhr, status, error) {
// 에러 처리는 xit-common.js의 ajaxError에서 처리됨
}
});
}
},
/**
* 대댓글 등록
*/
registerChildComment: function(parentCommentId, content) {
var self = this;
if (!content) {
alert('댓글 내용을 입력해주세요.');
return;
}
// 페이지에서 설정된 데이터 가져오기
var bbsId = $('#commentArea').data('bbs-id');
var postId = $('#commentArea').data('post-id');
var urlPrefix = this.getUrlPrefix();
$.ajax({
url: urlPrefix + bbsId + '/comment/register.ajax',
type: 'POST',
data: {
postId: postId,
parentCommentId: parentCommentId,
content: content
},
success: function(response) {
if (response.result) {
alert('답글이 성공적으로 등록되었습니다.');
location.reload();
} else {
alert(response.message || '답글 등록에 실패했습니다.');
}
},
error: function(xhr, status, error) {
// 에러 처리는 xit-common.js의 ajaxError에서 처리됨
}
});
},
/**
* 모듈 초기화
*/
init: function() {
// 이벤트 핸들러 설정
this.eventBindEvents();
}
};
// 페이지 로드 시 댓글 관리 모듈 초기화
$(function() {
CommentManager.init();
});
// 전역 네임스페이스에 모듈 노출
window.CommentManager = CommentManager;
})(window, jQuery);

@ -102,6 +102,166 @@
color: #1565c0;
}
/* ===== 그리드용 드롭다운 스타일 ===== */
.grid-dropdown {
position: relative;
display: inline-block;
}
.grid-dropdown-btn {
background-color: #007bff;
color: white;
border: none;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
}
.grid-dropdown-btn:hover {
background-color: #0056b3;
}
.grid-dropdown-menu {
position: absolute;
top: 100%;
left: 0;
min-width: 120px;
background-color: #fff;
border: 1px solid #e9ecef;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
border-radius: 4px;
z-index: 1000;
padding: 4px 0;
}
.grid-dropdown-menu .dropdown-item {
display: block;
padding: 8px 12px;
color: #495057;
text-decoration: none;
font-size: 12px;
transition: background-color 0.2s;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.grid-dropdown-menu .dropdown-item:hover {
background-color: #f8f9fa;
color: #007bff;
text-decoration: none;
}
/* ===== XIT TUI Grid 드롭다운 메뉴 공통 컴포넌트 스타일 ===== */
/* 메뉴 컨테이너 */
.xit-grid-menu-container {
position: relative;
display: inline-block;
}
/* 메뉴 버튼 */
.xit-grid-menu-btn {
background: #f8f9fa;
border: 1px solid #dee2e6;
padding: 2px 6px;
font-size: 12px;
line-height: 1;
border-radius: 3px;
cursor: pointer;
color: #495057;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
}
.xit-grid-menu-btn:hover {
background: #e9ecef;
border-color: #adb5bd;
color: #495057;
}
.xit-grid-menu-btn:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
/* 메뉴 리스트 */
.xit-grid-menu-list {
display: none;
position: fixed;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 9999;
min-width: 120px;
padding: 4px 0;
font-family: inherit;
}
/* 메뉴 아이템 */
.xit-grid-menu-item {
display: block;
padding: 8px 12px;
text-decoration: none;
color: #333;
font-size: 12px;
border-bottom: 1px solid #eee;
transition: background-color 0.2s ease;
cursor: pointer;
white-space: nowrap;
}
.xit-grid-menu-item:last-child {
border-bottom: none;
}
.xit-grid-menu-item:hover {
background-color: #f8f9fa;
color: #333;
text-decoration: none;
}
.xit-grid-menu-item:focus {
outline: none;
background-color: #e9ecef;
color: #333;
}
/* 삭제 메뉴 아이템 스타일 */
.xit-grid-menu-item[data-action="delete"] {
color: #dc3545;
}
.xit-grid-menu-item[data-action="delete"]:hover {
background-color: #f8d7da;
color: #721c24;
}
/* 메뉴 구분선 */
.xit-grid-menu-divider {
height: 0;
margin: 4px 0;
overflow: hidden;
border-top: 1px solid #e9ecef;
}
/* 비활성화된 메뉴 아이템 */
.xit-grid-menu-item.disabled {
color: #6c757d;
pointer-events: none;
cursor: default;
}
.xit-grid-menu-item.disabled:hover {
background-color: transparent;
}
/* 상단 메뉴 컨테이너 스타일 */
.top-level-menu {
display: flex;

@ -1,11 +1,42 @@
/* 컨택스트 패스 설정 로드 */
// 기본값 설정 (설정 로드 실패 시 사용)
var contextPath = '';
// 현재 스크립트 경로에서 컨택스트 패스 추출 (임시 방법)
(function() {
var scripts = document.getElementsByTagName('script');
var currentScript = scripts[scripts.length - 1];
var src = currentScript.src;
if (src) {
// 스크립트 경로에서 컨택스트 패스 추출 (예: http://localhost:8080/context/resources/xit/xit-common.js)
var match = src.match(/^https?:\/\/[^\/]+([^\/]*?)\/resources\//);
if (match && match[1]) {
contextPath = match[1];
}
}
})();
// 서버에서 정확한 컨택스트 패스 로드 (위의 추출된 값으로 URL 구성)
(function() {
var script = document.createElement('script');
script.src = (contextPath || '') + '/common/config/context-path.do';
script.async = false; // 동기적으로 로드하여 설정값이 먼저 로드되도록 함
script.onerror = function() {
// 컨택스트 패스 로드 실패 시 기본값 유지
console.warn('컨택스트 패스 설정 로드에 실패했습니다. 추출된 값("' + contextPath + '")을 사용합니다.');
};
document.head.appendChild(script);
})();
/* 로그인 URL 설정 로드 */
// 기본값 설정 (설정 로드 실패 시 사용)
var loginUrl = '/login/login.do';
// 서버에서 설정값 로드
// 서버에서 설정값 로드 (컨택스트 패스 적용)
(function() {
var script = document.createElement('script');
script.src = '/common/config/login-url.do';
script.src = (contextPath || '') + '/common/config/login-url.do';
script.async = false; // 동기적으로 로드하여 설정값이 먼저 로드되도록 함
document.head.appendChild(script);
})();
@ -92,12 +123,6 @@ var activeAjaxCount = 0;
// Ajax 시작 시 Progress Block UI 표시
$(document).ajaxSend(function(event, jqXHR, ajaxOptions) {
// 사이드바 상태 저장 요청인 경우 오버레이 표시하지 않음
if (ajaxOptions.url && ajaxOptions.url.indexOf('/common/header/sidebar/state.ajax') !== -1) {
// 사이드바 상태 저장 요청은 오버레이 카운트에서 제외
return;
}
activeAjaxCount++;
// 첫 번째 Ajax 요청일 때만 Progress Block UI 표시
if (activeAjaxCount === 1) {
@ -107,12 +132,6 @@ $(document).ajaxSend(function(event, jqXHR, ajaxOptions) {
// Ajax 완료 시 Progress Block UI 제거 (성공/실패 모든 경우)
$(document).ajaxComplete(function(event, jqXHR, ajaxOptions) {
// 사이드바 상태 저장 요청인 경우 오버레이 카운트에서 제외
if (ajaxOptions.url && ajaxOptions.url.indexOf('/common/header/sidebar/state.ajax') !== -1) {
// 사이드바 상태 저장 요청은 카운트 감소하지 않음
return;
}
activeAjaxCount--;
// 모든 Ajax 요청이 완료되었을 때만 Progress Block UI 숨김
if (activeAjaxCount <= 0) {
@ -377,3 +396,309 @@ $(document).ready(function() {
subtree: true
});
});
/**
* TUI Grid 드롭다운 메뉴 공통 컴포넌트
* 그리드 행에서 사용할 있는 드롭다운 액션 메뉴를 제공합니다.
*
* 사용법:
* 1. 그리드 컬럼 formatter에서 XitTuiGridDropdownMenu.createMenuHtml() 호출
* 2. 그리드 이벤트에서 XitTuiGridDropdownMenu.bindEvents() 호출
*
* [기본 사용 예제]
* // 1. 그리드 컬럼 정의 시 formatter에서 사용
* {
* header: '작업',
* name: 'actions',
* width: 80,
* align: 'center',
* sortable: false,
* resizable: false,
* formatter: function(props) {
* return XitTuiGridDropdownMenu.createMenuHtml(props);
* }
* }
*
* // 2. 그리드 이벤트 바인딩
* var menuCallbacks = {
* view: function(row, rowKey) {
* console.log('보기:', row);
* // 상세보기 모달 열기 또는 페이지 이동
* },
* edit: function(row, rowKey) {
* console.log('수정:', row);
* // 수정 모달 열기 또는 페이지 이동
* },
* delete: function(row, rowKey) {
* if (confirm('삭제하시겠습니까?')) {
* // 삭제 Ajax 요청 처리
* }
* }
* };
* XitTuiGridDropdownMenu.bindEvents('#grid', menuCallbacks, gridInstance);
*
* [커스텀 메뉴 예제 - 방법 1: 배열로 직접 전달]
* // 커스텀 메뉴 아이템 정의
* var customMenuItems = [
* { text: '보기', action: 'view', class: 'js-row-view' },
* { text: '복사', action: 'copy', class: 'js-row-copy' },
* { text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
* ];
*
* // formatter에서 커스텀 메뉴 사용
* formatter: function(props) {
* return XitTuiGridDropdownMenu.createMenuHtml(props, customMenuItems);
* }
*
* [커스텀 메뉴 예제 - 방법 2: config 객체로 설정]
* // formatter에서 설정 객체를 통한 커스텀 메뉴 사용
* formatter: function(props) {
* return XitTuiGridDropdownMenu.createMenuHtml(props, {
* menuItems: [
* { text: '보기', action: 'view', class: 'js-row-view' },
* { text: '복사', action: 'copy', class: 'js-row-copy' },
* { text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
* ]
* });
* }
*
* [동적 메뉴 생성 예제]
* // 행 데이터에 따라 동적으로 메뉴 생성
* formatter: function(props) {
* var row = props.grid.getRow(props.rowKey);
* var dynamicMenuItems = [
* { text: '보기', action: 'view', class: 'js-row-view' }
* ];
*
* // 조건에 따라 메뉴 추가
* if (row.status === 'ACTIVE') {
* dynamicMenuItems.push({ text: '수정', action: 'edit', class: 'js-row-edit' });
* dynamicMenuItems.push({ text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' });
* }
*
* return XitTuiGridDropdownMenu.createMenuHtml(props, { menuItems: dynamicMenuItems });
* }
*
* // 해당하는 콜백 함수들 정의
* var customCallbacks = {
* view: function(row, rowKey) { ... },
* copy: function(row, rowKey) { ... },
* delete: function(row, rowKey) { ... }
* };
*
* [스타일 커스터마이징 예제]
* var customConfig = {
* btnIcon: '⚙', // 버튼 아이콘 변경
* btnStyle: 'background: #007bff; color: white; padding: 4px 8px;', // 버튼 스타일 변경
* menuStyle: 'min-width: 150px; background: #f8f9fa;' // 메뉴 스타일 변경
* };
*
* // formatter에서 커스텀 설정 사용
* formatter: function(props) {
* return XitTuiGridDropdownMenu.createMenuHtml(props, null, customConfig);
* }
*
* @author XIT Framework Team
* @since 2025-08-22
*/
var XitTuiGridDropdownMenu = {
/**
* 기본 설정
*/
defaultConfig: {
// 메뉴 버튼 스타일
btnClass: 'xit-grid-menu-btn',
btnStyle: 'background: #f8f9fa; border: 1px solid #dee2e6; padding: 2px 6px; font-size: 12px; line-height: 1; border-radius: 3px; cursor: pointer; color: #495057;',
btnIcon: '&#8942;', // 세로 점 3개 아이콘
// 메뉴 컨테이너 스타일
containerClass: 'xit-grid-menu-container',
containerStyle: 'position: relative;',
// 메뉴 리스트 스타일
menuClass: 'xit-grid-menu-list',
menuStyle: 'display: none; position: fixed; background: white; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 9999; min-width: 120px;',
// 메뉴 아이템 스타일
itemClass: 'xit-grid-menu-item',
itemStyle: 'display: block; padding: 8px 12px; text-decoration: none; color: #333; border-bottom: 1px solid #eee;',
// 기본 메뉴 아이템들
defaultMenuItems: [
{ text: '보기', action: 'view', class: 'js-row-view' },
{ text: '수정', action: 'edit', class: 'js-row-edit' },
{ text: '삭제', action: 'delete', class: 'js-row-delete', style: 'color: #dc3545;' }
]
},
/**
* 드롭다운 메뉴 HTML 생성
* @param {Object} props - TUI Grid formatter props 객체
* @param {Array|Object} menuItems - 메뉴 아이템 배열 또는 설정 객체 (선택적)
* @param {Object} config - 설정 객체 (선택적, 기본값은 defaultConfig 사용)
* @returns {String} 생성된 HTML 문자열
*/
createMenuHtml: function(props, menuItems, config) {
// 파라미터 유형에 따른 처리
// 두 번째 파라미터가 객체이고 배열이 아닌 경우, config로 처리
if (menuItems && typeof menuItems === 'object' && !Array.isArray(menuItems) && !config) {
config = menuItems;
menuItems = null;
}
// 설정 병합 (기본 설정 + 사용자 설정)
config = $.extend({}, this.defaultConfig, config || {});
// 메뉴 아이템 설정 우선순위:
// 1. 직접 전달된 menuItems 배열
// 2. config.menuItems
// 3. config.defaultMenuItems (기본값)
if (menuItems && Array.isArray(menuItems)) {
// 직접 전달된 menuItems 사용
} else if (config.menuItems && Array.isArray(config.menuItems)) {
menuItems = config.menuItems;
} else {
menuItems = config.defaultMenuItems;
}
var rowKey = props.rowKey;
var html = '';
// 메뉴 컨테이너 시작
html += '<div class="' + config.containerClass + '" style="' + config.containerStyle + '">';
// 메뉴 버튼
html += ' <button type="button" class="' + config.btnClass + '" data-row-key="' + rowKey + '" title="작업" ';
html += ' style="' + config.btnStyle + '">';
html += ' ' + config.btnIcon;
html += ' </button>';
// 메뉴 리스트
html += ' <div class="' + config.menuClass + '" data-row-key="' + rowKey + '" style="' + config.menuStyle + '">';
// 메뉴 아이템들 생성
for (var i = 0; i < menuItems.length; i++) {
var item = menuItems[i];
var itemStyle = config.itemStyle;
// 개별 아이템 스타일이 있으면 병합
if (item.style) {
itemStyle += item.style;
}
// 마지막 아이템은 하단 테두리 제거
if (i === menuItems.length - 1) {
itemStyle = itemStyle.replace('border-bottom: 1px solid #eee;', '');
}
html += ' <a class="' + config.itemClass + ' ' + (item.class || '') + '" ';
html += ' href="javascript:void(0)" data-row-key="' + rowKey + '" data-action="' + item.action + '" ';
html += ' style="' + itemStyle + '">' + item.text + '</a>';
}
html += ' </div>';
html += '</div>';
return html;
},
/**
* 드롭다운 메뉴 이벤트 바인딩
* @param {String} gridSelector - 그리드 컨테이너 선택자 (기본값: '#grid')
* @param {Object} callbacks - 액션별 콜백 함수들 { view: function(row, rowKey) {}, edit: function(row, rowKey) {}, delete: function(row, rowKey) {} }
* @param {Object} gridInstance - TUI Grid 인스턴스
* @param {Object} config - 설정 객체 (선택적)
*/
bindEvents: function(gridSelector, callbacks, gridInstance, config) {
// 설정 병합
config = $.extend({}, this.defaultConfig, config || {});
// 기본값 설정
gridSelector = gridSelector || '#grid';
var $gridEl = $(gridSelector);
// 메뉴 아이템 클릭 이벤트 (이벤트 위임 방식)
$gridEl.off('click', '.' + config.itemClass).on('click', '.' + config.itemClass, function(e) {
e.preventDefault();
e.stopPropagation();
var $item = $(this);
var rowKey = $item.data('rowKey');
var action = $item.data('action');
// 그리드에서 행 데이터 가져오기
var row = gridInstance ? gridInstance.getRow(rowKey) : null;
// 모든 메뉴 닫기
$('.' + config.menuClass).hide();
// 콜백 함수 호출
if (callbacks && typeof callbacks[action] === 'function') {
callbacks[action](row, rowKey);
} else {
console.warn('XitTuiGridDropdownMenu: ' + action + ' 액션에 대한 콜백 함수가 정의되지 않았습니다.');
}
});
// 메뉴 버튼 클릭 이벤트 - 메뉴 토글
$gridEl.off('click', '.' + config.btnClass).on('click', '.' + config.btnClass, function(e) {
e.preventDefault();
e.stopPropagation();
var $btn = $(this);
var $menu = $btn.siblings('.' + config.menuClass);
var rowKey = $btn.data('rowKey');
// 다른 모든 메뉴 닫기
$('.' + config.menuClass).not($menu).hide();
if ($menu.is(':visible')) {
// 메뉴가 보이면 숨김
$menu.hide();
} else {
// 메뉴가 숨겨져 있으면 표시
var btnOffset = $btn.offset();
var btnHeight = $btn.outerHeight();
// 메뉴 위치 계산 (버튼 아래쪽 우측 정렬로 표시)
$menu.css({
//'top': btnOffset.top + btnHeight + 2,
'top': btnOffset.top + btnHeight + 2 - 35,
'left': btnOffset.left - $menu.outerWidth() + $btn.outerWidth()
}).show();
}
});
// 메뉴 아이템 hover 효과
$gridEl.off('mouseenter mouseleave', '.' + config.itemClass)
.on('mouseenter', '.' + config.itemClass, function() {
$(this).css('background-color', '#f8f9fa');
})
.on('mouseleave', '.' + config.itemClass, function() {
$(this).css('background-color', 'white');
});
// 문서 클릭 시 모든 메뉴 닫기
$(document).off('click.xitGridMenu').on('click.xitGridMenu', function(e) {
if (!$(e.target).closest('.' + config.containerClass).length) {
$('.' + config.menuClass).hide();
}
});
},
/**
* 특정 그리드의 모든 메뉴 닫기
* @param {String} gridSelector - 그리드 컨테이너 선택자 (선택적, 기본값: 전체)
* @param {Object} config - 설정 객체 (선택적)
*/
closeAllMenus: function(gridSelector, config) {
config = $.extend({}, this.defaultConfig, config || {});
if (gridSelector) {
$(gridSelector).find('.' + config.menuClass).hide();
} else {
$('.' + config.menuClass).hide();
}
}
};

@ -1,582 +0,0 @@
/* imageModify.jsp에서 분리된 스타일 */
.img-container {
max-height: 500px;
margin-bottom: 20px;
position: relative;
}
.img-preview {
width: 100%;
height: 200px;
overflow: hidden;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
/* 상단 도구 영역 */
.top-toolbar {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: #ecf0f1;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
border: 1px solid rgba(255,255,255,0.05);
}
.top-toolbar h3 {
margin: 0 0 15px 0;
font-size: 16px;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
color: #ecf0f1;
}
.toolbar-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.tool-section {
background: rgba(255,255,255,0.08);
padding: 15px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
}
.tool-section h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
opacity: 0.95;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
color: #ecf0f1;
}
.tool-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.tool-buttons button {
background: rgba(255,255,255,0.12);
border: 1px solid rgba(255,255,255,0.2);
color: #ecf0f1;
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
transition: all 0.3s ease;
cursor: pointer;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.tool-buttons button:hover {
background: rgba(255,255,255,0.18);
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(0,0,0,0.15);
}
.tool-buttons button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.tool-buttons button.active {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.3);
box-shadow: 0 3px 8px rgba(0,0,0,0.2);
}
.input-group {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 8px;
}
.input-group:last-child {
margin-bottom: 0;
}
.input-group label {
font-size: 12px;
font-weight: 500;
opacity: 0.9;
min-width: 50px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
color: #ecf0f1;
}
.input-group input, .input-group select {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
color: #ecf0f1;
padding: 6px 10px;
border-radius: 4px;
font-size: 12px;
width: 80px;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.input-group input::placeholder {
color: rgba(236, 240, 241, 0.6);
}
.input-group input:focus, .input-group select:focus {
outline: none;
border-color: rgba(255,255,255,0.4);
background: rgba(255,255,255,0.15);
box-shadow: 0 0 0 2px rgba(255,255,255,0.1);
}
.input-group button {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.25);
color: #ecf0f1;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
transition: all 0.3s ease;
cursor: pointer;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
white-space: nowrap;
}
.input-group button:hover {
background: rgba(255,255,255,0.22);
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.ratio-label {
font-size: 12px;
color: #ecf0f1;
opacity: 0.9;
margin-left: 5px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.input-group input[type="checkbox"] {
width: auto;
margin: 0;
cursor: pointer;
}
/* 메인 편집 영역 */
.main-edit-area {
display: grid;
grid-template-columns: 1fr 320px;
gap: 25px;
margin-bottom: 25px;
}
.edit-canvas {
background: #ffffff;
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.edit-sidebar {
background: #ffffff;
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.edit-sidebar h4 {
color: #495057;
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
.size-info {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
border: 1px solid #e9ecef;
}
.size-info p {
margin: 8px 0;
font-size: 14px;
color: #495057;
font-weight: 500;
}
/* 결과 영역 */
.result-container {
background: #ffffff;
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
margin-top: 25px;
border: 1px solid #e9ecef;
}
.result-container h4 {
color: #495057;
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
.result-image {
max-width: 100%;
max-height: 600px;
width: auto;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
border: 1px solid #e9ecef;
object-fit: contain;
display: block;
margin: 0 auto;
}
.result-image:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
.result-image:hover {
transform: scale(1.02);
transition: transform 0.3s ease;
}
.result-actions {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
max-width: 100%;
overflow: hidden;
}
.result-actions .newbtn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
margin-left: 15px;
}
.result-actions .newbtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
/* 상태 메시지 */
.status-message {
padding: 10px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
margin-bottom: 10px;
border: 1px solid;
}
.status-message.info {
background: #d1ecf1;
border-color: #bee5eb;
color: #0c5460;
}
.status-message.success {
background: #d4edda;
border-color: #c3e6cb;
color: #155724;
}
.status-message.processing {
background: #fff3cd;
border-color: #ffeaa7;
color: #856404;
}
/* 업로드 섹션 */
.upload-section {
background: #ffffff;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.upload-section h3 {
color: #495057;
margin-bottom: 20px;
font-size: 20px;
font-weight: 600;
}
.upload-section .form-group {
margin-bottom: 15px;
}
.upload-section .input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
}
.upload-section .input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.upload-section .help-text {
color: #6c757d;
font-size: 13px;
margin-top: 8px;
font-style: italic;
}
/* 반응형 디자인 */
@media (max-width: 768px) {
.toolbar-content {
grid-template-columns: 1fr;
gap: 15px;
}
.main-edit-area {
grid-template-columns: 1fr;
}
.input-group {
flex-direction: column;
align-items: stretch;
}
.input-group input, .input-group select {
width: 100%;
}
.tool-buttons {
justify-content: center;
}
.result-actions {
flex-direction: column;
gap: 15px;
padding: 15px;
}
.result-actions .newbtn {
margin-left: 0;
width: 100%;
max-width: 200px;
}
.top-toolbar {
padding: 12px 15px;
}
.tool-section {
padding: 12px;
}
/* 모바일에서 결과 이미지 크기 조정 */
.result-image {
max-height: 400px; /* 모바일에서는 더 작게 */
max-width: 100%;
}
}
/* Cropper.js 기본 선택영역 스타일 오버라이드 */
.cropper-view-box {
outline: 2px solid #667eea;
border-radius: 4px;
}
.cropper-face {
background-color: rgba(102, 126, 234, 0.1);
}
/* gs_b_top 업로드 영역 스타일 */
.gs_b_top {
background: #ffffff;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e9ecef;
}
.gs_b_top ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
.gs_b_top li {
margin: 0;
display: flex;
align-items: center;
}
.gs_b_top .th {
font-size: 14px;
font-weight: 600;
color: #495057;
margin-right: 8px;
white-space: nowrap;
min-width: 80px;
}
.gs_b_top .input {
padding: 6px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 13px;
transition: all 0.2s ease;
background: #ffffff;
min-width: 200px;
}
.gs_b_top .input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
}
.gs_b_top .help-text {
color: #6c757d;
font-size: 12px;
margin: 0;
font-style: italic;
padding: 4px 8px;
background: #f8f9fa;
border-radius: 4px;
border-left: 2px solid #667eea;
white-space: nowrap;
}
/* 파일 입력 필드 스타일 */
.gs_b_top input[type="file"] {
padding: 6px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 13px;
background: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
min-width: 250px;
max-width: 350px;
}
.gs_b_top input[type="file"]:hover {
border-color: #667eea;
background: #f8f9fa;
}
.gs_b_top input[type="file"]:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
background: #ffffff;
}
/* 파일 입력 필드 내부 텍스트 스타일 */
.gs_b_top input[type="file"]::file-selector-button {
padding: 4px 12px;
border: 1px solid #202342;
border-radius: 3px;
background: #202342;
color: #fff;
font-size: 12px;
font-weight: 500;
cursor: pointer;
margin-right: 8px;
transition: all 0.2s ease;
}
.gs_b_top input[type="file"]::file-selector-button:hover {
background: #1a1d35;
border-color: #1a1d35;
}
/* 유효성 검사 스타일 */
.input-error {
border-color: #dc3545 !important;
background-color: #fff5f5 !important;
color: #000000 !important;
}
.input-error:focus {
border-color: #dc3545 !important;
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.1) !important;
color: #000000 !important;
}
.input-error::placeholder {
color: #6c757d !important;
}
.validation-message {
color: #dc3545;
font-size: 11px;
margin-top: 4px;
padding: 2px 6px;
background: #fff5f5;
border: 1px solid #feb2b2;
border-radius: 3px;
display: block;
}
/* 입력 그룹 내 validation 메시지 위치 조정 */
.input-group .validation-message {
margin-top: 2px;
margin-bottom: 4px;
}
/* 결과 헤더 스타일 */
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.result-header h4 {
margin: 0;
color: #495057;
font-size: 18px;
font-weight: 600;
}
.result-header .newbtn {
margin: 0;
padding: 8px 16px;
font-size: 13px;
}

@ -1,582 +0,0 @@
/* imageModify.jsp에서 분리된 스타일 */
.img-container {
max-height: 500px;
margin-bottom: 20px;
position: relative;
}
.img-preview {
width: 100%;
height: 200px;
overflow: hidden;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
/* 상단 도구 영역 */
.top-toolbar {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: #ecf0f1;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
border: 1px solid rgba(255,255,255,0.05);
}
.top-toolbar h3 {
margin: 0 0 15px 0;
font-size: 16px;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
color: #ecf0f1;
}
.toolbar-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.tool-section {
background: rgba(255,255,255,0.08);
padding: 15px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
}
.tool-section h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
opacity: 0.95;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
color: #ecf0f1;
}
.tool-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.tool-buttons button {
background: rgba(255,255,255,0.12);
border: 1px solid rgba(255,255,255,0.2);
color: #ecf0f1;
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
transition: all 0.3s ease;
cursor: pointer;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.tool-buttons button:hover {
background: rgba(255,255,255,0.18);
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(0,0,0,0.15);
}
.tool-buttons button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.tool-buttons button.active {
background: rgba(255,255,255,0.22);
border-color: rgba(255,255,255,0.3);
box-shadow: 0 3px 8px rgba(0,0,0,0.2);
}
.input-group {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 8px;
}
.input-group:last-child {
margin-bottom: 0;
}
.input-group label {
font-size: 12px;
font-weight: 500;
opacity: 0.9;
min-width: 50px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
color: #ecf0f1;
}
.input-group input, .input-group select {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
color: #ecf0f1;
padding: 6px 10px;
border-radius: 4px;
font-size: 12px;
width: 80px;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.input-group input::placeholder {
color: rgba(236, 240, 241, 0.6);
}
.input-group input:focus, .input-group select:focus {
outline: none;
border-color: rgba(255,255,255,0.4);
background: rgba(255,255,255,0.15);
box-shadow: 0 0 0 2px rgba(255,255,255,0.1);
}
.input-group button {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.25);
color: #ecf0f1;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
transition: all 0.3s ease;
cursor: pointer;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
white-space: nowrap;
}
.input-group button:hover {
background: rgba(255,255,255,0.22);
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.ratio-label {
font-size: 12px;
color: #ecf0f1;
opacity: 0.9;
margin-left: 5px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.input-group input[type="checkbox"] {
width: auto;
margin: 0;
cursor: pointer;
}
/* 메인 편집 영역 */
.main-edit-area {
display: grid;
grid-template-columns: 1fr 320px;
gap: 25px;
margin-bottom: 25px;
}
.edit-canvas {
background: #ffffff;
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.edit-sidebar {
background: #ffffff;
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.edit-sidebar h4 {
color: #495057;
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
.size-info {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
border: 1px solid #e9ecef;
}
.size-info p {
margin: 8px 0;
font-size: 14px;
color: #495057;
font-weight: 500;
}
/* 결과 영역 */
.result-container {
background: #ffffff;
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
margin-top: 25px;
border: 1px solid #e9ecef;
}
.result-container h4 {
color: #495057;
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
.result-image {
max-width: 100%;
max-height: 600px;
width: auto;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
border: 1px solid #e9ecef;
object-fit: contain;
display: block;
margin: 0 auto;
}
.result-image:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
.result-image:hover {
transform: scale(1.02);
transition: transform 0.3s ease;
}
.result-actions {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
max-width: 100%;
overflow: hidden;
}
.result-actions .newbtn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
margin-left: 15px;
}
.result-actions .newbtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
/* 상태 메시지 */
.status-message {
padding: 10px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
margin-bottom: 10px;
border: 1px solid;
}
.status-message.info {
background: #d1ecf1;
border-color: #bee5eb;
color: #0c5460;
}
.status-message.success {
background: #d4edda;
border-color: #c3e6cb;
color: #155724;
}
.status-message.processing {
background: #fff3cd;
border-color: #ffeaa7;
color: #856404;
}
/* 업로드 섹션 */
.upload-section {
background: #ffffff;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.upload-section h3 {
color: #495057;
margin-bottom: 20px;
font-size: 20px;
font-weight: 600;
}
.upload-section .form-group {
margin-bottom: 15px;
}
.upload-section .input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
}
.upload-section .input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.upload-section .help-text {
color: #6c757d;
font-size: 13px;
margin-top: 8px;
font-style: italic;
}
/* 반응형 디자인 */
@media (max-width: 768px) {
.toolbar-content {
grid-template-columns: 1fr;
gap: 15px;
}
.main-edit-area {
grid-template-columns: 1fr;
}
.input-group {
flex-direction: column;
align-items: stretch;
}
.input-group input, .input-group select {
width: 100%;
}
.tool-buttons {
justify-content: center;
}
.result-actions {
flex-direction: column;
gap: 15px;
padding: 15px;
}
.result-actions .newbtn {
margin-left: 0;
width: 100%;
max-width: 200px;
}
.top-toolbar {
padding: 12px 15px;
}
.tool-section {
padding: 12px;
}
/* 모바일에서 결과 이미지 크기 조정 */
.result-image {
max-height: 400px; /* 모바일에서는 더 작게 */
max-width: 100%;
}
}
/* Cropper.js 기본 선택영역 스타일 오버라이드 */
.cropper-view-box {
outline: 2px solid #667eea;
border-radius: 4px;
}
.cropper-face {
background-color: rgba(102, 126, 234, 0.1);
}
/* gs_b_top 업로드 영역 스타일 */
.gs_b_top {
background: #ffffff;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e9ecef;
}
.gs_b_top ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
.gs_b_top li {
margin: 0;
display: flex;
align-items: center;
}
.gs_b_top .th {
font-size: 14px;
font-weight: 600;
color: #495057;
margin-right: 8px;
white-space: nowrap;
min-width: 80px;
}
.gs_b_top .input {
padding: 6px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 13px;
transition: all 0.2s ease;
background: #ffffff;
min-width: 200px;
}
.gs_b_top .input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
}
.gs_b_top .help-text {
color: #6c757d;
font-size: 12px;
margin: 0;
font-style: italic;
padding: 4px 8px;
background: #f8f9fa;
border-radius: 4px;
border-left: 2px solid #667eea;
white-space: nowrap;
}
/* 파일 입력 필드 스타일 */
.gs_b_top input[type="file"] {
padding: 6px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 13px;
background: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
min-width: 250px;
max-width: 350px;
}
.gs_b_top input[type="file"]:hover {
border-color: #667eea;
background: #f8f9fa;
}
.gs_b_top input[type="file"]:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
background: #ffffff;
}
/* 파일 입력 필드 내부 텍스트 스타일 */
.gs_b_top input[type="file"]::file-selector-button {
padding: 4px 12px;
border: 1px solid #202342;
border-radius: 3px;
background: #202342;
color: #fff;
font-size: 12px;
font-weight: 500;
cursor: pointer;
margin-right: 8px;
transition: all 0.2s ease;
}
.gs_b_top input[type="file"]::file-selector-button:hover {
background: #1a1d35;
border-color: #1a1d35;
}
/* 유효성 검사 스타일 */
.input-error {
border-color: #dc3545 !important;
background-color: #fff5f5 !important;
color: #000000 !important;
}
.input-error:focus {
border-color: #dc3545 !important;
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.1) !important;
color: #000000 !important;
}
.input-error::placeholder {
color: #6c757d !important;
}
.validation-message {
color: #dc3545;
font-size: 11px;
margin-top: 4px;
padding: 2px 6px;
background: #fff5f5;
border: 1px solid #feb2b2;
border-radius: 3px;
display: block;
}
/* 입력 그룹 내 validation 메시지 위치 조정 */
.input-group .validation-message {
margin-top: 2px;
margin-bottom: 4px;
}
/* 결과 헤더 스타일 */
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.result-header h4 {
margin: 0;
color: #495057;
font-size: 18px;
font-weight: 600;
}
.result-header .newbtn {
margin: 0;
padding: 8px 16px;
font-size: 13px;
}

@ -1,266 +0,0 @@
/* 전체 페이지 레벨에서 스크롤 방지 */
.main_body {
overflow-x: hidden !important;
box-sizing: border-box !important;
}
.main_bars {
overflow-x: hidden !important;
box-sizing: border-box !important;
}
.bgs-main {
overflow-x: hidden !important;
box-sizing: border-box !important;
}
/* Fabric.js 이미지 편집기 스타일 */
.image-editor-container {
width: 100% !important;
height: 780px;
border: 1px solid #ddd;
position: relative;
background: #f5f5f5;
cursor: default;
max-width: none !important;
min-width: 100% !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
/* 상위 객체들의 제한 해제 */
.box_column {
width: 100% !important;
max-width: none !important;
padding: 0 !important;
margin: 0 !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
.col-sm-12 {
width: 100% !important;
max-width: none !important;
padding: 0 !important;
margin: 0 !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
.gs_booking {
width: 100% !important;
max-width: none !important;
padding: 0 !important;
margin: 0 !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
.contants_body {
width: 100% !important;
max-width: none !important;
padding: 0 !important;
margin: 0 !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
.row {
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
.canvas-container {
width: 100% !important;
height: 100% !important;
display: flex;
justify-content: center;
align-items: center;
padding: 0 !important;
margin: 0 !important;
max-width: none !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
.canvas-container canvas {
cursor: crosshair;
max-width: 100% !important;
max-height: 100% !important;
width: auto !important;
height: auto !important;
}
/* Fabric.js 캔버스 요소들에 대한 스타일 */
.canvas-container .upper-canvas,
.canvas-container .lower-canvas {
max-width: 100% !important;
max-height: 100% !important;
width: auto !important;
height: auto !important;
}
.toolbar {
position: absolute;
top: 10px;
left: 10px;
background: white;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 1000;
}
.toolbar button {
margin: 2px;
padding: 8px 12px;
border: 1px solid #ddd;
background: #f8f9fa;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
}
.toolbar button:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.toolbar button.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.toolbar button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.toolbar-group {
display: inline-block;
margin-right: 10px;
padding-right: 10px;
border-right: 1px solid #ddd;
}
.toolbar-group:last-child {
border-right: none;
margin-right: 0;
}
.file-input {
display: none;
}
.upload-btn {
background: #28a745 !important;
color: white !important;
}
.upload-btn:hover {
background: #218838 !important;
}
.zoom-controls {
position: absolute;
bottom: 10px;
right: 10px;
background: white;
border: 1px solid #ccc;
border-radius: 5px;
padding: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 1000;
}
.zoom-controls button {
margin: 2px;
padding: 5px 8px;
border: 1px solid #ddd;
background: #f8f9fa;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.zoom-controls button:hover {
background: #e9ecef;
}
.zoom-level {
display: inline-block;
margin: 0 10px;
font-size: 12px;
color: #666;
}
.mosaic-controls {
position: absolute;
top: 10px;
right: 10px;
background: white;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 1000;
}
.mosaic-controls input[type="range"] {
width: 100px;
margin: 5px 0;
}
.mosaic-controls label {
font-size: 12px;
color: #666;
display: block;
margin-bottom: 5px;
}
.status-bar {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 5px 10px;
border-radius: 3px;
font-size: 12px;
z-index: 1000;
}
/* 모바일 반응형 */
@media (max-width: 768px) {
.toolbar {
position: relative;
top: auto;
left: auto;
margin-bottom: 10px;
width: 100%;
box-sizing: border-box;
}
.mosaic-controls {
position: relative;
top: auto;
right: auto;
margin-top: 10px;
width: 100%;
box-sizing: border-box;
}
.zoom-controls {
position: relative;
bottom: auto;
right: auto;
margin-top: 10px;
width: 100%;
box-sizing: border-box;
}
}

@ -1,3 +0,0 @@
.tui-image-editor-header-logo {
display: none !important;
}
Loading…
Cancel
Save