하석형 하석형 06-02
250602 하석형 스토리지 저장 방식 설정, 앱 구동 시 초기값 조회
@085335cbbc9642591d30aa91c24f968331e697bd
client/resources/api/index.js
--- client/resources/api/index.js
+++ client/resources/api/index.js
@@ -1,5 +1,6 @@
 import axios from 'axios';
-import store from "../../views/pages/AppStore";
+// import store from "../../views/pages/AppStore";
+import { getGlobalStore } from '../../views/pages/AppStore';
 
 const apiClient = axios.create({
   baseURL: '/',
@@ -14,13 +15,15 @@
 
 apiClient.interceptors.request.use(
   config => {
+    const store = getGlobalStore(); // 전역 스토어 가져오기
     const excludeCtxUrl = excludeCtxUrls.some(url => config.url.includes(url));
-    const contextPath = store.state.contextPath || '';
+    const contextPath = store?.state.contextPath || '';
+    console.log("Context Path:", contextPath);
     if(!excludeCtxUrl) {
       config.url = contextPath + config.url; // 요청 시 Context Path 추가
     }
 
-    config.headers.Authorization = store.state.authorization; // 요청 시 AccessToken 추가
+    config.headers.Authorization = store?.state.authorization; // 요청 시 AccessToken 추가
     return config;
   },
   error => {
@@ -45,34 +48,35 @@
       return Promise.reject(error);
     }
     // 토큰의 만료기간이 끝난경우
-  //  if (error.response.status == 401 && error.response.data.message == 'Token expired' && !originalReq._retry) {
-  if (error.response.status === 401 && error.response.data?.message?.toLowerCase().includes('expired') && !originalReq._retry) {
-      originalReq._retry = true; // 재요청 시도(한번만 실행)
-      try {
-        const res = await axios.post('/refresh/tokenReissue.json', {});
-        store.commit('setAuthorization', res.headers.authorization); // 새로 발급 받은 AccessToken 저장
-        originalReq.headers.Authorization = store.state.authorization; // 새로 발급 받은 AccessToken을 기존 요청에 추가
-        /** jwt토큰 디코딩 **/
-        const base64String = store.state.authorization.split('.')[1];
-        const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/');
-        const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
-          return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
-        }).join(''));
-        const mbr = JSON.parse(jsonPayload);
-        // const mbr = JSON.parse(decodeURIComponent(escape(window.atob(base64String)))); // jwt claim 추출
-        store.commit("setMbrNm", mbr.mbrNm);
-        store.commit('setRoles', mbr.roles);
-        /** jwt토큰 디코딩 **/
-        return apiClient(originalReq); // 원래 요청 재시도 /pathname + search
-      } catch (refreshError) {
-        const redirect = window.location.pathname + window.location.search;
-        sessionStorage.setItem("redirect", redirect);
-        alert('세션이 종료 되었습니다.\n로그인을 새로 해주세요.');
-        store.commit("setStoreReset");
-        window.location = '/login.page';
-        return Promise.reject(refreshError);
+    //  if (error.response.status == 401 && error.response.data.message == 'Token expired' && !originalReq._retry) {
+    if (error.response.status === 401 && error.response.data?.message?.toLowerCase().includes('expired') && !originalReq._retry) {
+      const store = getGlobalStore(); // 전역 스토어 가져오기
+        originalReq._retry = true; // 재요청 시도(한번만 실행)
+        try {
+          const res = await axios.post('/refresh/tokenReissue.json', {});
+          store.commit('setAuthorization', res.headers.authorization); // 새로 발급 받은 AccessToken 저장
+          originalReq.headers.Authorization = store.state.authorization; // 새로 발급 받은 AccessToken을 기존 요청에 추가
+          /** jwt토큰 디코딩 **/
+          const base64String = store.state.authorization.split('.')[1];
+          const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/');
+          const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
+            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+          }).join(''));
+          const mbr = JSON.parse(jsonPayload);
+          // const mbr = JSON.parse(decodeURIComponent(escape(window.atob(base64String)))); // jwt claim 추출
+          store.commit("setMbrNm", mbr.mbrNm);
+          store.commit('setRoles', mbr.roles);
+          /** jwt토큰 디코딩 **/
+          return apiClient(originalReq); // 원래 요청 재시도 /pathname + search
+        } catch (refreshError) {
+          const redirect = window.location.pathname + window.location.search;
+          sessionStorage.setItem("redirect", redirect);
+          alert('세션이 종료 되었습니다.\n로그인을 새로 해주세요.');
+          store.commit("setStoreReset");
+          window.location = '/login.page';
+          return Promise.reject(refreshError);
+        }
       }
-    }
     return Promise.reject(error);
   }
 )
client/resources/api/logOut.js
--- client/resources/api/logOut.js
+++ client/resources/api/logOut.js
@@ -1,7 +1,8 @@
 import apiClient from "./index";
-import store from '../../views/pages/AppStore';
+import { getGlobalStore } from '../../views/pages/AppStore';
 
 export const logOutProc = () => {
+    const store = getGlobalStore(); // 전역 스토어 가져오기
     return apiClient.post(`/mbr/logout.json`, {}, {
         headers: {
             'refresh': store.state.refresh
client/resources/api/loginPolicy.js
--- client/resources/api/loginPolicy.js
+++ client/resources/api/loginPolicy.js
@@ -22,4 +22,21 @@
 
 export const saveBy2ndAuth = email2ndAuth => {
     return apiClient.post(`/admin/loginPolicy/saveEmail2ndAuth.json`, email2ndAuth);
+}
+
+export const findAllByStorageMode = () => {
+    return apiClient.post(`/admin/loginPolicy/findByStorageMode.json`);
+}
+
+export const saveByStorageMode = storageMode => {
+    return apiClient.post(`/admin/loginPolicy/saveStorageMode.json`, storageMode);
+}
+
+/** 초기 세팅용 */
+export const getLoginMode = () => {
+    return apiClient.post(`/sys/loginPolicy/getLoginMode.json`);
+}
+
+export const getStorageMode = () => {
+    return apiClient.post(`/sys/loginPolicy/findByStorageMode.json`);
 }
(파일 끝에 줄바꿈 문자 없음)
client/resources/js/commonPlugin.js
--- client/resources/js/commonPlugin.js
+++ client/resources/js/commonPlugin.js
@@ -1,5 +1,5 @@
 import { save } from "../../resources/api/cntnStats";
-import store from "../../views/pages/AppStore";
+import { getGlobalStore } from "../../views/pages/AppStore";
 
 /**
  * 공통 처리 플러그인
@@ -20,6 +20,7 @@
  */
 
 async function cntnSave(data){
+    const store = getGlobalStore(); // 전역 스토어 가져오기
     const isLogin = store.state.loginAt; // 로그인 유무 정보 확인
 
     axios({
client/views/common/filters.js
--- client/views/common/filters.js
+++ client/views/common/filters.js
@@ -1,16 +1,17 @@
 import moment from 'moment';
-import store from '../pages/AppStore';
+import { getGlobalStore } from '../pages/AppStore';
 
 const filters = {
-
     // Context Path를 포함한 URL 생성
     ctxPath(path) {
+        const store = getGlobalStore(); // 전역 스토어 가져오기
         const contextPath = store.state.contextPath || '';
         return contextPath + path;
     },
 
     // Context Path를 제외한 URL 생성
     logicalPath(path) {
+        const store = getGlobalStore(); // 전역 스토어 가져오기
         const contextPath = store.state.contextPath || '';
         return (contextPath !== '' && path.startsWith(contextPath)) ? path.slice(contextPath.length) : path;
     },
client/views/index.js
--- client/views/index.js
+++ client/views/index.js
@@ -8,7 +8,7 @@
 // import AppRouter from "./pages/AppRouter.js";
 import createAppRouter from "./pages/AppRouter.js";
 import App from "./pages/App.vue";
-import store from "./pages/AppStore.js";
+import createAppStore from "./pages/AppStore.js";
 import COMMON_UTIL from "../resources/js/common.js";
 import filters from './common/filters.js';
 import cmmnPlugin from './common/commonPlugin.js';
@@ -18,12 +18,26 @@
 import VueDatePicker from '@vuepic/vue-datepicker';
 import '@vuepic/vue-datepicker/dist/main.css'
 import '../resources/scss/main.scss';
+import { getCntxtPth } from "../resources/api/cntxtPth.js";
+import { getStorageMode, getLoginMode } from "../resources/api/loginPolicy.js";
 
 async function initVueApp() {
-  const savedLoginMode = localStorage.getItem("loginMode");
-  if (savedLoginMode) {
-    store.commit("setLoginMode", savedLoginMode);
-  }
+  const ctx = await contextPath();
+  const strgMode = await storageMode();
+  const lgnMode = await loginMode();
+  console.log("Context Path:", ctx);
+  console.log("Storage Mode:", strgMode);
+  console.log("Login Mode:", lgnMode);
+
+  const store = createAppStore(ctx, strgMode, lgnMode);
+
+  // const savedLoginMode = localStorage.getItem("loginMode");
+  // const savedLoginMode = JSON.parse(localStorage.getItem("vuex"))?.loginMode;
+  // console.log("Saved Login Mode:", savedLoginMode);
+  // if (savedLoginMode) {
+  //   console.log("Store loginMode:", store.state.loginMode);
+  //   // store.commit("setLoginMode", savedLoginMode);
+  // }
 
   const router = await createAppRouter()
   const vue = createApp(App)
@@ -43,4 +57,60 @@
 
   vue.mount("#root");
 }
+
+// Context Path 조회
+async function contextPath() {
+  try {
+    const res = await getCntxtPth();
+    if (res.status == 200) {
+      return res.data.data;
+    }
+    return null;
+  } catch (error) {
+    console.log("Error fetching context path:", error);
+    const errorData = error.response.data;
+    if (errorData.message != null && errorData.message != "") {
+      alert(error.response.data.message);
+    } else {
+      alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+    }
+  }
+};
+
+// 스토리지 방식 조회
+async function storageMode() {
+  try {
+    const res = await getStorageMode ();
+    if (res.status == 200) {
+      return res.data.data;
+    }
+    return null;
+  } catch (error) {
+    const errorData = error.response.data;
+    if (errorData.message != null && errorData.message != "") {
+      alert(error.response.data.message);
+    } else {
+      alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+    }
+  }
+};
+
+// 로그인 방식 조회
+async function loginMode() {
+  try {
+    const res = await getLoginMode();
+    if (res.status == 200) {
+      return res.data.data;
+    }
+    return null;
+  } catch (error) {
+    const errorData = error.response.data;
+    if (errorData.message != null && errorData.message != "") {
+      alert(error.response.data.message);
+    } else {
+      alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+    }
+  }
+};
+
 initVueApp();
(파일 끝에 줄바꿈 문자 없음)
client/views/layout/AdminHeader copy.vue
--- client/views/layout/AdminHeader copy.vue
+++ client/views/layout/AdminHeader copy.vue
@@ -14,7 +14,6 @@
 </template>
 
 <script>
-import store from "../../views/pages/AppStore";
 import queryParams from '../../resources/js/queryParams';
 import { defaultSearchParams } from '../../resources/js/defaultSearchParams';
 import { findBySysMenu } from '../../resources/api/menu';
@@ -37,7 +36,7 @@
     },
     methods: {
         menuCheck() {
-            const menu = store.state.menu;
+            const menu = this.$store.state.menu;
             if(menu != null && menu != '' && menu != undefined) {
                 this.checkMenu = menu.menuId;
             }
@@ -46,8 +45,8 @@
         async findAll() {
             try {
                 const params = {
-                    roles : store.state.roles.map(auth => auth.authority),
-                    menuType : store.state.userType
+                    roles : this.$store.state.roles.map(auth => auth.authority),
+                    menuType : this.$store.state.userType
                 };
                 const res = await findBySysMenu(params);
                 if(res.status == 200) {
client/views/layout/AdminHeader.vue
--- client/views/layout/AdminHeader.vue
+++ client/views/layout/AdminHeader.vue
@@ -20,7 +20,6 @@
 </template>
 
 <script>
-import store from "../../views/pages/AppStore";
 import queryParams from '../../resources/js/queryParams';
 import { mapActions } from "vuex";
 import { cacheReSet } from "../../resources/api/cacheReSet";
@@ -33,7 +32,7 @@
     },
     data() {
         return {
-            mbrNm: store.state.mbrNm,
+            mbrNm: this.$store.state.mbrNm,
         }
     },
     created() {
@@ -76,7 +75,7 @@
     computed: {
         pgNm() {
             const route = this.$route;
-            const storeMenu = store.state.menu;
+            const storeMenu = this.$store.state.menu;
 
             if (route?.meta?.korName) {
                 if (route.path === '/adm/main.page') {
client/views/layout/AdminMenu copy.vue
--- client/views/layout/AdminMenu copy.vue
+++ client/views/layout/AdminMenu copy.vue
@@ -41,7 +41,6 @@
   </nav>
 </template>
 <script>
-import store from "../pages/AppStore";
 import queryParams from "../../resources/js/queryParams";
 import { defaultSearchParams } from "../../resources/js/defaultSearchParams";
 import { mapActions } from "vuex";
@@ -54,7 +53,7 @@
   props: {},
   data() {
     return {
-      mbrNm: store.state.mbrNm,
+      mbrNm: this.$store.state.mbrNm,
       currentPath: this.$route.path,
       resetSearch: { ...defaultSearchParams },
       menuList: [],
@@ -65,7 +64,7 @@
   },
   methods: {
     menuCheck() {
-      const menu = store.state.menu;
+      const menu = this.$store.state.menu;
       if (menu != null && menu != "" && menu != undefined) {
         this.menuList = menu.childList;
       }
client/views/layout/AdminMenu.vue
--- client/views/layout/AdminMenu.vue
+++ client/views/layout/AdminMenu.vue
@@ -54,7 +54,6 @@
   </template>
 
 <script>
-import store from "../pages/AppStore";
 import queryParams from '../../resources/js/queryParams';
 import { defaultSearchParams } from '../../resources/js/defaultSearchParams';
 import cntnStatsSave from "../../resources/js/cntnStatsSave";
@@ -82,7 +81,7 @@
     methods: {
         // 새로고침 시 메뉴 체크
         menuCheck() {
-            let menu = store.state.menu;
+            let menu = this.$store.state.menu;
             if (menu) {
                 // this.checkMenu = menu.menuId;
                 this.activeMenus = this.getParentMenus(menu);
@@ -96,8 +95,8 @@
         async findAll() {
             try {
                 const params = {
-                    roles: store.state.roles.map(auth => auth.authority),
-                    menuType: store.state.userType
+                    roles: this.$store.state.roles.map(auth => auth.authority),
+                    menuType: this.$store.state.userType
                 };
                 const res = await findBySysMenu(params);
                 if (res.status === 200) {
client/views/layout/UserMenu.vue
--- client/views/layout/UserMenu.vue
+++ client/views/layout/UserMenu.vue
@@ -110,7 +110,6 @@
 </template>
 
 <script>
-import store from "../../views/pages/AppStore";
 import { findBySysMenu } from "../../resources/api/menu";
 import queryParams from "../../resources/js/queryParams";
 import { defaultSearchParams } from "../../resources/js/defaultSearchParams";
@@ -123,7 +122,7 @@
     return {
       pageRole: this.$store.state.userType,
       currentOpenIndex: null,
-      roles: store.state.roles || [],
+      roles: this.$store.state.roles || [],
       menuList: [],
       currentActiveIndex: null,
       currentSubActiveIndex: null,
client/views/layout/menuSatisfaction.vue
--- client/views/layout/menuSatisfaction.vue
+++ client/views/layout/menuSatisfaction.vue
@@ -45,7 +45,6 @@
 </template>
 
 <script>
-import store from "../pages/AppStore.js";
 import { defaultMenuDgstfnParams } from '../../resources/js/defaultMenuSatisfactionParams.js';
 import { menuFindByMenu } from '../../resources/api/menu.js';
 import { findByMenuId, save } from '../../resources/api/menuDgstfn.js';
@@ -53,8 +52,8 @@
 export default {
     data() {
         return {
-            mbrNm: store.state.mbrNm,
-            pgNm: store.state.menu && store.state.menu.menuNm ? store.state.menu.menuNm : "홈",
+            mbrNm: this.$store.state.mbrNm,
+            pgNm: this.$store.state.menu && this.$store.state.menu.menuNm ? this.$store.state.menu.menuNm : "홈",
             menuId: null,
 
             dgstfnExmnUseYn: "N", // 메뉴 만족도 사용 여부
@@ -171,7 +170,7 @@
     },
     computed: {
         pgNm() {
-            return store.state.menu && store.state.menu.menuNm ? store.state.menu.menuNm : "홈";
+            return this.$store.state.menu && this.$store.state.menu.menuNm ? this.$store.state.menu.menuNm : "홈";
         }
     },
     mounted() {
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -1,5 +1,5 @@
 import { createWebHistory, createRouter } from "vue-router";
-import store from "./AppStore";
+import { getGlobalStore } from "./AppStore";
 import filters from '../common/filters';
 
 // 메인화면
@@ -32,6 +32,7 @@
 // Context Path 정보 호출
 async function getContextPath() {
   try {
+    const store = getGlobalStore(); // 전역 스토어 가져오기
     const res = await getCntxtPth();
     if (res.status == 200) {
       let ctx = res.data.data || ''; // Context Path 정보
@@ -52,6 +53,7 @@
 // 라우터 정보 호출
 async function fetchRoutes() {
   try {
+    const store = getGlobalStore(); // 전역 스토어 가져오기
     const res = await findAll();
     if (res.status == 200) {
       const newRoutes = res.data.data.map(route => ({
@@ -139,6 +141,7 @@
 
 // 권한 검증
 function isValidRole() {
+  const store = getGlobalStore(); // 전역 스토어 가져오기
   const roles = store.state.roles;
   if (!Array.isArray(roles)) {
     store.commit("setStoreReset");
@@ -167,6 +170,7 @@
   });
 
  AppRouter.beforeEach(async (to, from, next) => {
+  const store = getGlobalStore(); // 전역 스토어 가져오기
   const contextPath = store.state.contextPath; // Context Path 정보
   const admPath = to.path.includes('/adm'); // 관리자 페이지 여부 (true: 관리자 페이지, false: 사용자 페이지)
   
client/views/pages/AppStore.js
--- client/views/pages/AppStore.js
+++ client/views/pages/AppStore.js
@@ -2,185 +2,205 @@
 import createPersistedState from "vuex-persistedstate";
 import { logOutProc } from "../../resources/api/logOut"
 
-export default createStore({
-  plugins: [createPersistedState({
-    paths: ['loginMode', 'authorization', 'mbrId', 'mbrNm', 'roles', 'contextPath', 'pageAuth']
-  })],
-  state: {
-    authorization: null,
-    // refresh: null,
-    loginMode: 'J',
-    userType: "portal",
-    menu: null,
-    path: null,
-    roles: [{authority: "ROLE_NONE"}],
-    pageAuth: null,
-    contextPath: null,
-  },
-  getters: {
-    getAuthorization: function () {},
-    // getRefresh: function () {},
-    getMbrNm: function () {},
-    getRoles: function () {},
-    getLoginMode: state => state.loginMode,
-  },
-  mutations: {
-    setAuthorization(state, newValue) {
-      state.authorization = newValue;
-    },
-    // setRefresh(state, newValue) {
-    //   state.refresh = newValue;
-    // },
-    setMbrNm(state, newValue) {
-      state.mbrNm = newValue;
-    },
-    setMbrId(state, newValue) {
-      state.mbrId = newValue;
-    },
-    setRoles(state, newValue) {
-      state.roles = newValue;
-    },
-    setUserType(state, newValue) {
-      state.userType = newValue;
-    },
-    setMenu(state, newValue) {
-      state.menu = newValue;
-    },
-    setPath(state, newValue) {
-      state.path = newValue;
-    },
-    setStoreReset(state) {
-      state.authorization = null;
-      // state.refresh = null;
-      state.loginMode = 'J';
-      state.mbrNm = null;
-      state.mbrId = null;
-      state.roles = [{authority: "ROLE_NONE"}];
-      state.menu = null;
-      state.pageAuth = null;
-      state.contextPath = null;
-    },
-    setPageAuth(state, newValue) {
-      state.pageAuth = newValue;
-    },
-    setMenuList(state, menuList) {
-      state.menuList = menuList;
-      // 메뉴트리 펼치기
-      const flattenMenus = (menus) => {
-        const result = [];
+let globalStore = null;
 
-        for (const menu of menus) {
-          result.push(menu);
-          if (menu.childList?.length) {
-            result.push(...flattenMenus(menu.childList));
-          }
-        }
+// 전역 스토어 설정 함수
+export function setGlobalStore(store) {
+  globalStore = store;
+}
 
-        return result;
-      }
-      const flattenedMenuList = flattenMenus(menuList);
-      state.flatMenuList = flattenedMenuList;
+// 전역 스토어 호출 함수
+export function getGlobalStore() {
+  return globalStore;
+}
+
+export default function createAppStore(ctx, strgMode, lgnMode) {
+  const store = createStore({
+    plugins: [createPersistedState({
+      // storage: window.sessionStorage,
+      storage: strgMode === 'S' ? window.sessionStorage : window.localStorage,
+      paths: ['loginMode', 'authorization', 'mbrId', 'mbrNm', 'roles', 'contextPath', 'pageAuth']
+    })],
+    state: {
+      authorization: null,
+      // refresh: null,
+      loginMode: lgnMode || 'J',
+      userType: "portal",
+      menu: null,
+      path: null,
+      roles: [{authority: "ROLE_NONE"}],
+      pageAuth: null,
+      contextPath: ctx || null,
     },
-    setContextPath(state, ctx) {
-      state.contextPath = ctx;
+    getters: {
+      getAuthorization: function () {},
+      // getRefresh: function () {},
+      getMbrNm: function () {},
+      getRoles: function () {},
+      getLoginMode: state => state.loginMode,
     },
-    setLoginMode(state, value) {
-      state.loginMode = value;
-    },
-  },
-  actions: {
-    async logout({ commit }) {
-       try {
-        const ctx = this.state.contextPath; // 캐시 초기화 전 contextPath 저장
-        const admPath = this.state.path?.includes("/adm") // 캐시 초기화 전 경로 구분 (true: 관리자 페이지, false: 사용자 페이지)
-        const loginMode = this.state.loginMode || localStorage.getItem('loginMode') || 'J'; 
-        
-        // 로그인 모드에 따른 처리
-        if (loginMode === 'J') {
-          // JWT 방식인 경우만 서버 API 호출
-          try {
-            const res = await logOutProc();
-            if (res.data.message) {
-              alert(res.data.message);
+    mutations: {
+      setAuthorization(state, newValue) {
+        state.authorization = newValue;
+      },
+      // setRefresh(state, newValue) {
+      //   state.refresh = newValue;
+      // },
+      setMbrNm(state, newValue) {
+        state.mbrNm = newValue;
+      },
+      setMbrId(state, newValue) {
+        state.mbrId = newValue;
+      },
+      setRoles(state, newValue) {
+        state.roles = newValue;
+      },
+      setUserType(state, newValue) {
+        state.userType = newValue;
+      },
+      setMenu(state, newValue) {
+        state.menu = newValue;
+      },
+      setPath(state, newValue) {
+        state.path = newValue;
+      },
+      setStoreReset(state) {
+        state.authorization = null;
+        // state.refresh = null;
+        state.loginMode = 'J';
+        state.mbrNm = null;
+        state.mbrId = null;
+        state.roles = [{authority: "ROLE_NONE"}];
+        state.menu = null;
+        state.pageAuth = null;
+        state.contextPath = null;
+      },
+      setPageAuth(state, newValue) {
+        state.pageAuth = newValue;
+      },
+      setMenuList(state, menuList) {
+        state.menuList = menuList;
+        // 메뉴트리 펼치기
+        const flattenMenus = (menus) => {
+          const result = [];
+
+          for (const menu of menus) {
+            result.push(menu);
+            if (menu.childList?.length) {
+              result.push(...flattenMenus(menu.childList));
             }
-          } catch (error) {
-            console.log(error);
-            // API 에러가 발생해도 클라이언트는 정리
           }
-        }
-        
-        // 1. 상태 초기화
-        commit("setStoreReset");
-        
-        // 2. 로컬스토리지와 세션스토리지 초기화
-        localStorage.clear();
-        sessionStorage.clear();
 
-        // 3. 모든 가능한 쿠키 삭제 (OAuth 관련 포함)
-        const cookiesToDelete = [
-          'refresh', 
-          'Authorization', 
-          'JSESSIONID', 
-          'oauth_access_token',  // OAuth 토큰 쿠키
-          'oauth_refresh_token', // OAuth 리프레시 토큰
-          'SESSION'              // 스프링 기본 세션 쿠키
-        ];
-        
-        cookiesToDelete.forEach(cookieName => {
-          document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
-          document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
-        });
-        
-        // 4. 로그인 페이지로 이동 (OAuth 관련 파라미터 제거)
-        const cleanUrl = admPath ? 
-          ctx + "/cmslogin.page" : 
-          ctx + "/login.page";
-          
-        // URL에서 OAuth 관련 파라미터 제거
-        window.history.replaceState({}, document.title, cleanUrl);
-        window.location.href = cleanUrl;
-        
-      } catch(error) {
-        console.error("로그아웃 처리 중 오류:", error);
-        
-        // 에러가 발생해도 클라이언트 상태는 정리
-        commit("setStoreReset");
-        localStorage.clear();
-        sessionStorage.clear();
-        
-        const ctx = this.state.contextPath;
-        const admPath = this.state.path?.includes("/adm");
-        
-        // 에러 메시지 표시
-        const errorData = error.response?.data;
-        if (errorData?.message) {
-          alert(errorData.message);
-        } else {
-          console.log("로그아웃 처리 중 예상치 못한 오류 발생");
+          return result;
         }
-        
-        // 로그인 페이지로 이동
-        const cleanUrl = admPath ? 
-          ctx + "/cmslogin.page" : 
-          ctx + "/login.page";
+        const flattenedMenuList = flattenMenus(menuList);
+        state.flatMenuList = flattenedMenuList;
+      },
+      setContextPath(state, ctx) {
+        state.contextPath = ctx;
+      },
+      setLoginMode(state, value) {
+        state.loginMode = value;
+      },
+    },
+    actions: {
+      async logout({ commit }) {
+        try {
+          const ctx = this.state.contextPath; // 캐시 초기화 전 contextPath 저장
+          const admPath = this.state.path?.includes("/adm") // 캐시 초기화 전 경로 구분 (true: 관리자 페이지, false: 사용자 페이지)
+          const loginMode = this.state.loginMode || localStorage.getItem('loginMode') || 'J'; 
           
-        window.location.href = cleanUrl;
-      }
+          // 로그인 모드에 따른 처리
+          if (loginMode === 'J') {
+            // JWT 방식인 경우만 서버 API 호출
+            try {
+              const res = await logOutProc();
+              if (res.data.message) {
+                alert(res.data.message);
+              }
+            } catch (error) {
+              console.log(error);
+              // API 에러가 발생해도 클라이언트는 정리
+            }
+          }
+          
+          // 1. 상태 초기화
+          commit("setStoreReset");
+          
+          // 2. 로컬스토리지와 세션스토리지 초기화
+          localStorage.clear();
+          sessionStorage.clear();
+
+          // 3. 모든 가능한 쿠키 삭제 (OAuth 관련 포함)
+          const cookiesToDelete = [
+            'refresh', 
+            'Authorization', 
+            'JSESSIONID', 
+            'oauth_access_token',  // OAuth 토큰 쿠키
+            'oauth_refresh_token', // OAuth 리프레시 토큰
+            'SESSION'              // 스프링 기본 세션 쿠키
+          ];
+          
+          cookiesToDelete.forEach(cookieName => {
+            document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
+            document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
+          });
+          
+          // 4. 로그인 페이지로 이동 (OAuth 관련 파라미터 제거)
+          const cleanUrl = admPath ? 
+            ctx + "/cmslogin.page" : 
+            ctx + "/login.page";
+            
+          // URL에서 OAuth 관련 파라미터 제거
+          window.history.replaceState({}, document.title, cleanUrl);
+          window.location.href = cleanUrl;
+          
+        } catch(error) {
+          console.error("로그아웃 처리 중 오류:", error);
+          
+          // 에러가 발생해도 클라이언트 상태는 정리
+          commit("setStoreReset");
+          localStorage.clear();
+          sessionStorage.clear();
+          
+          const ctx = this.state.contextPath;
+          const admPath = this.state.path?.includes("/adm");
+          
+          // 에러 메시지 표시
+          const errorData = error.response?.data;
+          if (errorData?.message) {
+            alert(errorData.message);
+          } else {
+            console.log("로그아웃 처리 중 예상치 못한 오류 발생");
+          }
+          
+          // 로그인 페이지로 이동
+          const cleanUrl = admPath ? 
+            ctx + "/cmslogin.page" : 
+            ctx + "/login.page";
+            
+          window.location.href = cleanUrl;
+        }
+      },
+      setUserType({ commit }, userType) {
+        commit("setUserType", userType);
+      },
+      setMenu({ commit }, menu) {
+        commit("setMenu", menu);
+      },
+      setPath({ commit }, path) {
+        commit("setPath", path);
+      },
+      setPageAuth({ commit }, pageAuth) {
+        commit("setPageAuth", pageAuth);
+      },
+      setStoreReset({commit}) {
+        commit("setStoreReset");
+      },
     },
-    setUserType({ commit }, userType) {
-      commit("setUserType", userType);
-    },
-    setMenu({ commit }, menu) {
-      commit("setMenu", menu);
-    },
-    setPath({ commit }, path) {
-      commit("setPath", path);
-    },
-    setPageAuth({ commit }, pageAuth) {
-      commit("setPageAuth", pageAuth);
-    },
-    setStoreReset({commit}) {
-      commit("setStoreReset");
-    },
-  },
-});
(파일 끝에 줄바꿈 문자 없음)
+  });
+
+  setGlobalStore(store); // 전역 스토어 설정
+
+  return store;
+}
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/adm/system/LoginPolicy/LoginPolicy.vue
--- client/views/pages/adm/system/LoginPolicy/LoginPolicy.vue
+++ client/views/pages/adm/system/LoginPolicy/LoginPolicy.vue
@@ -47,6 +47,26 @@
               </div>
             </div>
             <div class="layout">
+              <label class="form-title">스토리지 방식 설정</label>
+              <div class="form-group">
+                <div class="check-area">
+                  <div class="form-check">
+                    <input type="radio" id="storageModeL" class="mr5" value="L" v-model="strgMode"
+                      @change="saveByStorageMode" />
+                    <label for="storageModeL">LOCAL 방식</label>
+                  </div>
+                  <div class="form-check">
+                    <input type="radio" id="storageModeS" class="mr5" value="S" v-model="strgMode"
+                      @change="saveByStorageMode" />
+                    <label for="storageModeS">SESSION 방식</label>
+                  </div>
+                </div>
+                <!-- <span class="ml10 gray">
+                  현재 로그인 방식은 <strong>{{ loginModeLabel }}</strong> 입니다.
+                </span> -->
+              </div>
+            </div>
+            <div class="layout">
               <label class="form-title">이메일 2차 인증 설정</label>
               <div class="form-group">
                 <div class="check-area">
@@ -63,8 +83,6 @@
                 </div>
                 <!--    <p>{{ allowMultipleLogin ? '중복 로그인을 허용하고 있습니다.' : '중복 로그인을 허용하지 않습니다.' }}</p>-->
               </div>
-            </div>
-            <div class="layout">
             </div>
             <div class="layout">
               <label class="form-title">Context Path 설정</label>
@@ -88,10 +106,9 @@
 </template>
 
 <script>
-import { findAllByLoginPolicy, saveByLoginPolicy, findAllByLoginMode, saveByLoginMode, findAllBy2ndAuth, saveBy2ndAuth } from '../../../../../resources/api/loginPolicy.js';
+import { findAllByLoginPolicy, saveByLoginPolicy, findAllByLoginMode, saveByLoginMode, findAllBy2ndAuth, saveBy2ndAuth, findAllByStorageMode, saveByStorageMode } from '../../../../../resources/api/loginPolicy.js';
 import { getCntxtPth, saveCntxtPth } from '../../../../../resources/api/cntxtPth';
 import { cacheReSet } from "../../../../../resources/api/cacheReSet";
-import store from "../../../../../views/pages/AppStore";
 export default {
   data() {
     return {
@@ -105,6 +122,9 @@
 
       use2ndAuth: null, // 2차 인증 사용 여부
       previousUse2ndAuth: null, // 이전 2차 인증 사용 여부 저장
+
+      strgMode: null, // 스토리지 방식
+      previousStrgMode: null, // 이전 스토리지 방식 저장
     }
   },
   created() {
@@ -130,6 +150,9 @@
         const res3 = await findAllBy2ndAuth();
         this.use2ndAuth = res3.data.data === true ? 'Y' : 'N';
         this.previousUse2ndAuth = this.use2ndAuth; // 초기 상태를 저장
+        const res4 = await findAllByStorageMode();
+        this.strgMode = res4.data.data;
+        this.previousStrgMode = this.strgMode; // 초기 상태를 저장
       } catch (err) {
         alert('설정 정보를 불러오는 데 실패했습니다.');
       }
@@ -148,7 +171,7 @@
         loginPolicy.allowMultipleLogin = this.allowMultipleLogin;
         await saveByLoginPolicy(loginPolicy);
         alert('중복 로그인 설정이 저장되었습니다.');
-        store.commit("setStoreReset");
+        this.$store.commit("setStoreReset");
         window.location = this.$filters.ctxPath('/cmslogin.page');
       } catch (err) {
         alert('중복 로그인 설정 저장 실패'+ (err.response?.data?.message || err.message));
@@ -169,7 +192,7 @@
         loginMode.lgnMode = this.lgnMode;
         await saveByLoginMode(loginMode);
         alert('로그인 방식이 변경되었습니다.\n다시 로그인해주세요.');
-        store.commit("setStoreReset");
+        this.$store.commit("setStoreReset");
         window.location = this.$filters.ctxPath('/cmslogin.page');
       } catch (err) {
         alert('로그인 방식 저장 실패: ' + (err.response?.data?.message || err.message));
@@ -212,14 +235,14 @@
             if (storeCtx == '/') {
               storeCtx = '';
             }
-            store.commit("setContextPath", storeCtx); // 캐시 초기화 요청을 보내기 위한 Context Path 정보 저장
+            this.$store.commit("setContextPath", storeCtx); // 캐시 초기화 요청을 보내기 위한 Context Path 정보 저장
             await this.$store.dispatch("logout");
             /* const cacheRes = await cacheReSet(); // 캐시 초기화
             if (cacheRes.status !== 200) {
               alert(cacheRes.data.message);
             }
-            store.commit("setStoreReset"); // 캐시 초기화 후 Store 초기화
-            store.commit("setContextPath", storeCtx); // 라우터 Context Path 정보 저장
+            this.$store.commit("setStoreReset"); // 캐시 초기화 후 Store 초기화
+            this.$store.commit("setContextPath", storeCtx); // 라우터 Context Path 정보 저장
             window.location.href = `${storeCtx}/login.page`;
             // window.location.href = `${storeCtx}/adm/main.page`; */
           } else {
@@ -277,15 +300,43 @@
         email2ndAuth.useYn = this.use2ndAuth;
         await saveBy2ndAuth(email2ndAuth);
         alert('이메일 2차 인증 설정이 저장되었습니다.');
-        store.commit("setStoreReset");
+        // this.$store.commit("setStoreReset");
+        // window.location = this.$filters.ctxPath('/cmslogin.page');
+      } catch (error) {
+        const errorData = error.response.data;
+        if (errorData.message != null && errorData.message != "") {
+            alert(error.response.data.message);
+        } else {
+            alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+        }
+      }
+    },
+
+    // 스토리지 방식 저장
+    async saveByStorageMode() {
+      const confirmed = confirm(
+        '스토리지 방식을 변경하면 전체 사용자가 로그아웃됩니다.\n계속하시겠습니까?'
+      );
+      if (!confirmed) {
+        this.strgMode = this.previousStrgMode;
+        return;
+      }
+
+      try {
+        const storageMode = {};
+        storageMode.strgMode = this.strgMode;
+        await saveByStorageMode(storageMode);
+        alert('스토리지 방식이 변경되었습니다.\n다시 로그인해주세요.');
+        this.$store.commit("setStoreReset");
         window.location = this.$filters.ctxPath('/cmslogin.page');
       } catch (error) {
         const errorData = error.response.data;
-          if (errorData.message != null && errorData.message != "") {
-              alert(error.response.data.message);
-          } else {
-              alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
-          }
+        if (errorData.message != null && errorData.message != "") {
+            alert(error.response.data.message);
+        } else {
+            alert("에러가 발생했습니다.\n관리자에게 문의해주세요.");
+        }
+        this.strgMode = this.previousStrgMode;
       }
     },
   }
client/views/pages/login/AdminLogin.vue
--- client/views/pages/login/AdminLogin.vue
+++ client/views/pages/login/AdminLogin.vue
@@ -61,7 +61,7 @@
 
 <script>
 import { useStore } from "vuex";
-import store from "../AppStore";
+// import store from "../AppStore";
 import { loginProc } from "../../../resources/api/login";
 import { check2ndAuthProc, sendAuthEmailProc } from "../../../resources/api/email";
 import queryParams from "../../../resources/js/queryParams";
@@ -76,7 +76,7 @@
                 pswd: null,
                 lgnReqPage: 'A'
             },
-            store: useStore(),
+            // store: useStore(),
             isAdminPage: false,
 
             memberInfo: {
@@ -87,6 +87,8 @@
             // 인증 절차
             loginStep1: false, // 1차 인증
             loginStep2: false, // 2차 인증
+
+            storageType: null, // 로컬 스토리지 타입 (세션/로컬)
         };
     },
     methods: {
@@ -184,6 +186,7 @@
         // 로그인 성공 시
         async loginSuccessProc(res) {
             const loginType = res.headers['login-type']; // 세션/토큰 로그인 구분
+            this.storageType = res.headers['storage-type']; // 로컬 스토리지 타입 (세션/로컬)
             if (loginType === 'J') {
                 this.handleJWTLogin(res);
                 // JWT 방식
@@ -283,35 +286,35 @@
         // 인증 정보 저장
         setAuthInfo(loginMode, token, userInfo) {
             // Vuex 상태 저장
-            store.commit("setLoginMode", loginMode);
-            store.commit("setAuthorization", token);
-            store.commit("setMbrId", userInfo.mbrId);
-            store.commit("setMbrNm", userInfo.mbrNm);
-            store.commit("setRoles", userInfo.roles);
+            this.$store.commit("setLoginMode", loginMode);
+            this.$store.commit("setAuthorization", token);
+            this.$store.commit("setMbrId", userInfo.mbrId);
+            this.$store.commit("setMbrNm", userInfo.mbrNm);
+            this.$store.commit("setRoles", userInfo.roles);
 
             // localStorage 저장
-            localStorage.setItem("loginMode", loginMode);
-            localStorage.setItem("mbrId", userInfo.mbrId);
-            localStorage.setItem("mbrNm", userInfo.mbrNm);
-            localStorage.setItem("roles", JSON.stringify(userInfo.roles));
+            // localStorage.setItem("loginMode", loginMode);
+            // localStorage.setItem("mbrId", userInfo.mbrId);
+            // localStorage.setItem("mbrNm", userInfo.mbrNm);
+            // localStorage.setItem("roles", JSON.stringify(userInfo.roles));
 
-            if (token) {
-                localStorage.setItem("authorization", token);
-            } else {
-                localStorage.removeItem("authorization");
-            }
+            // if (token) {
+            //     localStorage.setItem("authorization", token);
+            // } else {
+            //     localStorage.removeItem("authorization");
+            // }
         },
 
         // ========== 로그인 성공 후 처리 ==========
         async handleLoginSuccess() {
-            const isAdmin = store.state.roles.some(role => role.authority === "ROLE_ADMIN");
+            const isAdmin = this.$store.state.roles.some(role => role.authority === "ROLE_ADMIN");
             let redirectUrl = this.restoreRedirect("redirect")
             // 리다이렉트 URL 정리
             if (redirectUrl && this.shouldRedirectToMain(redirectUrl)) {
                 redirectUrl = this.$filters.ctxPath("/");
             }
             // Context Path 처리
-            if (redirectUrl && !redirectUrl.startsWith(store.state.contextPath) && store.state.contextPath) {
+            if (redirectUrl && !redirectUrl.startsWith(this.$store.state.contextPath) && this.$store.state.contextPath) {
                 redirectUrl = this.$filters.ctxPath(redirectUrl);
             }
 
client/views/pages/login/Login.vue
--- client/views/pages/login/Login.vue
+++ client/views/pages/login/Login.vue
@@ -74,7 +74,7 @@
 
 <script>
 import { useStore } from "vuex";
-import store from "../AppStore";
+// import store from "../AppStore";
 import { loginProc, getUserInfo, oauthLogin } from "../../../resources/api/login";
 import { check2ndAuthProc, sendAuthEmailProc } from "../../../resources/api/email";
 import queryParams from "../../../resources/js/queryParams";
@@ -86,7 +86,7 @@
     return {
       isLoading: false,
       member: { lgnId: null, pswd: null, lgnReqPage: 'U' },
-      store: useStore(),
+      // store: useStore(),
       isAdminPage: false,
       isOAuthLoading: false,
       memberInfo: { email: '', code: '' },
@@ -251,10 +251,10 @@
 
       try {
         // 기존 시스템 로그인 모드 따라가기
-        const finalLoginMode = loginMode || store.state.loginMode || localStorage.getItem('loginMode') || 'J';
+        const finalLoginMode = loginMode || this.$store.state.loginMode || localStorage.getItem('loginMode') || 'J';
         
-        store.commit("setLoginMode", finalLoginMode);
-        localStorage.setItem("loginMode", finalLoginMode);
+        this.$store.commit("setLoginMode", finalLoginMode);
+        // localStorage.setItem("loginMode", finalLoginMode);
 
         if (finalLoginMode === 'J') {
           await this.handleOAuthJWT();
@@ -351,39 +351,39 @@
     setAuthInfo(loginMode, token, userInfo) {
       // Store 설정
       try {
-        if (typeof store !== 'undefined' && store.commit) {
-          store.commit("setLoginMode", loginMode);
-          store.commit("setAuthorization", token);
-          store.commit("setMbrId", userInfo.mbrId);
-          store.commit("setMbrNm", userInfo.mbrNm);
-          store.commit("setRoles", userInfo.roles);
+        if (typeof this.$store !== 'undefined' && this.$store.commit) {
+          this.$store.commit("setLoginMode", loginMode);
+          this.$store.commit("setAuthorization", token);
+          this.$store.commit("setMbrId", userInfo.mbrId);
+          this.$store.commit("setMbrNm", userInfo.mbrNm);
+          this.$store.commit("setRoles", userInfo.roles);
         }
       } catch (e) {
         console.warn("store 설정 실패, localStorage만 사용:", e);
       }
 
       // localStorage 저장
-      localStorage.setItem("loginMode", loginMode);
-      localStorage.setItem("mbrId", userInfo.mbrId);
-      localStorage.setItem("mbrNm", userInfo.mbrNm);
-      localStorage.setItem("roles", JSON.stringify(userInfo.roles));
+      // localStorage.setItem("loginMode", loginMode);
+      // localStorage.setItem("mbrId", userInfo.mbrId);
+      // localStorage.setItem("mbrNm", userInfo.mbrNm);
+      // localStorage.setItem("roles", JSON.stringify(userInfo.roles));
 
-      if (token && loginMode === 'J') {
-        localStorage.setItem("authorization", token);
-      } else {
-        localStorage.removeItem("authorization");
-      }
+      // if (token && loginMode === 'J') {
+      //   localStorage.setItem("authorization", token);
+      // } else {
+      //   localStorage.removeItem("authorization");
+      // }
     },
 
     async handleLoginSuccess() {
-      const isAdmin = store.state.roles.some(role => role.authority === "ROLE_ADMIN");
+      const isAdmin = this.$store.state.roles.some(role => role.authority === "ROLE_ADMIN");
       let redirectUrl = this.restoreRedirect("redirect") || sessionStorage.getItem('oauth_redirect');
 
       if (redirectUrl && this.shouldRedirectToMain(redirectUrl)) {
         redirectUrl = this.$filters.ctxPath("/");
       }
 
-      if (redirectUrl && !redirectUrl.startsWith(store.state.contextPath) && store.state.contextPath) {
+      if (redirectUrl && !redirectUrl.startsWith(this.$store.state.contextPath) && this.$store.state.contextPath) {
         redirectUrl = this.$filters.ctxPath(redirectUrl);
       }
 
client/views/pages/user/portal/main/Main.vue
--- client/views/pages/user/portal/main/Main.vue
+++ client/views/pages/user/portal/main/Main.vue
@@ -212,7 +212,6 @@
   
   <script>
   import ClusteredColumnChart from "../../../../component/chart/ClusteredColumnChart.vue";
-  import store from "../../../AppStore.js";
   
   // 통합검색 관련
   import queryParams from "../../../../../resources/js/queryParams.js";
@@ -262,9 +261,9 @@
           }
         ],
         search: { ...defaultTotalSearchParams },
-        mbrId: store.state.mbrId || null, // 사용자 아이디
-        roles: store.state.roles.map((auth) => auth.authority) || [], // 사용자 권한
-        menuType: store.state.userType || null, // 메뉴 타입
+        mbrId: this.$store.state.mbrId || null, // 사용자 아이디
+        roles: this.$store.state.roles.map((auth) => auth.authority) || [], // 사용자 권한
+        menuType: this.$store.state.userType || null, // 메뉴 타입
   
         // 공지사항 top5
         noticeTop5: [],
client/views/pages/user/portal/search/Search.vue
--- client/views/pages/user/portal/search/Search.vue
+++ client/views/pages/user/portal/search/Search.vue
@@ -254,7 +254,6 @@
 
 <script>
 import { toRaw } from 'vue'
-import store from "../../../AppStore";
 import queryParams from '../../../../../resources/js/queryParams';
 import { defaultTotalSearchParams } from "../../../../../resources/js/defaultTotalSearchParams.js"
 import { searchAll } from "../../../../../resources/api/search.js";
@@ -267,9 +266,9 @@
         return {
             search: { ...defaultTotalSearchParams },
             pageSearch: { ...defaultSearchParams },
-            mbrId: store.state.mbrId || null, // 사용자 아이디
-            roles: store.state.roles.map(auth => auth.authority) || [], // 사용자 권한
-            menuType: store.state.userType || null, // 메뉴 타입
+            mbrId: this.$store.state.mbrId || null, // 사용자 아이디
+            roles: this.$store.state.roles.map(auth => auth.authority) || [], // 사용자 권한
+            menuType: this.$store.state.userType || null, // 메뉴 타입
             inputSearchText: '',
             totalCount: 0,
             menuCount: 0,
Add a comment
List