폴더 · 화면 구조
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.ts의 beforeEach(토큰 없거나 JWT 만료 → /login).
| URL | 파일 | route name |
|---|---|---|
/order | src/pages/order/index.vue | order |
/mfcCenterManage | src/pages/mfcCenterManage/index.vue | mfc-center-manage |
/manage/account | src/pages/manage/account/index.vue | manage-account |
/login | src/pages/login.vue | login |
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* 페이지 + 물류비 정책(partnerInfo/.../LogisticsFeePolicy) 형태로 이 어드민 안에 들어있는 화면군이다.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)로 트리거한다.API 호출 · 연동 구조
계층 규칙: request(호출 함수) → query(Vue Query 래퍼) → page/component. 모든 호출은 중앙 axios 인스턴스 하나를 지난다.
중앙 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 →alert→localStorage.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.ts의 app.use(msw)가 주석 처리. "목이 안 먹는" 게 정상 |
.env는 커밋되지 않는다. 클론 직후 VITE_API_BASE_URL·VITE_CONTEXT_PATH 등을 직접 세팅하지 않으면 getBaseUrl()이 경고 문자열을 반환해 모든 API가 404가 된다.수정 시 주의점 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
postinstall이build:icons+msw:init실행 — 아이콘 세트 변경 시yarn build:icons재실행. 실패하면 아이콘이 안 뜬다.- prod 빌드는
terser가drop_console→ 프로덕션에서 console 디버깅 불가.vite-plugin-vue-devtools는 크래시로 의도적 off(다시 켜지 말 것). - 서브패스 배포 시 asset
base만 바뀌고 라우터 base는/고정. Node는.nvmrc버전.
첫 PR에서 물리기 쉬운 것
- auto-import 대상을 수동 import → 중복/충돌
@core/@layouts(대문자 @) 템플릿을 앱 코드로 착각해 수정DynamicSearchModule·instance.ts를 로컬 화면 목적으로 고치다 80화면·전역 회귀.env없이 실행 후 "API가 안 된다" 착각typed-router.d.ts·auto-imports.d.ts자동생성물을 손으로 수정- 권한을 CASL로 새로 짜려다 실제
getPermission체계와 이원화
빠른 진입 체크리스트
신규 투입 시 이 순서대로.
.nvmrc버전으로 Node 맞추고yarn install(→ postinstall이 아이콘·msw init 수행).env생성 — 최소VITE_API_BASE_URL+VITE_CONTEXT_PATH(팀에서 값 수령)yarn dev→/login진입, 로그인하면ACCESS_TOKEN이 localStorage에 저장- 만들 화면을
src/pages/<domain>/index.vue에서 찾기 (URL이 곧 경로) - 목록/검색이면
DynamicSearchModule+DataTable패턴 답습 - 새 API는
request/<domain>/함수 →querys/래퍼(useMutationWithHandler) → 화면 순 - 버튼/기능 노출은
getPermission(PERMISSION_CODE.X) - PR 전
yarn typecheck && yarn lint