| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- <template>
- <div class="admin--login">
- <div class="login--container">
- <div class="login--layout">
- <div class="login--layout-inner">
- <div class="login--brand">
- <div class="login--brand--logo">
- 🏴☠️
- </div>
- <div class="login--brand--txt">
- <h1 class="login--brand-title">파이럿존</h1>
- <p class="login--brand-tagline">ADMIN SYSTEM</p>
- </div>
- </div>
- <div class="login--deco--line">
- <span class="circle1"></span>
- <span class="circle2"></span>
- <span class="circle3">🧭</span>
- </div>
- <div class="login--intro">
- <h2 class="login--intro-title">낚시를 모험으로</h2>
- <p class="login--intro-desc">
- Fishing as Adventure
- </p>
- <ul class="login--intro-list">
- 파이럿존 관리자 시스템 v1.0 <br />챌린지 · 퀘스트 · 회원 · 진출권을 관리합니다.
- </ul>
- </div>
- </div>
- </div>
- <div class="login--panel">
- <div class="login--box">
- <div class="login--logo">
- <span>환영합니다 👋</span>
- <h1>어드민 로그인</h1>
- <p class="subtitle">관리자 권한이 있는 계정으로 로그인하세요.</p>
- </div>
- <form @submit.prevent="handleLogin" class="login--form">
- <div v-if="errorMessage" class="login--error">
- {{ errorMessage }}
- </div>
- <div class="form--group">
- <input
- v-model="username"
- type="text"
- placeholder="아이디"
- class="form--input"
- required
- :disabled="isLoading"
- />
- </div>
- <div class="form--group">
- <input
- v-model="password"
- type="password"
- placeholder="비밀번호"
- class="form--input"
- required
- :disabled="isLoading"
- />
- </div>
- <div class="form--options">
- <label class="checkbox--label">
- <input type="checkbox" v-model="rememberMe" :disabled="isLoading" />
- <span>로그인 상태 유지</span>
- </label>
- <!-- <a href="#" class="forgot--password">비밀번호 찾기</a> -->
- </div>
- <button type="submit" class="login--button" :disabled="isLoading">
- {{ isLoading ? "로그인 중..." : "로그인 →" }}
- </button>
- </form>
- </div>
- </div>
- </div>
- <!-- 비밀번호 변경 권장 모달 -->
- <PasswordChangeRecommendModal
- v-if="showPasswordChangeModal"
- :admin-id="loggedInAdminId"
- @close="closePasswordChangeModal"
- @changed="handlePasswordChanged"
- />
- <!-- 계정 잠금 모달 -->
- <AccountLockedModal v-if="showAccountLockedModal" @close="closeAccountLockedModal" />
- </div>
- </template>
- <script setup>
- import { ref } from "vue";
- import PasswordChangeRecommendModal from "~/components/admin/PasswordChangeRecommendModal.vue";
- import AccountLockedModal from "~/components/admin/AccountLockedModal.vue";
- definePageMeta({
- layout: false,
- middleware: ["auth"],
- });
- const { post } = useApi();
- const username = ref("");
- const password = ref("");
- const rememberMe = ref(false);
- const isLoading = ref(false);
- const errorMessage = ref("");
- const showPasswordChangeModal = ref(false);
- const showAccountLockedModal = ref(false);
- const loggedInAdminId = ref(null);
- const handleLogin = async () => {
- if (!username.value || !password.value) {
- errorMessage.value = "아이디와 비밀번호를 입력해주세요.";
- return;
- }
- isLoading.value = true;
- errorMessage.value = "";
- try {
- console.log("[Login] 로그인 시도:", username.value);
- const { data, error } = await post("/auth/login", {
- username: username.value,
- password: password.value,
- remember: rememberMe.value,
- });
- console.log("[Login] API 응답:", { data, error });
- if (error) {
- console.error("[Login] 에러 발생:", error);
- const errorMsg = error.message || "로그인에 실패했습니다.";
- // 403 에러이고 계정 잠금 메시지인 경우 모달 표시
- if (
- error.statusCode === 403 ||
- errorMsg.includes("계정이 잠겼습니다") ||
- errorMsg.includes("슈퍼 관리자에게 문의")
- ) {
- showAccountLockedModal.value = true;
- errorMessage.value = "";
- } else {
- errorMessage.value = errorMsg;
- }
- return;
- }
- // API 응답: { success: true, data: { token, admin, password_change_needed }, message }
- if (data?.success && data?.data?.token) {
- console.log(
- "[Login] 로그인 성공, 토큰 저장:",
- data.data.token.substring(0, 20) + "..."
- );
- localStorage.setItem("admin_token", data.data.token);
- if (data.data.admin) {
- localStorage.setItem("admin_user", JSON.stringify(data.data.admin));
- loggedInAdminId.value = data.data.admin.id;
- }
- console.log(
- "[Login] localStorage 확인:",
- localStorage.getItem("admin_token") ? "저장됨" : "저장 실패"
- );
- // 비밀번호 변경이 필요한 경우 모달 표시
- if (data.data.password_change_needed) {
- console.log("[Login] 비밀번호 변경 필요, 모달 표시");
- showPasswordChangeModal.value = true;
- } else {
- // 비밀번호 변경이 필요 없으면 바로 dashboard로 이동
- console.log("[Login] dashboard로 이동 시도...");
- await navigateTo("/site-manager/dashboard");
- console.log("[Login] navigateTo 완료");
- }
- } else {
- console.error("[Login] 잘못된 응답 형식:", data);
- errorMessage.value = data?.message || "로그인 정보가 올바르지 않습니다.";
- }
- } catch (error) {
- errorMessage.value = "서버 오류가 발생했습니다.";
- console.error("[Login] Exception:", error);
- } finally {
- isLoading.value = false;
- }
- };
- const closePasswordChangeModal = async () => {
- showPasswordChangeModal.value = false;
- // 모달을 닫으면 dashboard로 이동
- await navigateTo("/site-manager/dashboard");
- };
- const handlePasswordChanged = async (message) => {
- showPasswordChangeModal.value = false;
- // 비밀번호 변경 성공 후 dashboard로 이동
- if (message) {
- console.log("[Login] 비밀번호 변경 성공:", message);
- }
- await navigateTo("/site-manager/dashboard");
- };
- const closeAccountLockedModal = () => {
- showAccountLockedModal.value = false;
- // 계정 잠금 모달 닫으면 에러 메시지 지우고 로그인 폼 초기화
- errorMessage.value = "";
- password.value = "";
- };
- </script>
|