박정하 박정하 04-07
250407 박정하 메뉴 통계 수정
@e27ae43020e704d63cd69d12d82e44f844080c3a
client/views/layout/Header.vue
--- client/views/layout/Header.vue
+++ client/views/layout/Header.vue
@@ -1,180 +1,135 @@
 <template>
-
-    <header>
-        <div class="header-container w1500">
-            <div class="logo-wrap">
-                <router-link :to="{ path: '/' }" class="logo"><img :src="logo" alt=""></router-link>
-            </div>
-            <div class="nav-wrap">
-                <nav>
-                    <ul>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000001')">기록물
-                            <div class="submenu">
-                                <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
-                                <div class="hr"></div>
-                                <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
-                            </div>
-                        </li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시
-                            <div class="submenu">
-                                <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000005')">미디어 영상</router-link></p>
-                                <div class="hr"></div>
-                                <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000006')">보도자료</router-link></p>
-                            </div>
-                        </li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000007')"><router-link
-                                :to="{ path: '/MemberManagement.page' }">회원관리</router-link></li>
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"
-                            @click="updateMenuStats('MENU_00000008')"><router-link
-                                :to="{ path: '/CategoryManagement.page' }">카테고리 관리</router-link></li>
-
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'"
-                            @click="updateMenuStats('MENU_00000001')">기록물
-                            <div class="submenu">
-                                <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
-                                <div class="hr"></div>
-                                <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
-                            </div>
-                        </li>
-                            
-                        <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'"
-                            @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시
-                            <div class="submenu">
-                                <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000005')">미디어 영상</router-link></p>
-                                <div class="hr"></div>
-                                <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000006')">보도자료</router-link></p>
-                            </div>
-                        </li>
-                    </ul>
-                </nav>
-            </div>
-            <div class="auth-area">
-                <ul v-if="$store.state.userId != null">
-                    <li><img src="../../resources/images/icon/user-settings-line.png" alt="">
-                        <router-link :to="{ path: '/MyInfo.page' }">{{ $store.state.userNm }}</router-link>
-                    </li>
-                    <li>
-                        <div class="line"></div>
-                    </li>
-                    <li><img src="../../resources/images/icon/logout-box-line.png" alt="">
-                        <a href="#" @click.prevent="logout">로그아웃</a>
-                    </li>
-                </ul>
-                <a href="#" class="all-menu-btn" @click="toggleMenu"><img src="../../resources/images/allmenu.png"
-                        alt=""></a>
-
-            </div>
-        </div>
-        <div class="overlay"  v-if="isMenuOpen">
-            <div class="all-menu">
-    <button @click="closeMenu" class="closebtn">✕</button>
-    <div class="nav-wrap">
-        <nav>
-            <ul>
-                <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000001')">
-                    <h6>기록물</h6>
-                    <div class="submenu">
-                        <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
-                        <div class="hr pink"></div>
-                        <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
-                    </div>
-                </li>
-                <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000004')">
-                    <h6>언론에서 바라본 구미시</h6>
-                    <div class="submenu">
-                        <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000004')">미디어 영상</router-link></p>
-                        <div class="hr pink"></div>
-                        <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000005')">보도자료</router-link></p>
-                    </div>
-                </li>
-                <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000007')">
-                    <h6>회원관리</h6>
-                    <div class="submenu">
-                        <p>• <router-link :to="{ path: '/MemberManagement.page' }" @click="closeMenu">회원관리</router-link></p>
-                    </div>
-                </li>
-                <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000008')">
-                    <h6>카테고리 관리</h6>
-                    <div class="submenu">
-                        <p>• <router-link :to="{ path: '/CategoryManagement.page' }" @click="closeMenu">카테고리 관리</router-link></p>
-                    </div>
-                </li>
-
-                <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000001')">
-                    <h6>기록물</h6>
-                    <div class="submenu">
-                        <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" @click="updateMenuStats('MENU_00000002')">사진 기록물</router-link></p>
-                        <div class="hr pink"></div>
-                        <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" @click="updateMenuStats('MENU_00000003')">영상 기록물</router-link></p>
-                    </div>
-                </li>
-                <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000004')">
-                    <h6>언론에서 바라본 구미시</h6>
-                    <div class="submenu">
-                        <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" @click="updateMenuStats('MENU_00000004')">미디어 영상</router-link></p>
-                        <div class="hr pink"></div>
-                        <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" @click="updateMenuStats('MENU_00000005')">보도자료</router-link></p>
-                    </div>
-                </li>
+  <header>
+    <div class="header-container w1500">
+      <div class="logo-wrap">
+        <router-link :to="{ path: '/' }" class="logo"><img :src="logo" alt=""></router-link>
+      </div>
+      <template v-if="!$isEmpty($store.state.userId)">
+        <div class="nav-wrap">
+          <nav>
+            <ul v-if="$store.state.roles.length > 0">
+              <li @click="updateMenuStats('MENU_00000001')">기록물 <div class="submenu">
+                  <p>• <router-link :to="{ path: '/PicHistorySearch.page' }">사진 기록물</router-link></p>
+                  <div class="hr"></div>
+                  <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }">영상 기록물</router-link></p>
+                </div>
+              </li>
+              <li @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시 <div class="submenu">
+                  <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }">미디어 영상</router-link></p>
+                  <div class="hr"></div>
+                  <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }">보도자료</router-link></p>
+                </div>
+              </li>
+              <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"><router-link :to="{ path: '/MemberManagement.page' }">회원관리</router-link></li>
+              <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'"><router-link :to="{ path: '/CategoryManagement.page' }">카테고리 관리</router-link></li>
             </ul>
-        </nav>
-    </div>
-</div>
-
+          </nav>
         </div>
-    </header>
+        <div class="auth-area">
+          <ul>
+            <li><img src="../../resources/images/icon/user-settings-line.png" alt="">
+              <router-link :to="{ path: '/MyInfo.page' }">{{ $store.state.userNm }}</router-link>
+            </li>
+            <li>
+              <div class="line"></div>
+            </li>
+            <li><img src="../../resources/images/icon/logout-box-line.png" alt="">
+              <a href="#" @click.prevent="logout">로그아웃</a>
+            </li>
+          </ul>
+          <a href="#" class="all-menu-btn" @click="toggleMenu"><img src="../../resources/images/allmenu.png" alt=""></a>
+        </div>
+      </template>
+    </div>
+    <div class="overlay" v-if="isMenuOpen">
+      <div class="all-menu">
+        <button @click="closeMenu" class="closebtn">✕</button>
+        <div class="nav-wrap">
+          <nav>
+            <ul>
+              <li @click="updateMenuStats('MENU_00000001')">
+                <h6>기록물</h6>
+                <div class="submenu">
+                  <p>• <router-link :to="{ path: '/PicHistorySearch.page' }">사진 기록물</router-link></p>
+                  <div class="hr pink"></div>
+                  <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }">영상 기록물</router-link></p>
+                </div>
+              </li>
+              <li @click="updateMenuStats('MENU_00000004')">
+                <h6>언론에서 바라본 구미시</h6>
+                <div class="submenu">
+                  <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }">미디어 영상</router-link></p>
+                  <div class="hr pink"></div>
+                  <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }">보도자료</router-link></p>
+                </div>
+              </li>
+              <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'">
+                <h6>회원관리</h6>
+                <div class="submenu">
+                  <p>• <router-link :to="{ path: '/MemberManagement.page' }" @click="closeMenu">회원관리</router-link></p>
+                </div>
+              </li>
+              <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'">
+                <h6>카테고리 관리</h6>
+                <div class="submenu">
+                  <p>• <router-link :to="{ path: '/CategoryManagement.page' }" @click="closeMenu">카테고리 관리</router-link></p>
+                </div>
+              </li>
+            </ul>
+          </nav>
+        </div>
+      </div>
+    </div>
+  </header>
 </template>
 <script>
 import { logOutProc } from "../../resources/api/user"
 import { updateStatsByMenuId } from "../../resources/api/main"
 export default {
-    data() {
-        return {
-            isMenuOpen: false,
-            // Define the image sources
-            logo: 'client/resources/images/logo.png',
-        };
+  data() {
+    return {
+      isMenuOpen: false,
+      // Define the image sources
+      logo: 'client/resources/images/logo.png',
+    };
+  },
+  methods: {
+    toggleMenu() {
+      this.isMenuOpen = !this.isMenuOpen;
     },
-    methods: {
-        toggleMenu() {
-            this.isMenuOpen = !this.isMenuOpen;
-        },
-        // 메뉴 닫기
-        closeMenu() {
-            this.isMenuOpen = false;
-        },
-        async updateMenuStats(menuId) {
-            try {
-                const response = await updateStatsByMenuId(menuId);
-                if (response.status === 200) {
-                    console.log(`메뉴 ID ${menuId} 통계 업데이트 성공`);
-                }
-            } catch (error) {
-                console.error(`메뉴 ID ${menuId} 통계 업데이트 중 오류:`, error);
-            }
-        },
-        logout() {
-            // 백엔드 로그아웃 API 호출
-            logOutProc()
-                .then(() => {
-                    console.log('로그아웃 성공 - 서버 측 쿠키 삭제 완료');
-                    this.$store.commit('setStoreReset'); // 로그아웃 성공 후 스토어 초기화
-                    this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
-                })
-                .catch(err => {
-                    console.error('로그아웃 처리 중 오류:', err);
-                    this.$store.commit('setStoreReset'); // 오류가 있어도 스토어는 초기화
-                    this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
-                });
-        },
+    // 메뉴 닫기
+    closeMenu() {
+      this.isMenuOpen = false;
+    },
+    async updateMenuStats(menuId) {
+      try {
+        const response = await updateStatsByMenuId(menuId);
+        if (response.status === 200) {
+          console.log(`메뉴 ID ${menuId} 통계 업데이트 성공`);
+        }
+      } catch (error) {
+        console.error(`메뉴 ID ${menuId} 통계 업데이트 중 오류:`, error);
+      }
+    },
+    logout() {
+      // 백엔드 로그아웃 API 호출
+      logOutProc()
+        .then(() => {
+          console.log('로그아웃 성공 - 서버 측 쿠키 삭제 완료');
+          this.$store.commit('setStoreReset'); // 로그아웃 성공 후 스토어 초기화
+          this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
+        })
+        .catch(err => {
+          console.error('로그아웃 처리 중 오류:', err);
+          this.$store.commit('setStoreReset'); // 오류가 있어도 스토어는 초기화
+          this.$router.push({ path: '/Login.page' }); // 로그인 페이지로 리다이렉트
+        });
+    },
 
-    },
-    watch: {},
-    computed: {},
-    components: {},
-    mounted() { },
+  },
+  watch: {},
+  computed: {},
+  components: {},
+  mounted() { },
 };
 </script>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -1,5 +1,6 @@
 import { createWebHistory, createRouter } from "vue-router";
 import store from "./AppStore";
+import { updateStatsByMenuId } from "../../resources/api/main";
 
 // 공통
 import Main from "./main/Main.vue";
@@ -27,68 +28,144 @@
 // 카테고리 관리
 import CategoryManagement from "./ctgry/CategoryManagement.vue";
 
+// 메뉴 ID 매핑
+const pathToMenuIdMap = {
+  '/PicHistorySearch.page': 'MENU_00000002',
+  '/VideoHistorySearch.page': 'MENU_00000003',
+  '/MediaVideoSearch.page': 'MENU_00000005',
+  '/NewsReleaseSearch.page': 'MENU_00000006',
+  '/MemberManagement.page': 'MENU_00000007',
+  '/CategoryManagement.page': 'MENU_00000008'
+};
+
 const routes = [
   // 공통
-  { path: "/", name: "MainPage", component: Main, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } },
-  { path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch },
+  {
+    path: "/", name: "MainPage", component: Main,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
   // 회원관리
-  { path: "/Login.page", name: "Login", component: Login },
-  { path: "/MyInfo.page", name: "MyInfo", component: MyInfo, meta: { authorization: ['ROLE_ADMIN', 'ROLE_USER'] } },
-  { path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement },
+  {
+    path: "/Login.page", name: "Login", component: Login,
+    meta: { requiresAuth: false, }
+  },
+  {
+    path: "/MyInfo.page", name: "MyInfo", component: MyInfo,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN'] }
+  },
   // 기록물 - 사진
-  { path: "/PicHistorySearch.page", name: "PicHistorySearch", component: PicHistorySearch },
-  { path: "/PicHistoryInsert.page", name: "PicHistoryInsert", component: PicHistoryInsert },
-  { path: "/PicHistoryDetail.page", name: "PicHistoryDetail", component: PicHistoryDetail },
+  {
+    path: "/PicHistorySearch.page", name: "PicHistorySearch", component: PicHistorySearch,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/PicHistoryInsert.page", name: "PicHistoryInsert", component: PicHistoryInsert,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/PicHistoryDetail.page", name: "PicHistoryDetail", component: PicHistoryDetail,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
   // 기록물 - 영상
-  { path: "/VideoHistorySearch.page", name: "VideoHistorySearch", component: VideoHistorySearch },
-  { path: "/VideoHistoryInsert.page", name: "VideoHistoryInsert", component: VideoHistoryInsert },
-  { path: "/VideoHistoryDetail.page", name: "VideoHistoryDetail", component: VideoHistoryDetail },
+  {
+    path: "/VideoHistorySearch.page", name: "VideoHistorySearch", component: VideoHistorySearch,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/VideoHistoryInsert.page", name: "VideoHistoryInsert", component: VideoHistoryInsert,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/VideoHistoryDetail.page", name: "VideoHistoryDetail", component: VideoHistoryDetail,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
   // 미디어 영상
-  { path: "/MediaVideoSearch.page", name: "MediaVideoSearch", component: MediaVideoSearch },
-  { path: "/MediaVideoInsert.page", name: "MediaVideoInsert", component: MediaVideoInsert },
-  { path: "/MediaVideoDetail.page", name: "MediaVideoDetail", component: MediaVideoDetail },
+  {
+    path: "/MediaVideoSearch.page", name: "MediaVideoSearch", component: MediaVideoSearch,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/MediaVideoInsert.page", name: "MediaVideoInsert", component: MediaVideoInsert,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/MediaVideoDetail.page", name: "MediaVideoDetail", component: MediaVideoDetail,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
   // 보도자료
-  { path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch },
-  { path: "/NewsReleaseInsert.page", name: "NewsReleaseInsert", component: NewsReleaseInsert },
-  { path: "/NewsReleaseDetail.page", name: "NewsReleaseDetail", component: NewsReleaseDetail },
+  {
+    path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/NewsReleaseInsert.page", name: "NewsReleaseInsert", component: NewsReleaseInsert,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
+  {
+    path: "/NewsReleaseDetail.page", name: "NewsReleaseDetail", component: NewsReleaseDetail,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
+  },
   // 카테고리 관리
-  { path: "/CategoryManagement.page", name: "CategoryManagement", component: CategoryManagement },
+  {
+    path: "/CategoryManagement.page", name: "CategoryManagement", component: CategoryManagement,
+    meta: { requiresAuth: true, roles: ['ROLE_ADMIN'] }
+  },
 ];
 
 const AppRouter = createRouter({
   history: createWebHistory(),
   routes,
-  // 모든 라우트 이동 후 페이지 상단으로 스크롤
   scrollBehavior() {
     return { top: 0 }
   },
 });
 
-AppRouter.beforeEach((to, from, next) => {
-  const routeExists = AppRouter.getRoutes().some(route => route.path === to.path || (route.name && route.name === to.name));
-  const userId = store.state.userId;
-  const { authorization } = to.meta;
+AppRouter.beforeEach(async (to, from, next) => {
+  // 인증 필요 여부 확인
+  const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false);
 
-  // 로그인 상태 확인
-  const isLoggedIn = userId;
-
-  // 로그인되지 않은 경우
-  if (isLoggedIn == null && to.path !== '/Login.page') {
+  // 로그인 여부
+  const isLoggedIn = !!store.state.authorization && !!store.state.userId;
+  // 로그인이 필요한데 로그인이 안되어 있는 경우
+  if (requiresAuth && !isLoggedIn) {
     alert('로그인이 필요합니다.');
-    return next('/Login.page'); // 로그인 페이지로 리다이렉트
+    return next('/Login.page');
   }
 
-  // 권한이 없을 경우
-  if (authorization) {
-    if (!authorization.includes(store.state.roles[0].authority)) {
+  // 이미 로그인했는데 로그인 페이지로 가려는 경우
+  if (isLoggedIn && to.path === '/Login.page') {
+    return next('/');
+  }
+
+  // 권한 체크
+  if (isLoggedIn && to.meta.roles) {
+    const userRole = store.state.roles?.[0]?.authority;
+
+    if (!userRole || !to.meta.roles.includes(userRole)) {
       alert('접근 권한이 없습니다.');
       return next(from.path);
     }
   }
-  if (!routeExists) {
-    next('/');
-    return;
+
+  // 메뉴 통계 업데이트
+  const menuId = pathToMenuIdMap[to.path];
+  if (menuId) {
+    try {
+      await updateStatsByMenuId(menuId);
+      console.log(`메뉴 통계 업데이트 성공: ${menuId}`);
+    } catch (error) {
+      console.log(`메뉴 통계 업데이트 실패: ${menuId}`, error);
+    }
   }
+
   next();
 });
 
Add a comment
List