사이드바 상태 세션 저장 후 사용

dev
박성영 5 months ago
parent 9f49ccfc0a
commit fdd7908e4e

@ -1,20 +1,56 @@
package go.kr.project.common.controller;
import go.kr.project.common.service.CommonHeaderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@RequestMapping("/common/header")
@Controller
@RequiredArgsConstructor
@Slf4j
@Tag(name = "공통 일정", description = "공통 일정 관련 API")
@Tag(name = "공통 헤더", description = "공통 헤더 관련 API")
public class CommonHeaderController {
private final CommonHeaderService commonHeaderService;
/**
* API
*
* @param state (collapsed, expanded, )
* @param session HTTP
* @return
*/
@PostMapping("/sidebar/state.ajax")
@ResponseBody
@Operation(summary = "사이드바 상태 저장", description = "사이드바 상태를 세션에 저장합니다.")
public ResponseEntity<Map<String, Object>> saveSidebarState(
@Parameter(description = "사이드바 상태 (sidebar-collapse, 또는 빈 문자열)")
@RequestParam(value = "state", required = false, defaultValue = "") String state,
HttpSession session) {
// 세션에 사이드바 상태 저장
session.setAttribute("sidebarState", state);
log.debug("사이드바 상태 저장: {}", state);
// 응답 데이터 구성
Map<String, Object> response = new HashMap<>();
response.put("result", true);
response.put("message", "사이드바 상태가 저장되었습니다.");
return ResponseEntity.ok(response);
}
}

@ -1,3 +1,5 @@
<%@ page import="egovframework.util.SessionUtil" %>
<%@ page import="javax.websocket.Session" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
@ -79,7 +81,7 @@
<link rel="stylesheet" href="<c:url value="/css/xit-multi-fileupload.css"/>" />
</head>
<body>
<body class="${sessionScope.sidebarState}">
<!-- Sidebar -->
<div class="sidebar">
<tiles:insertAttribute name="menu_header" />

@ -4,26 +4,133 @@
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dateUtil" uri="http://egovframework.go.kr/functions/date-util" %>
<style>
/* 상단 메뉴 스타일 */
.top-level-menu {
display: flex;
align-items: center;
margin-left: 15px;
}
.top-menu-item {
display: flex;
align-items: center;
padding: 0 20px;
color: #495057;
font-weight: 500;
text-decoration: none;
height: 40px;
position: relative;
transition: color 0.2s;
font-size: 16px;
}
.top-menu-item:hover {
color: #007bff;
text-decoration: none;
}
.top-menu-item.active {
color: #007bff;
font-weight: 550;
position: relative;
}
.top-menu-item.active:before {
content: '';
position: absolute;
bottom: 0;
left: 10px;
right: 10px;
height: 2px;
background-color: #007bff;
}
.top-menu-item:after {
content: '';
position: absolute;
right: 0;
top: 25%;
height: 50%;
width: 1px;
background-color: #e9ecef;
}
.top-menu-item:last-child:after {
display: none;
}
.top-menu-icon {
width: 16px;
height: 16px;
margin-right: 5px;
}
/* 반응형 스타일 */
@media (max-width: 992px) {
.top-menu-item span {
display: none;
}
.top-menu-item {
padding: 0 12px;
}
.top-menu-icon {
margin-right: 0;
width: 18px;
height: 18px;
}
}
</style>
<!-- Main header -->
<div class="main-header">
<!-- 사이드바 토글 버튼 -->
<a class="nav-link nav-link-faded rounded-circle nav-icon" href="#" data-toggle="sidebar">
<a class="nav-link nav-link-faded rounded-circle nav-icon" href="#" data-toggle="sidebar" id="saveSidebarState">
<i class="material-icons">menu</i>
</a>
<%-- LEVEL1 DEPTH MENU 표출(클릭 시 하위에 있는 URL 중 첫번째 URL로 이동 가능) --%>
<c:if test="${not empty sessionScope.sessionVO.menus}">
<div class="top-level-menu">
<c:forEach var="menu" items="${sessionScope.sessionVO.menus}">
<c:if test="${menu.useYn eq 'Y' and menu.viewYn eq 'Y'}">
<c:set var="firstChildUrl" value="#" />
<c:set var="hasValidChild" value="false" />
<%-- 메뉴 자체에 유효한 URL이 있는 경우 우선 적용 --%>
<c:if test="${not empty menu.menuUrl and menu.menuUrl != '#'}">
<c:set var="firstChildUrl" value="${menu.menuUrl}" />
<c:set var="hasValidChild" value="true" />
</c:if>
<%-- 메뉴 자체에 URL이 없는 경우 첫 번째 유효한 하위 URL 찾기 --%>
<c:if test="${not empty menu.children and not hasValidChild}">
<c:forEach var="subMenu" items="${menu.children}" varStatus="subStatus">
<c:if test="${subMenu.useYn eq 'Y' and subMenu.viewYn eq 'Y' and not empty subMenu.menuUrl and subMenu.menuUrl != '#' and not hasValidChild}">
<c:set var="firstChildUrl" value="${subMenu.menuUrl}" />
<c:set var="hasValidChild" value="true" />
</c:if>
</c:forEach>
</c:if>
<a href="<c:url value='${firstChildUrl}' />" class="top-menu-item" title="${menu.menuNm}">
<c:if test="${not empty menu.menuIcon}"><i data-feather="${menu.menuIcon}" class="top-menu-icon"></i></c:if>
<span>${menu.menuNm}</span>
</a>
</c:if>
</c:forEach>
</div>
</c:if>
<!-- 우측 메뉴 영역 -->
<ul class="nav nav-circle ml-auto">
<!-- SMS 발송 버튼 -->
<%--<li class="nav-item sms-btn-wrapper">
<a href="#" title="SMS 발송" class="iocnsbtn sms sms"><span>SMS</span></a>
</li>--%>
<!-- 세션 종료 시간 표시 -->
<li class="nav-item d-none d-sm-block ml-2">
<label class="h_date" title="세션 종료 예정시간">세션 종료: <span id="sessionExpiryTime">${dateUtil:getSessionExpiryTime(pageContext.request, 'HH:mm:ss')}</span></label>
</li>
<!-- 사용자 프로필 드롭다운 -->
<li class="nav-item dropdown ml-2" style="display: flex; align-items: center;">
<a class="nav-link nav-link-faded rounded nav-link-img dropdown-toggle px-2" href="#"
@ -65,6 +172,31 @@ $(document).ready(function() {
// 세션 타이머 초기화
initSessionTimer();
// 상단 메뉴 아이콘 초기화
initializeTopMenuIcons();
$(document).on('click', '#saveSidebarState', function () {
var saveSidebarStateValue = "sidebar-collapse";
if( $("body").hasClass("sidebar-collapse") ){
saveSidebarStateValue = ""
}
console.log(saveSidebarStateValue);
// AJAX 요청으로 사이드바 상태 저장
$.ajax({
url: "<c:url value='/common/header/sidebar/state.ajax'/>",
type: "POST",
data: {"state":saveSidebarStateValue},
dataType: "json",
success: function(response) {
//console.log("사이드바 상태 저장 성공:", response);
},
error: function(xhr, status, error) {
console.error(xhr.responseText);
}
});
});
/**
* 드롭다운 메뉴 초기화 함수
@ -124,9 +256,41 @@ $(document).ready(function() {
}
/**
* 세션 타이머 초기화 함수
* 세션 종료 시간을 매초 업데이트
* 상단 메뉴 아이콘 초기화 함수
* Feather 아이콘 라이브러리를 사용하여 메뉴 아이콘을 렌더링
*/
function initializeTopMenuIcons() {
// Feather 아이콘 초기화 (이미 로드된 경우에만 실행)
if (typeof feather !== 'undefined') {
// 상단 메뉴의 아이콘 렌더링
feather.replace('.top-menu-icon');
// 현재 URL과 일치하는 메뉴 항목 활성화
highlightActiveTopMenu();
}
}
/**
* 현재 URL과 일치하는 상단 메뉴 항목을 활성화하는 함수
*/
function highlightActiveTopMenu() {
// 현재 URL 가져오기
var currentUrl = window.location.pathname;
// 각 상단 메뉴 항목 확인
$('.top-menu-item').each(function() {
var menuUrl = $(this).attr('href');
// URL에서 컨텍스트 경로 제거
menuUrl = menuUrl.replace(/^.*\/\/[^\/]+/, '');
// 현재 URL이 메뉴 URL로 시작하는 경우 활성화
if (currentUrl.startsWith(menuUrl) && menuUrl !== '#') {
$(this).addClass('active');
$(this).css('color', '#007bff');
}
});
}
function initSessionTimer() {
// 초기 세션 시간 가져오기 (HH:mm:ss 형식)
var $sessionTimeElement = $("#sessionExpiryTime");

@ -0,0 +1,105 @@
/* 사이드바 최적화를 위한 CSS */
/* 사이드바 하드웨어 가속 활성화 */
.sidebar {
will-change: transform, width;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 사이드바 배경 요소 최적화 */
.sidebar-backdrop {
will-change: opacity;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
/* 메인 콘텐츠 영역 최적화 */
.main {
will-change: margin-left;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
/* 사이드바 내부 요소 최적화 */
.sidebar-body {
will-change: transform;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
/* 사이드바 헤더 최적화 */
.sidebar-header {
will-change: transform;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
/* 사이드바 트랜지션 최적화 */
@media (min-width: 992px) {
body .sidebar {
transition: width 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
body .main {
transition: margin-left 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
}
@media (max-width: 991.98px) {
body .sidebar {
transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.sidebar-backdrop {
transition: opacity 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
}
/* 사이드바 펼치기/접기 상태 최적화 */
body.sidebar-collapse .sidebar {
width: 60px !important;
min-width: 60px !important;
max-width: 60px !important;
}
body.sidebar-collapse .main {
margin-left: 60px !important;
}
body.sidebar-expand .sidebar {
transform: translateX(0);
}
/* 사이드바 초기 로드 시 깜박임 방지 */
.sidebar-loading {
visibility: hidden;
opacity: 0;
}
.sidebar-loaded {
visibility: visible;
opacity: 1;
transition: opacity 0.1s ease-out;
}
/* 메뉴 아이템 최적화 */
.nav-item {
will-change: transform, opacity;
transform: translateZ(0);
backface-visibility: hidden;
}
/* 메뉴 아이콘 최적화 */
.nav-icon {
will-change: transform;
transform: translateZ(0);
backface-visibility: hidden;
}
Loading…
Cancel
Save