From e5f5a926a3815afcbd59255afbcc8d24d6fec39f Mon Sep 17 00:00:00 2001 From: sohot8653 Date: Tue, 23 Sep 2025 15:21:00 +0900 Subject: [PATCH] =?UTF-8?q?[=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=9E=91=EC=97=85=EC=A4=91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/layout/AppHeader.vue | 6 +- composables/usePermission.ts | 16 +-- layouts/default.vue | 5 +- pages/admin/permission-test.vue | 78 +++++------- stores/permissions.ts | 38 +++--- types/permissions.ts | 212 +++++++++++++++++++++----------- 6 files changed, 205 insertions(+), 150 deletions(-) diff --git a/components/layout/AppHeader.vue b/components/layout/AppHeader.vue index fe766ff..b3c5756 100644 --- a/components/layout/AppHeader.vue +++ b/components/layout/AppHeader.vue @@ -89,10 +89,10 @@ onMounted(async () => { await permissionStore.fetchPermissions(); }); -// 권한이 있는 메뉴들만 필터링 +// 권한이 있는 페이지그룹들만 필터링 const availableMenus = computed(() => { - return permissionStore.permissions.resources.menus.filter(menu => { - return permissionStore.hasMenuPermission(menu.code); + return permissionStore.permissions.resources.pageGroups.filter(pageGroup => { + return permissionStore.hasPageGroupPermission(pageGroup.code); }); }); diff --git a/composables/usePermission.ts b/composables/usePermission.ts index 33cb922..c2d7b68 100644 --- a/composables/usePermission.ts +++ b/composables/usePermission.ts @@ -10,9 +10,9 @@ export const usePermission = () => { hasPagePermission: (page: string) => permissionsStore.hasPagePermission(page), - // 메뉴 권한 체크 - hasMenuPermission: (menu: string) => - permissionsStore.hasMenuPermission(menu), + // 페이지그룹 권한 체크 + hasPageGroupPermission: (pageGroup: string) => + permissionsStore.hasPageGroupPermission(pageGroup), // 컴포넌트 권한 체크 hasComponentPermission: (component: string) => @@ -21,16 +21,18 @@ export const usePermission = () => { // 여러 권한 중 하나라도 있는지 체크 hasAnyPagePermission: (pages: string[]) => permissionsStore.hasAnyPagePermission(pages), - hasAnyMenuPermission: (menus: string[]) => - permissionsStore.hasAnyMenuPermission(menus), + hasAnyPageGroupPermission: (pageGroups: string[]) => + permissionsStore.hasAnyPageGroupPermission(pageGroups), hasAnyComponentPermission: (components: string[]) => permissionsStore.hasAnyComponentPermission(components), // 모든 권한이 있는지 체크 hasAllPagePermissions: (pages: string[]) => pages.every(page => permissionsStore.hasPagePermission(page)), - hasAllMenuPermissions: (menus: string[]) => - menus.every(menu => permissionsStore.hasMenuPermission(menu)), + hasAllPageGroupPermissions: (pageGroups: string[]) => + pageGroups.every(pageGroup => + permissionsStore.hasPageGroupPermission(pageGroup) + ), hasAllComponentPermissions: (components: string[]) => components.every(component => permissionsStore.hasComponentPermission(component) diff --git a/layouts/default.vue b/layouts/default.vue index 927a39d..8f79e6d 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -26,12 +26,13 @@ watch(activeMenu, newValue => { const subMenus = computed(() => { if (activeMenu.value === "HOME") return []; - // 활성 메뉴의 코드 찾기 (M01, M02 등) + // 활성 메뉴의 코드 찾기 (PG01, PG02 등) const activeMenuCode = activeMenu.value; - // 해당 메뉴의 하위 페이지들 필터링 + // 해당 페이지그룹의 하위 페이지들 필터링 (menu_yn이 "Y"인 것만) return permissionStore.permissions.resources.pages .filter(page => page.parentCode === activeMenuCode) + .filter(page => page.menuYn === "Y") // 메뉴에 표시할 페이지만 .filter(page => permissionStore.hasPagePermission(page.path || "")) .sort((a, b) => a.sortOrder - b.sortOrder) .map(page => ({ diff --git a/pages/admin/permission-test.vue b/pages/admin/permission-test.vue index 36115a1..0440f1f 100644 --- a/pages/admin/permission-test.vue +++ b/pages/admin/permission-test.vue @@ -105,16 +105,18 @@

- 관리자 메뉴 권한(M01): + 테스트 페이지그룹 권한(PG01): {{ - permission.hasMenuPermission("M01") ? "있음" : "없음" + permission.hasPageGroupPermission("PG01") + ? "있음" + : "없음" }}

@@ -214,7 +216,7 @@ - +
3

- 메뉴 권한 테스트 + 페이지그룹 권한 테스트

- 메뉴 권한에 따라 메뉴가 표시되거나 숨겨지는 것을 확인합니다. + 페이지그룹 권한에 따라 메뉴가 표시되거나 숨겨지는 것을 확인합니다.

- 관리자 메뉴 + 테스트 페이지그룹
관리자 메뉴 (권한 없음)테스트 페이지그룹 (권한 없음)
- 사용자 메뉴 + 관리자 페이지그룹
사용자 메뉴 (권한 없음) -
- -
-
- 설정 메뉴 -
- 설정 메뉴 (권한 없음) -
- -
-
- 보고서 메뉴 -
- 보고서 메뉴 (권한 없음)관리자 페이지그룹 (권한 없음)
@@ -324,21 +302,21 @@

- 메뉴 권한 체크: + 페이지그룹 권한 체크:

- hasMenuPermission('M000001') + hasPageGroupPermission('PG01')

{{ - permission.hasMenuPermission("M000001") ? "true" : "false" + permission.hasPageGroupPermission("PG01") ? "true" : "false" }}

@@ -500,17 +478,21 @@
- +
-

메뉴 권한

+

+ 페이지그룹 권한 +

@@ -553,8 +535,8 @@ 권한이 없으면 홈으로 리다이렉트됩니다.

- 메뉴 권한: 메뉴 표시 여부를 제어합니다. 권한이 - 없으면 메뉴가 숨겨집니다. + 페이지그룹 권한: 페이지그룹 표시 여부를 제어합니다. + 권한이 없으면 페이지그룹이 숨겨집니다.

컴포넌트 권한: 버튼 등 UI 컴포넌트의 표시 여부를 diff --git a/stores/permissions.ts b/stores/permissions.ts index da01b9f..9eec734 100644 --- a/stores/permissions.ts +++ b/stores/permissions.ts @@ -7,7 +7,7 @@ export const usePermissionsStore = defineStore( // 권한 데이터 상태 const permissions = ref({ resources: { - menus: [], + pageGroups: [], pages: [], components: [], }, @@ -48,7 +48,7 @@ export const usePermissionsStore = defineStore( permissions.value = { resources: { - menus: [...MOCK_PERMISSIONS.resources.menus], + pageGroups: [...MOCK_PERMISSIONS.resources.pageGroups], pages: [...MOCK_PERMISSIONS.resources.pages], components: [...MOCK_PERMISSIONS.resources.components], }, @@ -70,8 +70,10 @@ export const usePermissionsStore = defineStore( .filter(Boolean) as string[]; }; - const getMenuCodes = (): string[] => { - return permissions.value.resources.menus.map(menu => menu.code); + const getPageGroupCodes = (): string[] => { + return permissions.value.resources.pageGroups.map( + pageGroup => pageGroup.code + ); }; const getComponentCodes = (): string[] => { @@ -85,8 +87,8 @@ export const usePermissionsStore = defineStore( return getPagePaths().includes(page); }; - const hasMenuPermission = (menu: string): boolean => { - return getMenuCodes().includes(menu); + const hasPageGroupPermission = (pageGroup: string): boolean => { + return getPageGroupCodes().includes(pageGroup); }; const hasComponentPermission = (component: string): boolean => { @@ -98,8 +100,8 @@ export const usePermissionsStore = defineStore( return pages.some(page => hasPagePermission(page)); }; - const hasAnyMenuPermission = (menus: string[]): boolean => { - return menus.some(menu => hasMenuPermission(menu)); + const hasAnyPageGroupPermission = (pageGroups: string[]): boolean => { + return pageGroups.some(pageGroup => hasPageGroupPermission(pageGroup)); }; const hasAnyComponentPermission = (components: string[]): boolean => { @@ -134,14 +136,14 @@ export const usePermissionsStore = defineStore( const findResourceByCode = ( resources: { - menus: Resource[]; + pageGroups: Resource[]; pages: Resource[]; components: Resource[]; }, code: string ): Resource | undefined => { const allResources = [ - ...resources.menus, + ...resources.pageGroups, ...resources.pages, ...resources.components, ]; @@ -149,7 +151,7 @@ export const usePermissionsStore = defineStore( if (resource.code === code) return resource; if (resource.children) { const found = findResourceByCode( - { menus: [], pages: [], components: resource.children }, + { pageGroups: [], pages: [], components: resource.children }, code ); if (found) return found; @@ -160,14 +162,14 @@ export const usePermissionsStore = defineStore( const findResourceByPath = ( resources: { - menus: Resource[]; + pageGroups: Resource[]; pages: Resource[]; components: Resource[]; }, path: string ): Resource | undefined => { const allResources = [ - ...resources.menus, + ...resources.pageGroups, ...resources.pages, ...resources.components, ]; @@ -175,7 +177,7 @@ export const usePermissionsStore = defineStore( if (resource.path === path) return resource; if (resource.children) { const found = findResourceByPath( - { menus: [], pages: [], components: resource.children }, + { pageGroups: [], pages: [], components: resource.children }, path ); if (found) return found; @@ -188,7 +190,7 @@ export const usePermissionsStore = defineStore( const clearPermissions = () => { permissions.value = { resources: { - menus: [], + pageGroups: [], pages: [], components: [], }, @@ -206,15 +208,15 @@ export const usePermissionsStore = defineStore( // 기존 호환성 함수들 hasPagePermission, - hasMenuPermission, + hasPageGroupPermission, hasComponentPermission, hasAnyPagePermission, - hasAnyMenuPermission, + hasAnyPageGroupPermission, hasAnyComponentPermission, // 헬퍼 함수들 getPagePaths, - getMenuCodes, + getPageGroupCodes, getComponentCodes, // 새로운 계층 구조 권한 체크 함수들 diff --git a/types/permissions.ts b/types/permissions.ts index 94272ea..569880e 100644 --- a/types/permissions.ts +++ b/types/permissions.ts @@ -1,27 +1,28 @@ /** * 권한 시스템 타입 정의 * - * 2자리 계층 코드 시스템을 사용하여 메뉴 > 페이지 > 컴포넌트 계층 구조를 표현합니다. + * 2자리 계층 코드 시스템을 사용하여 페이지그룹 > 페이지 > 컴포넌트 계층 구조를 표현합니다. * * 코드 규칙: - * - 메뉴: M01, M02, M03, M04... - * - 페이지: P0101 (M01 하위), P0201 (M02 하위), P0501 (독립 페이지)... + * - 페이지그룹: PG01, PG02, PG03, PG04... + * - 페이지: P0101 (PG01 하위), P0201 (PG02 하위), P0501 (독립 페이지)... * - 컴포넌트: C010101 (P0101 하위), C010102 (P0101 하위)... */ -// 리소스 타입 정의 (메뉴, 페이지, 컴포넌트) -export type ResourceType = "menu" | "page" | "component"; +// 리소스 타입 정의 (페이지그룹, 페이지, 컴포넌트) +export type ResourceType = "PAGE_GROUP" | "PAGE" | "COMPONENT"; // 개별 리소스 객체 (계층 구조 지원) export interface Resource { oid: number; // OID (고유 식별자) - code: string; // 2자리 계층 코드 (M01, P0101, C010101) + code: string; // 2자리 계층 코드 (PG01, P0101, C010101) name: string; // 리소스 이름 parentCode?: string; // 부모 리소스 코드 (계층 구조) type: ResourceType; // 리소스 타입 - path?: string; // 페이지 경로 (페이지 타입만) + path?: string; // 페이지 경로 (PAGE 타입만) sortOrder: number; // 정렬 순서 description?: string; // 리소스 설명 + menuYn?: string; // 메뉴 여부 (char(1)) componentType?: string; // 컴포넌트 세부 타입 (버튼, 그리드 등) children?: Resource[]; // 자식 리소스들 (계층 구조) } @@ -29,7 +30,7 @@ export interface Resource { // 사용자 권한 구조 (계층적 리소스 관리) export interface UserPermissions { resources: { - menus: Resource[]; // 메뉴 리소스들 (M01, M02...) + pageGroups: Resource[]; // 페이지그룹 리소스들 (PG01, PG02...) pages: Resource[]; // 페이지 리소스들 (P0101, P0201...) components: Resource[]; // 컴포넌트 리소스들 (C010101, C010102...) }; @@ -39,7 +40,7 @@ export interface UserPermissions { export interface PermissionCheckResult { // 기존 호환성 함수들 (경로/코드 기반) hasPagePermission: (page: string) => boolean; // 페이지 경로로 권한 체크 - hasMenuPermission: (menu: string) => boolean; // 메뉴 코드로 권한 체크 + hasPageGroupPermission: (pageGroup: string) => boolean; // 페이지그룹 코드로 권한 체크 hasComponentPermission: (component: string) => boolean; // 컴포넌트 코드로 권한 체크 // 계층 구조를 고려한 권한 체크 함수들 @@ -52,7 +53,7 @@ export interface PermissionCheckResult { // 권한 종류별 상수 (기존 호환성 유지용) export const PERMISSION_TYPES = { PAGE: "pagePermissions", - MENU: "menuPermissions", + PAGE_GROUP: "pageGroupPermissions", COMPONENT: "componentPermissions", } as const; @@ -62,218 +63,291 @@ export const PERMISSION_TYPES = { * 2자리 계층 코드 시스템을 사용한 실제 권한 데이터 예시입니다. * * 계층 구조: - * - M01 (관리자 메뉴) > P0101~P0105 (관리자 페이지들) > C010101~C010106 (컴포넌트들) - * - M02 (사용자 메뉴) > P0201~P0202 (사용자 페이지들) + * - PG01 (테스트 페이지그룹) > P0101~P0115 (테스트 페이지들) > C010101~C010106 (컴포넌트들) + * - PG02 (관리자 페이지그룹) > P0201~P0203 (관리자 페이지들) * - P0501~P0504 (독립 페이지들: 홈, 로그인, 회원가입, 소개) * - P0601~P0602 (테스트 페이지들) * - P0701 (팝업 페이지들) */ export const MOCK_PERMISSIONS: UserPermissions = { resources: { - // 메뉴 리소스들 (최상위 레벨) - menus: [ + // 페이지그룹 리소스들 (최상위 레벨) + pageGroups: [ { oid: 1, - code: "M01", - name: "테스트 메뉴", - type: "menu", + code: "PG01", + name: "테스트", + type: "PAGE_GROUP", sortOrder: 1, - description: "테스트 관련 메뉴", + description: "테스트 관련 페이지그룹", + menuYn: "Y", }, { oid: 2, - code: "M02", - name: "관리자 메뉴", - type: "menu", + code: "PG02", + name: "관리자", + type: "PAGE_GROUP", sortOrder: 2, - description: "관리자 전용 메뉴", + description: "관리자 전용 페이지그룹", + menuYn: "Y", + }, + { + oid: 3, + code: "PG03", + name: "공통 팝업", + type: "PAGE_GROUP", + sortOrder: 2, + description: "공통 팝업 페이지그룹", + menuYn: "N", }, ], // 페이지 리소스들 (화면 메뉴 구성에 맞춤) pages: [ - // 테스트 메뉴 하위 페이지들 (M01 > P0101~P0115) + // 테스트 페이지그룹 하위 페이지들 (PG01 > P0101~P0115) { oid: 5, code: "P0101", name: "테스트", - type: "page", + type: "PAGE", path: "/test", - parentCode: "M01", + parentCode: "PG01", sortOrder: 1, description: "기본 테스트 페이지", + menuYn: "Y", + }, + { + oid: 50, + code: "P010101", + name: "테스트 등록 팝업", + type: "PAGE", + path: "/test/registerPopup", + parentCode: "P0101", + sortOrder: 1, + description: "기본 테스트 페이지", + menuYn: "Y", }, { oid: 6, code: "P0102", name: "ivg", - type: "page", + type: "PAGE", path: "/test/igv", - parentCode: "M01", + parentCode: "PG01", sortOrder: 2, description: "IGV 테스트 페이지", + menuYn: "Y", }, { oid: 7, code: "P0103", name: "ivg2", - type: "page", + type: "PAGE", path: "/test/igv2", - parentCode: "M01", + parentCode: "PG01", sortOrder: 3, description: "IGV2 테스트 페이지", + menuYn: "Y", }, { oid: 8, code: "P0104", name: "pathway", - type: "page", + type: "PAGE", path: "/test/pathway", - parentCode: "M01", + parentCode: "PG01", sortOrder: 4, description: "경로 분석 페이지", + menuYn: "Y", }, { oid: 9, code: "P0105", name: "pathway2", - type: "page", + type: "PAGE", path: "/test/pathway2", - parentCode: "M01", + parentCode: "PG01", sortOrder: 5, description: "경로 분석 페이지 2", + menuYn: "Y", }, { oid: 10, code: "P0106", name: "pathway3", - type: "page", + type: "PAGE", path: "/test/pathway3", - parentCode: "M01", + parentCode: "PG01", sortOrder: 6, description: "경로 분석 페이지 3", + menuYn: "Y", }, { oid: 11, code: "P0107", name: "pathway4", - type: "page", + type: "PAGE", path: "/test/pathway4", - parentCode: "M01", + parentCode: "PG01", sortOrder: 7, description: "경로 분석 페이지 4", + menuYn: "Y", }, { oid: 12, code: "P0108", name: "pathwayjson", - type: "page", + type: "PAGE", path: "/test/pathwayJson", - parentCode: "M01", + parentCode: "PG01", sortOrder: 8, description: "경로 분석 JSON 페이지", + menuYn: "Y", }, { oid: 13, code: "P0109", name: "배양그래프", - type: "page", + type: "PAGE", path: "/test/culture-graph", - parentCode: "M01", + parentCode: "PG01", sortOrder: 9, description: "배양 그래프 페이지", + menuYn: "Y", }, { oid: 14, code: "P0110", name: "배양그래프 멀티", - type: "page", + type: "PAGE", path: "/test/culture-graph-multi", - parentCode: "M01", + parentCode: "PG01", sortOrder: 10, description: "배양 그래프 멀티 페이지", + menuYn: "Y", }, { oid: 15, code: "P0111", name: "배양그래프 탭", - type: "page", + type: "PAGE", path: "/test/culture-graph-tab", - parentCode: "M01", + parentCode: "PG01", sortOrder: 11, description: "배양 그래프 탭 페이지", + menuYn: "Y", }, { oid: 16, code: "P0112", name: "tui-grid", - type: "page", + type: "PAGE", path: "/tui", - parentCode: "M01", + parentCode: "PG01", sortOrder: 12, description: "TUI 그리드 페이지", + menuYn: "Y", }, { oid: 17, code: "P0113", name: "리소스", - type: "page", + type: "PAGE", path: "/admin/resource", - parentCode: "M01", + parentCode: "PG01", sortOrder: 13, description: "리소스 관리 페이지", + menuYn: "Y", }, { oid: 18, code: "P0114", name: "sample", - type: "page", + type: "PAGE", path: "/sampleList", - parentCode: "M01", + parentCode: "PG01", sortOrder: 14, description: "샘플 목록 페이지", + menuYn: "Y", }, { oid: 19, code: "P0115", name: "공용 기능 테스트", - type: "page", + type: "PAGE", path: "/test/common-test", - parentCode: "M01", + parentCode: "PG01", sortOrder: 15, description: "공용 기능 테스트 페이지", + menuYn: "Y", + }, + { + oid: 25, + code: "P0116", + name: "등록", + type: "PAGE", + path: "/test/register", + parentCode: "PG01", + sortOrder: 16, + description: "테스트 등록 페이지", + menuYn: "N", }, - // 관리자 메뉴 하위 페이지들 (M02 > P0201~P0203) + // 관리자 페이지그룹 하위 페이지들 (PG02 > P0201~P0203) { oid: 20, code: "P0201", name: "접속기록", - type: "page", + type: "PAGE", path: "/admin/logs", - parentCode: "M02", + parentCode: "PG02", sortOrder: 1, description: "사용자 접속 기록 관리", + menuYn: "Y", }, { oid: 21, code: "P0202", name: "공통코드", - type: "page", + type: "PAGE", path: "/admin/codes", - parentCode: "M02", + parentCode: "PG02", sortOrder: 2, description: "공통 코드 관리", + menuYn: "Y", }, { oid: 22, code: "P0203", name: "프로그램", - type: "page", + type: "PAGE", path: "/admin/programs", - parentCode: "M02", + parentCode: "PG02", sortOrder: 3, description: "프로그램 관리", + menuYn: "Y", + }, + { + oid: 26, + code: "P0204", + name: "등록", + type: "PAGE", + path: "/admin/register", + parentCode: "PG02", + sortOrder: 4, + description: "관리자 등록 페이지", + menuYn: "N", + }, + { + oid: 27, + code: "P0301", + name: "부서 조회 팝업", + type: "PAGE", + path: "/test/departmentPopup", + parentCode: "PG03", + sortOrder: 1, + description: "부서 조회 팝업", + menuYn: "N", }, ], @@ -284,8 +358,7 @@ export const MOCK_PERMISSIONS: UserPermissions = { oid: 19, code: "C010101", name: "삭제 버튼", - type: "component", - componentType: "button", + type: "COMPONENT", parentCode: "P0101", sortOrder: 1, description: "테스트 삭제 버튼", @@ -294,8 +367,7 @@ export const MOCK_PERMISSIONS: UserPermissions = { oid: 20, code: "C010102", name: "수정 버튼", - type: "component", - componentType: "button", + type: "COMPONENT", parentCode: "P0101", sortOrder: 2, description: "테스트 수정 버튼", @@ -304,8 +376,7 @@ export const MOCK_PERMISSIONS: UserPermissions = { oid: 21, code: "C010103", name: "내보내기 버튼", - type: "component", - componentType: "button", + type: "COMPONENT", parentCode: "P0101", sortOrder: 3, description: "테스트 내보내기 버튼", @@ -314,8 +385,7 @@ export const MOCK_PERMISSIONS: UserPermissions = { oid: 22, code: "C010104", name: "가져오기 버튼", - type: "component", - componentType: "button", + type: "COMPONENT", parentCode: "P0101", sortOrder: 4, description: "테스트 가져오기 버튼", @@ -324,8 +394,7 @@ export const MOCK_PERMISSIONS: UserPermissions = { oid: 23, code: "C010105", name: "생성 버튼", - type: "component", - componentType: "button", + type: "COMPONENT", parentCode: "P0101", sortOrder: 5, description: "테스트 생성 버튼", @@ -334,8 +403,7 @@ export const MOCK_PERMISSIONS: UserPermissions = { oid: 24, code: "C010106", name: "보기 버튼", - type: "component", - componentType: "button", + type: "COMPONENT", parentCode: "P0101", sortOrder: 6, description: "테스트 상세보기 버튼",