FE 구조 파악 & 정리 · 기술 검토 결과물

ADM FE 구조 가이드 폴더·화면 구조 / 컴포넌트 재사용 / API 연동 / 수정 시 주의점 — 한눈에 파악용 참고 자료

orderhero-admin-front

스택
Vue 3 · Vuetify 3
템플릿
Vuexy 9.0.0
빌드
Vite · TypeScript
서버상태
TanStack Query
화면(.vue)
~468
기준 커밋
73be2af

한 줄 요약 — 상용 어드민 템플릿 Vuexy 기반. 파일 기반 라우팅(src/pages/** = 라우트), 화면 약 75개 도메인. 대부분 목록 화면의 검색은 다이나믹 서치 모듈 하나가 담당(~80화면 공유). 이 모듈 · axios 인터셉터 · 권한 스토어가 "건드리면 전역 파급" 3대 지점이다.

01

폴더 · 화면 구조

1-1. src 최상위 — 템플릿 vs 커스텀 구분이 핵심

가장 먼저 잡아야 할 축. 대문자 @core/@layouts = Vuexy 템플릿(건드리지 않음), 나머지는 오더히어로 팀 커스텀.

디렉토리역할출처
@core/Vuexy 코어 — 전역 컴포넌트·컴포저블·scss·config 스토어템플릿
@layouts/Vuexy 레이아웃 엔진 — 세로/가로 네비 렌더, casl.ts템플릿
layouts/실제 앱 레이아웃(default/blank) + 커스텀 확장커스텀
api/HTTP 레이어 전체 (→ 3장)커스텀
components/공용 컴포넌트 44개 하위폴더(테이블·모달·다이나믹서치·엑셀·폼)커스텀
composables/useConfirmModal·usePagination·useGroupSelection (auto-import)커스텀
core/소규모 커스텀 코어 — @core와 완전 별개, 혼동 주의커스텀
navigation/좌측 메뉴 정의(vertical/index.ts)커스텀
pages/파일 기반 라우팅 페이지 (~468 .vue)커스텀
plugins/부팅 플러그인(router·pinia·vueQuery·vuetify·iconify·msw)골격 +커스텀
store/Pinia 스토어 40여 도메인(order·product·permission·dynamicSearch)커스텀
utils/ · types/유틸·상수(constants_PermissionCode.ts) / 전역 타입커스텀
mocks/MSW 핸들러 — 현재 비활성 (→ 3-6)커스텀
@core(대문자·템플릿)core(소문자·커스텀) 는 다른 폴더다. 실수로 템플릿을 앱 코드처럼 고치는 사고가 바로 여기서 난다.

1-2. 라우팅 = 파일 기반 (unplugin-vue-router)

명시적 라우트 파일이 없다. src/pages/** 파일이 곧 라우트가 되고, 라우트명은 PascalCase→kebab-case로 변환된다(타입은 typed-router.d.ts에 자동 생성). 인증 가드는 plugins/1.router/index.tsbeforeEach(토큰 없거나 JWT 만료 → /login).

URL파일route name
/ordersrc/pages/order/index.vueorder
/mfcCenterManagesrc/pages/mfcCenterManage/index.vuemfc-center-manage
/manage/accountsrc/pages/manage/account/index.vuemanage-account
/loginsrc/pages/login.vuelogin
부작용: pages/<domain>/components/*.vue 도 라우트로 자동 등록된다(예: /order/components/OrderTable). 컴포넌트를 pages 하위에 두는 관례 탓 — 문제는 아니지만 인지 필요.

1-3. 주요 화면 도메인 (약 75개 폴더 · 좌측 메뉴 28그룹)

시스템 관리

manage/account · permission · menu · key-value · contents · message

주문 핵심

order(index/detail/csDetail) · orderStatus · placementOrder(발주) · recall

상품

product · productCategoryManage · vipPricingManage · segmentsManage

가맹점 / 파트너

restaurantInfo · partnerInfo · partnerList · gradeManage · insuranceContract

정산

settlement* ×9 · depositHistory · cashFlowRecords · paymentManage ×5

회원 / CS

cs · challenge · notification · support

쿠폰 / 마케팅

couponList · campaignManage · popup·fcPopup · deepLinkManage · quickMenu*

입고

inbound · inboundIssue · hubTcInspection

MFC / 물류 어드민 내

mfcCenter · mfcCrew · mfcBox · mfcStock · mfcDeliveryOrder · mfcCalculate(Monthly)

MFC는 별도 프론트가 아니다. 위 7개 mfc* 페이지 + 물류비 정책(partnerInfo/.../LogisticsFeePolicy) 형태로 이 어드민 안에 들어있는 화면군이다.
02

UI 컴포넌트 구성 · 재사용 패턴

컴포넌트는 @core/components(템플릿 34개)와 src/components(커스텀 44 폴더)로 나뉜다. 둘 다 전역 auto-import.vue에서 태그만 쓰면 되고 수동 import는 필요 없다.

핵심 재사용 컴포넌트 4종

A · 다이나믹 서치 모듈 이 앱의 심장

components/dynamicsearch/ — 화면마다 menuCode prop만 넘기면 백엔드가 그 메뉴의 검색 컬럼·저장 프리셋을 내려주고 필터 UI를 동적 생성, @search emit으로 상위 테이블에 파라미터 전달. 약 80개 파일이 공유(order·settlement·coupon·popup·mfc·vipPricing …). 쿼리 querys/dynamicSearch/ · 상태 store/dynamicSearch/ · 타입 types/dynamicSearch.ts.

B · 데이터 테이블 관례 미통일

components/dataTable/(DataTable·DataTableSettingModal 컬럼설정 등). 단 대형 화면(OrderTable)은 Vuetify 원본 VDataTable을 직접 쓰기도 한다 — 공용 vs 원본 혼재. 수정 전 해당 화면이 어느 쪽인지 먼저 확인.

C · 모달 / 다이얼로그 전역 상시 마운트

components/modals/ 20+. 전역 확인창useGlobalConfirmModal() + App.vue에 상시 마운트되어 어디서든 호출. 검색 선택 모달군 searchMember/Partner/Center/FC/Order/Admin. 도메인 모달은 각 페이지 components/에.

D · 엑셀 모듈 광범위 재사용

components/excel/ExcelModule + ExcelModuleButton + Container/EditModal/KeySelector. 다운로드·업로드가 많은 어드민 특성상 넓게 재사용(가이드 pages/documentation/excel/).

전역 마운트(App.vue): BasicSnackbar · Loading · GlobalConfirmModal · ScrollToTop이 항상 떠 있음 → 스낵바·로딩·확인창은 스토어(store/global, store/loading)로 트리거한다.
03

API 호출 · 연동 구조

계층 규칙: request(호출 함수) → query(Vue Query 래퍼) → page/component. 모든 호출은 중앙 axios 인스턴스 하나를 지난다.

page / componentOrderTable.vue
query 래퍼useMutationWithHandler
request 함수orderApi.ts · ORDER_URL
axios instanceapi/utils/instance.ts
백엔드{baseURL}/order/get-all

중앙 axios 인스턴스 — api/utils/instance.ts (반드시 이해)

  • baseURL = VITE_API_BASE_URL + VITE_CONTEXT_PATH, timeout 180초.
  • 요청 인터셉터localStorage.ACCESS_TOKEN을 JWT 파싱해 만료 아니면 Authorization: Bearer 주입.
  • 응답 인터셉터 — 헤더 Authorization 있으면 토큰 자동 갱신(슬라이딩). HTTP 2xx여도 body가 에러 포맷이면 reject(isApiErrorResponse) — 이 백엔드는 200에 에러 envelope를 담기도 해서 필수 로직. 401/특정 code → alertlocalStorage.clear()/login.

인증 · 인가 · 서버상태

주제방식
토큰 저장localStorage ACCESS_TOKEN (+adminId) — 쿠키 아님. parseJwt로 만료 판정, 응답 헤더로 슬라이딩 갱신
인가(권한)CASL 아님. usePermissionCodeStore().getPermission(PERMISSION_CODE.X)~75화면에서 v-if 게이팅. CASL은 네비 표시 판정에만 사용
서버상태TanStack Query. 뮤테이션 표준 래퍼 @core/composable/useMutationWithHandler(공통 에러+성공 메시지). 쿼리키 상수 querys/keys.ts
MSW mocks현재 비활성plugins/msw.tsapp.use(msw)가 주석 처리. "목이 안 먹는" 게 정상
.env는 커밋되지 않는다. 클론 직후 VITE_API_BASE_URL·VITE_CONTEXT_PATH 등을 직접 세팅하지 않으면 getBaseUrl()이 경고 문자열을 반환해 모든 API가 404가 된다.
04

수정 시 주의점 MOST IMPORTANT

건드리면 전역 파급 · 손대면 안 되는 곳

다이나믹 서치 모듈 ~80화면

components/dynamicsearch/* 회귀는 어드민 절반의 검색을 마비시킨다. menuCode 계약 + @search emit 시그니처를 반드시 유지.

axios 인터셉터 전 호출

api/utils/instance.ts — 모든 호출·인증·에러·토큰갱신·/login 리다이렉트가 여기 하나. "200+에러 body" 판정(isApiErrorResponse)을 깨면 정상↔에러가 뒤바뀐다.

권한 스토어 ~75화면

store/permission/permissionCode.ts + utils/constants_PermissionCode.ts. getPermission 시그니처나 코드 상수 rename 시 버튼 노출이 대량으로 흔들린다.

Vuexy 템플릿 원본 DO NOT TOUCH

src/@core/**·src/@layouts/**·plugins/vuetify·plugins/iconify·themeConfig.ts. 수정 시 템플릿 업그레이드 충돌 + 전 화면 테마/네비 영향. 커스터마이즈는 src/layouts·src/components에서 감싸는 방식으로.

네비게이션 · 쿼리키 · 전역 UI 주의

navigation/vertical/index.ts(라우트명 어긋나면 죽은 링크) · querys/keys.ts(중복/오타 → 캐시 무효화 버그) · App.vue 전역 마운트 컴포넌트.

따라야 할 컨벤션

  • Auto-import 영역은 수동 import 금지 — vue API·router·@vueuse·pinia·lodash(_BigNumber + composables·utils·store가 자동 주입. 수동으로 넣으면 중복 경고. auto-imports.d.ts·components.d.ts·typed-router.d.ts자동 생성물 → 수동 편집 금지. (예외: useCookies·useStorage는 의도적 제외 → 명시 import)
  • 뮤테이션은 useMutationWithHandler로. request/query/schema 3계층 관례 유지.
  • 권한은 getPermission(PERMISSION_CODE.X) — CASL $can을 새로 도입하지 말 것(체계 이원화).
  • PR 전 yarn typecheck(vue-tsc) · yarn lint · yarn fix(Prettier).

빌드 / 배포 gotcha

  • postinstallbuild:icons+msw:init 실행 — 아이콘 세트 변경 시 yarn build:icons 재실행. 실패하면 아이콘이 안 뜬다.
  • prod 빌드는 terserdrop_console프로덕션에서 console 디버깅 불가. vite-plugin-vue-devtools는 크래시로 의도적 off(다시 켜지 말 것).
  • 서브패스 배포 시 asset base만 바뀌고 라우터 base는 / 고정. Node는 .nvmrc 버전.

첫 PR에서 물리기 쉬운 것

  1. auto-import 대상을 수동 import → 중복/충돌
  2. @core/@layouts(대문자 @) 템플릿을 앱 코드로 착각해 수정
  3. DynamicSearchModule·instance.ts를 로컬 화면 목적으로 고치다 80화면·전역 회귀
  4. .env 없이 실행 후 "API가 안 된다" 착각
  5. typed-router.d.ts·auto-imports.d.ts 자동생성물을 손으로 수정
  6. 권한을 CASL로 새로 짜려다 실제 getPermission 체계와 이원화
05

빠른 진입 체크리스트

신규 투입 시 이 순서대로.

  1. .nvmrc 버전으로 Node 맞추고 yarn install (→ postinstall이 아이콘·msw init 수행)
  2. .env 생성 — 최소 VITE_API_BASE_URL + VITE_CONTEXT_PATH (팀에서 값 수령)
  3. yarn dev/login 진입, 로그인하면 ACCESS_TOKEN이 localStorage에 저장
  4. 만들 화면을 src/pages/<domain>/index.vue에서 찾기 (URL이 곧 경로)
  5. 목록/검색이면 DynamicSearchModule + DataTable 패턴 답습
  6. 새 API는 request/<domain>/ 함수 → querys/ 래퍼(useMutationWithHandler) → 화면 순
  7. 버튼/기능 노출은 getPermission(PERMISSION_CODE.X)
  8. PR 전 yarn typecheck && yarn lint