admin.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <script setup>
  2. import { ref, computed } from "vue";
  3. import { useRoute, useRouter } from "vue-router";
  4. import AdminAlertModal from "~/components/admin/AdminAlertModal.vue";
  5. import AdminModal from "~/components/admin/AdminModal.vue";
  6. const route = useRoute();
  7. const router = useRouter();
  8. // 메뉴 열림 상태 관리
  9. const openMenus = ref(["basic", "branch", "staff", "service", "board", "system"]);
  10. // GNB 메뉴 구조
  11. const menuItems = ref([
  12. {
  13. id: "basic",
  14. title: "기본정보관리",
  15. children: [
  16. {
  17. title: "사이트 정보",
  18. path: "/site-manager/basic/site-info",
  19. pattern: /^\/site-manager\/basic\/site-info/,
  20. },
  21. {
  22. title: "팝업관리",
  23. path: "/site-manager/basic/popup",
  24. pattern: /^\/site-manager\/basic\/popup/,
  25. },
  26. ],
  27. },
  28. {
  29. id: "branch",
  30. title: "지점관리",
  31. children: [
  32. {
  33. title: "딜러사 관리",
  34. path: "/site-manager/branch/list",
  35. pattern: /^\/site-manager\/branch\/(list|create|edit)/,
  36. },
  37. {
  38. title: "전시장목록",
  39. path: "/site-manager/showroom/list",
  40. pattern: /^\/site-manager\/showroom\/(list|create|edit)/,
  41. },
  42. {
  43. title: "서비스센터목록",
  44. path: "/site-manager/service-center/list",
  45. pattern: /^\/site-manager\/service-center\/(list|create|edit)/,
  46. },
  47. ],
  48. },
  49. {
  50. id: "board",
  51. title: "게시판관리",
  52. children: [
  53. {
  54. title: "이벤트",
  55. path: "/site-manager/board/event",
  56. pattern: /^\/site-manager\/board\/event/,
  57. },
  58. // { title: "뉴스", path: "/site-manager/board/news", pattern: /^\/site-manager\/board\/news/ },
  59. // { title: "IR", path: "/site-manager/board/ir", pattern: /^\/site-manager\/board\/ir/ },
  60. ],
  61. },
  62. {
  63. id: "system",
  64. title: "시스템관리",
  65. children: [
  66. { title: "관리자관리", path: "/site-manager/admins", pattern: /^\/site-manager\/admins/ },
  67. ],
  68. },
  69. ]);
  70. // 메뉴 토글
  71. const toggleMenu = (menuId) => {
  72. const index = openMenus.value.indexOf(menuId);
  73. if (index > -1) {
  74. openMenus.value.splice(index, 1);
  75. } else {
  76. openMenus.value.push(menuId);
  77. }
  78. };
  79. // 현재 활성 라우트 체크
  80. const isActiveRoute = (path) => {
  81. return route.path === path;
  82. };
  83. // 현재 경로에 맞는 메뉴 찾기
  84. const findCurrentMenu = () => {
  85. const currentPath = route.path;
  86. // 대시보드인 경우
  87. if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
  88. return { menu: null, child: null };
  89. }
  90. for (const menu of menuItems.value) {
  91. for (const child of menu.children) {
  92. // pattern이 있으면 정규식으로 매칭, 없으면 정확히 일치하는지 확인
  93. if (child.pattern && child.pattern.test(currentPath)) {
  94. return { menu, child };
  95. } else if (currentPath === child.path) {
  96. return { menu, child };
  97. }
  98. }
  99. }
  100. return { menu: null, child: null };
  101. };
  102. // 페이지 타이틀 계산
  103. const pageTitle = computed(() => {
  104. const currentPath = route.path;
  105. // 대시보드
  106. if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
  107. return "대시보드";
  108. }
  109. const { child } = findCurrentMenu();
  110. if (child) {
  111. // 상세 페이지 타이틀 처리
  112. if (currentPath.includes("/create")) {
  113. return `${child.title} 등록`;
  114. } else if (currentPath.includes("/edit/")) {
  115. return `${child.title} 수정`;
  116. } else if (currentPath.includes("/print/")) {
  117. return `${child.title} 인쇄`;
  118. } else if (currentPath.includes("/print-a2")) {
  119. return `${child.title} 인쇄 (A2)`;
  120. }
  121. return child.title;
  122. }
  123. return "대시보드";
  124. });
  125. // Breadcrumb 계산
  126. const breadcrumbs = computed(() => {
  127. const currentPath = route.path;
  128. const crumbs = [{ title: "Home", path: "/site-manager/dashboard" }];
  129. // 대시보드인 경우 Home만 표시
  130. if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
  131. return crumbs;
  132. }
  133. const { menu, child } = findCurrentMenu();
  134. if (menu && child) {
  135. // 메뉴 그룹 추가
  136. crumbs.push({ title: menu.title, path: null });
  137. // 현재 페이지 타이틀 추가
  138. if (currentPath.includes("/create")) {
  139. crumbs.push({ title: child.title, path: child.path });
  140. crumbs.push({ title: "등록", path: null });
  141. } else if (currentPath.includes("/edit/")) {
  142. crumbs.push({ title: child.title, path: child.path });
  143. crumbs.push({ title: "수정", path: null });
  144. } else if (currentPath.includes("/print/")) {
  145. crumbs.push({ title: child.title, path: child.path });
  146. crumbs.push({ title: "인쇄", path: null });
  147. } else if (currentPath.includes("/print-a2")) {
  148. crumbs.push({ title: child.title, path: child.path });
  149. crumbs.push({ title: "인쇄 (A2)", path: null });
  150. } else {
  151. crumbs.push({ title: child.title, path: null });
  152. }
  153. }
  154. return crumbs;
  155. });
  156. // 로그아웃 모달
  157. const showLogoutModal = ref(false);
  158. const handleLogout = () => {
  159. console.log("[Logout] 로그아웃 버튼 클릭");
  160. showLogoutModal.value = true;
  161. console.log("[Logout] showLogoutModal:", showLogoutModal.value);
  162. };
  163. const confirmLogout = () => {
  164. console.log("[Logout] 로그아웃 확인");
  165. localStorage.removeItem("admin_token");
  166. localStorage.removeItem("admin_user");
  167. router.push("/site-manager");
  168. };
  169. const closeLogoutModal = () => {
  170. console.log("[Logout] 모달 닫기");
  171. showLogoutModal.value = false;
  172. };
  173. // 정보수정 모달
  174. const showProfileModal = ref(false);
  175. const currentAdmin = ref(null);
  176. const { get } = useApi();
  177. // 알림 모달
  178. const alertModal = ref({
  179. show: false,
  180. title: "알림",
  181. message: "",
  182. type: "alert",
  183. onConfirm: null,
  184. });
  185. // 알림 모달 표시
  186. const showAlert = (message, title = "알림") => {
  187. alertModal.value = {
  188. show: true,
  189. title,
  190. message,
  191. type: "alert",
  192. onConfirm: null,
  193. };
  194. };
  195. // 알림 모달 닫기
  196. const closeAlertModal = () => {
  197. alertModal.value.show = false;
  198. };
  199. // 알림 모달 확인
  200. const handleAlertConfirm = () => {
  201. if (alertModal.value.onConfirm) {
  202. alertModal.value.onConfirm();
  203. }
  204. closeAlertModal();
  205. };
  206. // 알림 모달 취소
  207. const handleAlertCancel = () => {
  208. closeAlertModal();
  209. };
  210. // 현재 로그인한 관리자 ID 가져오기
  211. const getCurrentAdminId = () => {
  212. if (typeof window === "undefined") return null;
  213. const user = localStorage.getItem("admin_user");
  214. if (!user) return null;
  215. try {
  216. return JSON.parse(user).id;
  217. } catch {
  218. return null;
  219. }
  220. };
  221. // 대시보드로 이동
  222. const goToDashboard = () => {
  223. router.push("/site-manager/dashboard");
  224. };
  225. // 정보수정 버튼 클릭
  226. const goToProfile = async () => {
  227. const adminId = getCurrentAdminId();
  228. if (!adminId) {
  229. showAlert("로그인 정보를 찾을 수 없습니다.", "오류");
  230. return;
  231. }
  232. // 현재 관리자 정보 조회
  233. const { data, error } = await get(`/admin/${adminId}`);
  234. if (error) {
  235. showAlert("관리자 정보를 불러올 수 없습니다.", "오류");
  236. console.error("[Profile] 관리자 정보 조회 실패:", error);
  237. return;
  238. }
  239. if (data?.success && data?.data) {
  240. currentAdmin.value = data.data;
  241. showProfileModal.value = true;
  242. }
  243. };
  244. // 정보수정 모달 닫기
  245. const closeProfileModal = () => {
  246. showProfileModal.value = false;
  247. currentAdmin.value = null;
  248. };
  249. // 정보수정 완료
  250. const handleProfileSaved = (message) => {
  251. closeProfileModal();
  252. if (message) {
  253. showAlert(message, "성공");
  254. // 로컬스토리지의 사용자 정보도 업데이트
  255. if (currentAdmin.value) {
  256. localStorage.setItem("admin_user", JSON.stringify(currentAdmin.value));
  257. }
  258. }
  259. };
  260. </script>
  261. <template>
  262. <div class="admin--layout">
  263. <!-- Header -->
  264. <header class="admin--header">
  265. <div class="admin--header-left">
  266. <div class="admin--logo" @click="goToDashboard" style="cursor: pointer">
  267. <h1>파이럿존</h1>
  268. <span class="admin--logo-sub">ADMIN</span>
  269. </div>
  270. </div>
  271. <div class="admin--header-right">
  272. <button class="admin--header-btn" @click="goToProfile">정보수정</button>
  273. <button
  274. type="button"
  275. class="admin--header-btn admin--header-btn-logout"
  276. @click.prevent="handleLogout"
  277. >
  278. 로그아웃
  279. </button>
  280. </div>
  281. </header>
  282. <!-- Main Content Area -->
  283. <div class="admin--content-wrapper">
  284. <!-- Sidebar GNB -->
  285. <aside class="admin--sidebar">
  286. <nav class="admin--gnb">
  287. <div v-for="menu in menuItems" :key="menu.id" class="admin--gnb-group">
  288. <div class="admin--gnb-title" @click="toggleMenu(menu.id)">
  289. {{ menu.title }}
  290. <span
  291. class="admin--gnb-arrow"
  292. :class="{ 'is-open': openMenus.includes(menu.id) }"
  293. >
  294. </span>
  295. </div>
  296. <transition name="admin--submenu">
  297. <ul v-show="openMenus.includes(menu.id)" class="admin--gnb-submenu">
  298. <li
  299. v-for="submenu in menu.children"
  300. :key="submenu.path"
  301. class="admin--gnb-item"
  302. :class="{ 'is-active': isActiveRoute(submenu.path) }"
  303. >
  304. <NuxtLink :to="submenu.path" class="admin--gnb-link">
  305. {{ submenu.title }}
  306. </NuxtLink>
  307. </li>
  308. </ul>
  309. </transition>
  310. </div>
  311. </nav>
  312. </aside>
  313. <!-- Content Area -->
  314. <main class="admin--main">
  315. <!-- Breadcrumb & Title -->
  316. <div class="admin--page-header">
  317. <h2 class="admin--page-title">{{ pageTitle }}</h2>
  318. <div class="admin--breadcrumb">
  319. <span v-for="(crumb, index) in breadcrumbs" :key="index">
  320. <NuxtLink v-if="crumb.path" :to="crumb.path" class="admin--breadcrumb-link">
  321. {{ crumb.title }}
  322. </NuxtLink>
  323. <span v-else class="admin--breadcrumb-current">{{ crumb.title }}</span>
  324. <span
  325. v-if="index < breadcrumbs.length - 1"
  326. class="admin--breadcrumb-separator"
  327. >/</span
  328. >
  329. </span>
  330. </div>
  331. </div>
  332. <!-- Page Content -->
  333. <div class="admin--page-content">
  334. <slot />
  335. </div>
  336. <!-- Admin Footer -->
  337. <footer class="admin--footer">
  338. <p>
  339. &copy; {{ new Date().getFullYear() }} FORDKOREA 포드코리아. All rights
  340. reserved.
  341. </p>
  342. </footer>
  343. </main>
  344. </div>
  345. <!-- 로그아웃 확인 모달 -->
  346. <AdminAlertModal
  347. v-if="showLogoutModal"
  348. title="로그아웃"
  349. message="로그아웃 하시겠습니까?"
  350. type="confirm"
  351. @confirm="confirmLogout"
  352. @cancel="closeLogoutModal"
  353. @close="closeLogoutModal"
  354. />
  355. <!-- 관리자 정보수정 모달 -->
  356. <AdminModal
  357. v-if="showProfileModal"
  358. :admin="currentAdmin"
  359. @close="closeProfileModal"
  360. @saved="handleProfileSaved"
  361. />
  362. <!-- 알림 모달 -->
  363. <AdminAlertModal
  364. v-if="alertModal.show"
  365. :title="alertModal.title"
  366. :message="alertModal.message"
  367. :type="alertModal.type"
  368. @confirm="handleAlertConfirm"
  369. @cancel="handleAlertCancel"
  370. @close="closeAlertModal"
  371. />
  372. </div>
  373. </template>