frontend backend connection
This commit is contained in:
parent
0f291383a1
commit
9f64f93ed8
@ -1,15 +1,17 @@
|
|||||||
//* public/runtime-env.js
|
//* public/runtime-env.js - 디버깅 포함 버전
|
||||||
|
console.log('=== RUNTIME-ENV.JS 로드됨 ===');
|
||||||
|
|
||||||
window.__runtime_config__ = {
|
window.__runtime_config__ = {
|
||||||
// API 서버 URL들
|
// 로컬 개발 환경 설정
|
||||||
AUTH_URL: 'http://20.1.2.3/auth',
|
AUTH_URL: 'http://localhost:8081/api/auth',
|
||||||
STORE_URL: 'http://20.1.2.3/store',
|
MEMBER_URL: 'http://localhost:8081/api/member',
|
||||||
CONTENT_URL: 'http://20.1.2.3/content',
|
STORE_URL: 'http://localhost:8082/api/store',
|
||||||
RECOMMEND_URL: 'http://20.1.2.3/recommend',
|
CONTENT_URL: 'http://localhost:8083/api/content',
|
||||||
|
RECOMMEND_URL: 'http://localhost:8084/api/recommendation',
|
||||||
// 외부 API 설정
|
|
||||||
CLAUDE_AI_ENABLED: true,
|
// Gateway 주석 처리 (로컬에서는 사용 안함)
|
||||||
WEATHER_API_ENABLED: true,
|
// GATEWAY_URL: 'http://20.1.2.3',
|
||||||
|
|
||||||
// 기능 플래그
|
// 기능 플래그
|
||||||
FEATURES: {
|
FEATURES: {
|
||||||
ANALYTICS: true,
|
ANALYTICS: true,
|
||||||
@ -19,10 +21,15 @@ window.__runtime_config__ = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 환경 설정
|
// 환경 설정
|
||||||
ENV: 'production',
|
ENV: 'development',
|
||||||
DEBUG: false,
|
DEBUG: true,
|
||||||
|
|
||||||
// 버전 정보
|
// 버전 정보
|
||||||
VERSION: '1.0.0',
|
VERSION: '1.0.0',
|
||||||
BUILD_DATE: new Date().toISOString(),
|
BUILD_DATE: new Date().toISOString(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
console.log('=== 설정된 API URLs ===');
|
||||||
|
console.log('AUTH_URL:', window.__runtime_config__.AUTH_URL);
|
||||||
|
console.log('MEMBER_URL:', window.__runtime_config__.MEMBER_URL);
|
||||||
|
console.log('전체 설정:', window.__runtime_config__);
|
||||||
@ -6,10 +6,12 @@ const getApiUrls = () => {
|
|||||||
const config = window.__runtime_config__ || {}
|
const config = window.__runtime_config__ || {}
|
||||||
return {
|
return {
|
||||||
GATEWAY_URL: config.GATEWAY_URL || 'http://20.1.2.3',
|
GATEWAY_URL: config.GATEWAY_URL || 'http://20.1.2.3',
|
||||||
MEMBER_URL: config.MEMBER_URL || 'http://20.1.2.3/api/member',
|
AUTH_URL: 'http://localhost:8081/api/auth',
|
||||||
AUTH_URL: config.AUTH_URL || 'http://20.1.2.3/api/auth',
|
MEMBER_URL: 'http://localhost:8081/api/member',
|
||||||
STORE_URL: config.STORE_URL || 'http://20.1.2.3/api/store',
|
STORE_URL: config.STORE_URL || 'http://20.1.2.3/api/store',
|
||||||
CONTENT_URL: config.CONTENT_URL || 'http://20.1.2.3/api/content',
|
CONTENT_URL: config.CONTENT_URL || 'http://20.1.2.3/api/content',
|
||||||
|
MENU_URL: config.MENU_URL || 'http://20.1.2.3/api/menu',
|
||||||
|
SALES_URL: config.SALES_URL || 'http://20.1.2.3/api/sales',
|
||||||
RECOMMEND_URL: config.RECOMMEND_URL || 'http://20.1.2.3/api/recommendation',
|
RECOMMEND_URL: config.RECOMMEND_URL || 'http://20.1.2.3/api/recommendation',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,7 +28,7 @@ const createApiInstance = (baseURL) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 요청 인터셉터 - JWT 토큰 자동 추가
|
// 요청 인터셉터 - JWT 토큰 자동 추가
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = localStorage.getItem('accessToken')
|
const token = localStorage.getItem('accessToken')
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -58,8 +60,9 @@ const createApiInstance = (baseURL) => {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { accessToken } = refreshResponse.data.data
|
const { accessToken, refreshToken: newRefreshToken } = refreshResponse.data.data
|
||||||
localStorage.setItem('accessToken', accessToken)
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', newRefreshToken)
|
||||||
|
|
||||||
// 원래 요청에 새 토큰으로 재시도
|
// 원래 요청에 새 토큰으로 재시도
|
||||||
originalRequest.headers.Authorization = `Bearer ${accessToken}`
|
originalRequest.headers.Authorization = `Bearer ${accessToken}`
|
||||||
@ -69,6 +72,7 @@ const createApiInstance = (baseURL) => {
|
|||||||
// 토큰 갱신 실패 시 로그아웃 처리
|
// 토큰 갱신 실패 시 로그아웃 처리
|
||||||
localStorage.removeItem('accessToken')
|
localStorage.removeItem('accessToken')
|
||||||
localStorage.removeItem('refreshToken')
|
localStorage.removeItem('refreshToken')
|
||||||
|
localStorage.removeItem('userInfo')
|
||||||
window.location.href = '/login'
|
window.location.href = '/login'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,6 +90,8 @@ export const memberApi = createApiInstance(apiUrls.MEMBER_URL)
|
|||||||
export const authApi = createApiInstance(apiUrls.AUTH_URL)
|
export const authApi = createApiInstance(apiUrls.AUTH_URL)
|
||||||
export const storeApi = createApiInstance(apiUrls.STORE_URL)
|
export const storeApi = createApiInstance(apiUrls.STORE_URL)
|
||||||
export const contentApi = createApiInstance(apiUrls.CONTENT_URL)
|
export const contentApi = createApiInstance(apiUrls.CONTENT_URL)
|
||||||
|
export const menuApi = createApiInstance(apiUrls.MENU_URL)
|
||||||
|
export const salesApi = createApiInstance(apiUrls.SALES_URL)
|
||||||
export const recommendApi = createApiInstance(apiUrls.RECOMMEND_URL)
|
export const recommendApi = createApiInstance(apiUrls.RECOMMEND_URL)
|
||||||
|
|
||||||
// 기본 API 인스턴스 (Gateway URL 사용)
|
// 기본 API 인스턴스 (Gateway URL 사용)
|
||||||
@ -99,7 +105,7 @@ export const handleApiError = (error) => {
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '네트워크 연결을 확인해주세요.',
|
message: '네트워크 연결을 확인해주세요.',
|
||||||
code: 'NETWORK_ERROR',
|
error: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,46 +117,47 @@ export const handleApiError = (error) => {
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: data?.message || '잘못된 요청입니다.',
|
message: data?.message || '잘못된 요청입니다.',
|
||||||
code: 'BAD_REQUEST',
|
error: data?.error
|
||||||
}
|
}
|
||||||
case 401:
|
case 401:
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '인증이 필요합니다.',
|
message: '인증이 필요합니다.',
|
||||||
code: 'UNAUTHORIZED',
|
error: 'UNAUTHORIZED'
|
||||||
}
|
}
|
||||||
case 403:
|
case 403:
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '접근 권한이 없습니다.',
|
message: '접근 권한이 없습니다.',
|
||||||
code: 'FORBIDDEN',
|
error: 'FORBIDDEN'
|
||||||
}
|
}
|
||||||
case 404:
|
case 404:
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '요청하신 정보를 찾을 수 없습니다.',
|
message: '요청한 리소스를 찾을 수 없습니다.',
|
||||||
code: 'NOT_FOUND',
|
error: 'NOT_FOUND'
|
||||||
}
|
}
|
||||||
case 500:
|
case 500:
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '서버 오류가 발생했습니다.',
|
message: '서버 오류가 발생했습니다.',
|
||||||
code: 'SERVER_ERROR',
|
error: 'INTERNAL_SERVER_ERROR'
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: data?.message || '알 수 없는 오류가 발생했습니다.',
|
message: data?.message || `오류가 발생했습니다. (${status})`,
|
||||||
code: 'UNKNOWN_ERROR',
|
error: data?.error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 성공 응답 포맷터
|
// 성공 응답 포맷터
|
||||||
export const formatSuccessResponse = (data, message = '성공적으로 처리되었습니다.') => {
|
export const formatSuccessResponse = (data, message = '요청이 성공적으로 처리되었습니다.') => {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message,
|
message,
|
||||||
data,
|
data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,22 @@
|
|||||||
//* src/views/LoginView.vue
|
//* src/views/LoginView.vue - 수정된 회원가입 기능
|
||||||
<template>
|
<template>
|
||||||
<v-container fluid class="login-container">
|
<v-container fluid class="login-container">
|
||||||
<v-row justify="center" align="center" class="fill-height">
|
<v-row justify="center" align="center" style="min-height: 100vh">
|
||||||
<v-col cols="12" sm="8" md="6" lg="4" xl="3">
|
<v-col cols="12" sm="8" md="6" lg="4" xl="3">
|
||||||
<v-card class="login-card" elevation="8">
|
<!-- 로고 및 제목 -->
|
||||||
<!-- 로고 섹션 -->
|
<div class="text-center logo-section">
|
||||||
<v-card-text class="text-center pa-8">
|
<v-img src="/images/logo192.png" alt="AI 마케팅 로고" max-width="80" class="mx-auto mb-4" />
|
||||||
<div class="logo-section mb-6">
|
<h1 class="text-h4 font-weight-bold text-primary mb-2">AI 마케팅</h1>
|
||||||
<v-img
|
<p class="text-subtitle-1 text-grey-darken-1">소상공인을 위한 스마트 마케팅 솔루션</p>
|
||||||
src="/images/logo192.png"
|
</div>
|
||||||
alt="AI 마케팅 로고"
|
|
||||||
width="80"
|
|
||||||
height="80"
|
|
||||||
class="mx-auto mb-4"
|
|
||||||
/>
|
|
||||||
<h1 class="text-h4 font-weight-bold primary--text mb-2">AI 마케팅</h1>
|
|
||||||
<p class="text-subtitle-1 text-grey-darken-1">소상공인을 위한 스마트 마케팅 솔루션</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 로그인 폼 -->
|
<!-- 로그인 카드 -->
|
||||||
<v-form ref="loginForm" v-model="isFormValid" @submit.prevent="handleLogin">
|
<v-card class="login-card" elevation="8">
|
||||||
|
<v-card-text class="pa-8">
|
||||||
|
<v-form v-model="isFormValid" ref="loginForm" @submit.prevent="handleLogin">
|
||||||
|
<h2 class="text-h5 font-weight-bold text-center mb-6">로그인</h2>
|
||||||
|
|
||||||
|
<!-- 아이디 입력 -->
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="credentials.username"
|
v-model="credentials.username"
|
||||||
label="아이디"
|
label="아이디"
|
||||||
@ -32,6 +29,7 @@
|
|||||||
@keyup.enter="handleLogin"
|
@keyup.enter="handleLogin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 비밀번호 입력 -->
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="credentials.password"
|
v-model="credentials.password"
|
||||||
label="비밀번호"
|
label="비밀번호"
|
||||||
@ -77,15 +75,6 @@
|
|||||||
{{ loginError }}
|
{{ loginError }}
|
||||||
</v-alert>
|
</v-alert>
|
||||||
|
|
||||||
<!-- 디버그 정보 (개발 중에만 표시) -->
|
|
||||||
<v-card v-if="showDebugInfo" variant="outlined" class="mb-4 pa-3">
|
|
||||||
<div class="text-caption">디버그 정보:</div>
|
|
||||||
<div class="text-caption">아이디: "{{ credentials.username }}"</div>
|
|
||||||
<div class="text-caption">비밀번호: "{{ credentials.password }}"</div>
|
|
||||||
<div class="text-caption">폼 유효성: {{ isFormValid }}</div>
|
|
||||||
<div class="text-caption">로딩 상태: {{ authStore.isLoading }}</div>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<!-- 로그인 버튼 -->
|
<!-- 로그인 버튼 -->
|
||||||
<v-btn
|
<v-btn
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -102,7 +91,7 @@
|
|||||||
|
|
||||||
<!-- 회원가입 링크 -->
|
<!-- 회원가입 링크 -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-body-2 text-grey-darken-1"> 계정이 없으신가요? </span>
|
<span class="text-body-2 text-grey-darken-1">계정이 없으신가요? </span>
|
||||||
<v-btn variant="text" color="primary" size="small" @click="showSignup = true">
|
<v-btn variant="text" color="primary" size="small" @click="showSignup = true">
|
||||||
회원가입
|
회원가입
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@ -157,23 +146,147 @@
|
|||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<!-- 회원가입 다이얼로그 -->
|
<!-- 회원가입 다이얼로그 -->
|
||||||
<v-dialog v-model="showSignup" max-width="500">
|
<v-dialog v-model="showSignup" max-width="600" persistent>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>회원가입</v-card-title>
|
<v-card-title class="d-flex justify-space-between align-center">
|
||||||
|
<span>회원가입</span>
|
||||||
|
<v-btn icon variant="text" @click="closeSignupDialog">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="mb-4">AI 마케팅 서비스에 오신 것을 환영합니다!</p>
|
<p class="mb-4">AI 마케팅 서비스에 오신 것을 환영합니다!</p>
|
||||||
<v-form>
|
|
||||||
<v-text-field label="아이디" variant="outlined" class="mb-2" />
|
<!-- 회원가입 폼 -->
|
||||||
<v-text-field label="비밀번호" type="password" variant="outlined" class="mb-2" />
|
<v-form v-model="isSignupFormValid" ref="signupForm">
|
||||||
<v-text-field label="비밀번호 확인" type="password" variant="outlined" class="mb-2" />
|
<!-- 아이디 (중복체크 임시 제거) -->
|
||||||
<v-text-field label="이메일" type="email" variant="outlined" class="mb-2" />
|
<v-text-field
|
||||||
<v-text-field label="매장명" variant="outlined" class="mb-2" />
|
v-model="signupData.userId"
|
||||||
|
label="아이디"
|
||||||
|
variant="outlined"
|
||||||
|
:rules="signupUserIdRules"
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 이메일 (중복체크 임시 제거) -->
|
||||||
|
<v-text-field
|
||||||
|
v-model="signupData.email"
|
||||||
|
label="이메일"
|
||||||
|
type="email"
|
||||||
|
variant="outlined"
|
||||||
|
:rules="emailRules"
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 비밀번호 -->
|
||||||
|
<v-text-field
|
||||||
|
v-model="signupData.password"
|
||||||
|
label="비밀번호"
|
||||||
|
type="password"
|
||||||
|
variant="outlined"
|
||||||
|
:rules="signupPasswordRules"
|
||||||
|
class="mb-2"
|
||||||
|
hint="8자 이상, 영문+숫자+특수문자(@$!%*?&) 조합"
|
||||||
|
persistent-hint
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 비밀번호 확인 -->
|
||||||
|
<v-text-field
|
||||||
|
v-model="signupData.passwordConfirm"
|
||||||
|
label="비밀번호 확인"
|
||||||
|
type="password"
|
||||||
|
variant="outlined"
|
||||||
|
:rules="passwordConfirmRules"
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 이름 -->
|
||||||
|
<v-text-field
|
||||||
|
v-model="signupData.name"
|
||||||
|
label="이름"
|
||||||
|
variant="outlined"
|
||||||
|
:rules="nameRules"
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 사업자 번호 -->
|
||||||
|
<v-text-field
|
||||||
|
v-model="signupData.businessNumber"
|
||||||
|
label="사업자 번호 (선택사항)"
|
||||||
|
variant="outlined"
|
||||||
|
:rules="businessNumberRules"
|
||||||
|
class="mb-2"
|
||||||
|
hint="10자리 숫자, '-' 없이 입력"
|
||||||
|
persistent-hint
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 중복 확인 상태 표시 (임시 숨김) -->
|
||||||
|
<!--
|
||||||
|
<div class="mb-4">
|
||||||
|
<v-chip
|
||||||
|
v-if="userIdChecked"
|
||||||
|
color="success"
|
||||||
|
size="small"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
|
<v-icon start size="small">mdi-check</v-icon>
|
||||||
|
아이디 확인완료
|
||||||
|
</v-chip>
|
||||||
|
<v-chip
|
||||||
|
v-if="emailChecked"
|
||||||
|
color="success"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<v-icon start size="small">mdi-check</v-icon>
|
||||||
|
이메일 확인완료
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- 디버깅 정보 (개발 중에만 표시) -->
|
||||||
|
<v-card v-if="isDev" variant="outlined" class="mb-4 pa-3">
|
||||||
|
<div class="text-caption">디버깅 정보:</div>
|
||||||
|
<div class="text-caption">폼 유효성: {{ isSignupFormValid }}</div>
|
||||||
|
<div class="text-caption">아이디 확인: {{ userIdChecked }}</div>
|
||||||
|
<div class="text-caption">이메일 확인: {{ emailChecked }}</div>
|
||||||
|
<div class="text-caption">가입 가능: {{ canSignup }}</div>
|
||||||
|
<div class="text-caption">로딩 상태: {{ signupLoading }}</div>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<!-- 에러/성공 메시지 -->
|
||||||
|
<v-alert
|
||||||
|
v-if="signupError"
|
||||||
|
type="error"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
closable
|
||||||
|
@click:close="signupError = ''"
|
||||||
|
>
|
||||||
|
{{ signupError }}
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<v-alert
|
||||||
|
v-if="signupSuccess"
|
||||||
|
type="success"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
{{ signupSuccess }}
|
||||||
|
</v-alert>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn variant="text" @click="showSignup = false">취소</v-btn>
|
<v-btn variant="text" @click="closeSignupDialog">취소</v-btn>
|
||||||
<v-btn color="primary" @click="handleSignup">가입하기</v-btn>
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="handleSignup"
|
||||||
|
:loading="signupLoading"
|
||||||
|
:disabled="!canSignup"
|
||||||
|
>
|
||||||
|
가입하기
|
||||||
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@ -181,30 +294,51 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/store/auth'
|
import { useAuthStore } from '@/store/auth'
|
||||||
import { useAppStore } from '@/store/app'
|
import { useAppStore } from '@/store/app'
|
||||||
|
import { memberApi } from '@/services/api'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
// 반응형 데이터
|
// 로그인 관련 반응형 데이터
|
||||||
const loginForm = ref(null)
|
const loginForm = ref(null)
|
||||||
const isFormValid = ref(false)
|
const isFormValid = ref(false)
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
const rememberMe = ref(false)
|
const rememberMe = ref(false)
|
||||||
const loginError = ref('')
|
const loginError = ref('')
|
||||||
const showForgotPassword = ref(false)
|
const showForgotPassword = ref(false)
|
||||||
const showSignup = ref(false)
|
|
||||||
const forgotEmail = ref('')
|
const forgotEmail = ref('')
|
||||||
const showDebugInfo = ref(true) // 개발 중에는 true로 설정
|
|
||||||
|
|
||||||
// 로그인 자격 증명 - 기본값 설정
|
// 회원가입 관련 반응형 데이터
|
||||||
|
const showSignup = ref(false)
|
||||||
|
const signupForm = ref(null)
|
||||||
|
const isSignupFormValid = ref(false)
|
||||||
|
const signupLoading = ref(false)
|
||||||
|
const signupError = ref('')
|
||||||
|
const signupSuccess = ref('')
|
||||||
|
const checkingUserId = ref(false)
|
||||||
|
const checkingEmail = ref(false)
|
||||||
|
const userIdChecked = ref(false)
|
||||||
|
const emailChecked = ref(false)
|
||||||
|
|
||||||
|
// 로그인 자격 증명
|
||||||
const credentials = ref({
|
const credentials = ref({
|
||||||
username: 'user01', // 기본값 설정
|
username: 'user01',
|
||||||
password: 'passw0rd', // 기본값 설정
|
password: 'passw0rd',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 회원가입 데이터
|
||||||
|
const signupData = ref({
|
||||||
|
userId: '',
|
||||||
|
password: '',
|
||||||
|
passwordConfirm: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
businessNumber: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const fieldErrors = ref({
|
const fieldErrors = ref({
|
||||||
@ -212,7 +346,19 @@ const fieldErrors = ref({
|
|||||||
password: [],
|
password: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
// 유효성 검사 규칙
|
// 개발 모드 여부
|
||||||
|
const isDev = ref(import.meta.env.DEV)
|
||||||
|
|
||||||
|
// 가입 가능 여부 computed (중복체크 조건 임시 제거)
|
||||||
|
const canSignup = computed(() => {
|
||||||
|
return isSignupFormValid.value &&
|
||||||
|
!signupLoading.value
|
||||||
|
// 임시로 중복체크 조건 제거
|
||||||
|
// userIdChecked.value &&
|
||||||
|
// emailChecked.value &&
|
||||||
|
})
|
||||||
|
|
||||||
|
// 로그인 유효성 검사 규칙
|
||||||
const usernameRules = [
|
const usernameRules = [
|
||||||
(v) => !!v || '아이디를 입력해주세요',
|
(v) => !!v || '아이디를 입력해주세요',
|
||||||
(v) => (v && v.length >= 3) || '아이디는 3자 이상이어야 합니다',
|
(v) => (v && v.length >= 3) || '아이디는 3자 이상이어야 합니다',
|
||||||
@ -223,60 +369,77 @@ const passwordRules = [
|
|||||||
(v) => (v && v.length >= 6) || '비밀번호는 6자 이상이어야 합니다',
|
(v) => (v && v.length >= 6) || '비밀번호는 6자 이상이어야 합니다',
|
||||||
]
|
]
|
||||||
|
|
||||||
// 메서드
|
// 회원가입 유효성 검사 규칙
|
||||||
|
const signupUserIdRules = [
|
||||||
|
(v) => !!v || '아이디를 입력해주세요',
|
||||||
|
(v) => (v && v.length >= 3) || '아이디는 3자 이상이어야 합니다',
|
||||||
|
(v) => (v && v.length <= 20) || '아이디는 20자 이하여야 합니다',
|
||||||
|
(v) => /^[a-zA-Z0-9_]+$/.test(v) || '아이디는 영문, 숫자, _만 사용 가능합니다',
|
||||||
|
]
|
||||||
|
|
||||||
|
const signupPasswordRules = [
|
||||||
|
(v) => !!v || '비밀번호를 입력해주세요',
|
||||||
|
(v) => (v && v.length >= 8) || '비밀번호는 8자 이상이어야 합니다',
|
||||||
|
(v) => (v && v.length <= 20) || '비밀번호는 20자 이하여야 합니다',
|
||||||
|
// 임시로 완화된 규칙 (영문과 숫자만 체크)
|
||||||
|
(v) => /^(?=.*[a-zA-Z])(?=.*\d)[A-Za-z\d@$!%*?&]/.test(v) ||
|
||||||
|
'영문과 숫자를 포함해야 합니다 (특수문자 선택사항)',
|
||||||
|
]
|
||||||
|
|
||||||
|
const passwordConfirmRules = [
|
||||||
|
(v) => !!v || '비밀번호 확인을 입력해주세요',
|
||||||
|
(v) => v === signupData.value.password || '비밀번호가 일치하지 않습니다',
|
||||||
|
]
|
||||||
|
|
||||||
|
const nameRules = [
|
||||||
|
(v) => !!v || '이름을 입력해주세요',
|
||||||
|
(v) => (v && v.length >= 2) || '이름은 2자 이상이어야 합니다',
|
||||||
|
(v) => (v && v.length <= 10) || '이름은 10자 이하여야 합니다',
|
||||||
|
]
|
||||||
|
|
||||||
|
const emailRules = [
|
||||||
|
(v) => !!v || '이메일을 입력해주세요',
|
||||||
|
(v) => /.+@.+\..+/.test(v) || '올바른 이메일 형식이 아닙니다',
|
||||||
|
]
|
||||||
|
|
||||||
|
const businessNumberRules = [
|
||||||
|
(v) => !v || v.length === 10 || '사업자 번호는 10자리여야 합니다',
|
||||||
|
(v) => !v || /^\d{10}$/.test(v) || '사업자 번호는 숫자만 입력 가능합니다',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 로그인 관련 메서드
|
||||||
const fillDemoCredentials = () => {
|
const fillDemoCredentials = () => {
|
||||||
credentials.value.username = 'user01'
|
credentials.value.username = 'user01'
|
||||||
credentials.value.password = 'passw0rd'
|
credentials.value.password = 'passw0rd'
|
||||||
loginError.value = ''
|
loginError.value = ''
|
||||||
console.log('데모 계정 정보 자동 입력 완료')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
console.log('=== 로그인 시도 시작 ===')
|
if (!isFormValid.value) return
|
||||||
console.log('폼 유효성:', isFormValid.value)
|
|
||||||
console.log('입력된 자격증명:', {
|
|
||||||
username: credentials.value.username,
|
|
||||||
password: credentials.value.password,
|
|
||||||
usernameLength: credentials.value.username?.length,
|
|
||||||
passwordLength: credentials.value.password?.length,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 폼 유효성 검사
|
|
||||||
if (!isFormValid.value) {
|
|
||||||
console.log('폼 유효성 검사 실패')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 빈 값 체크
|
|
||||||
if (!credentials.value.username || !credentials.value.password) {
|
if (!credentials.value.username || !credentials.value.password) {
|
||||||
loginError.value = '아이디와 비밀번호를 모두 입력해주세요'
|
loginError.value = '아이디와 비밀번호를 모두 입력해주세요'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 에러 초기화
|
|
||||||
loginError.value = ''
|
loginError.value = ''
|
||||||
fieldErrors.value = { username: [], password: [] }
|
fieldErrors.value = { username: [], password: [] }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('auth store 로그인 호출 중...')
|
|
||||||
const result = await authStore.login({
|
const result = await authStore.login({
|
||||||
username: credentials.value.username.trim(), // 공백 제거
|
username: credentials.value.username.trim(),
|
||||||
password: credentials.value.password.trim(), // 공백 제거
|
password: credentials.value.password.trim(),
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('로그인 결과:', result)
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log('로그인 성공!')
|
|
||||||
appStore.showSnackbar('로그인되었습니다', 'success')
|
appStore.showSnackbar('로그인되었습니다', 'success')
|
||||||
router.push('/dashboard')
|
router.push('/dashboard')
|
||||||
} else {
|
} else {
|
||||||
console.log('로그인 실패:', result.error)
|
|
||||||
loginError.value = result.error || '로그인에 실패했습니다'
|
loginError.value = result.error || '로그인에 실패했습니다'
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('로그인 에러:', error)
|
console.error('로그인 에러:', error)
|
||||||
loginError.value = '서버 오류가 발생했습니다'
|
loginError.value = '로그인 실패: ' + (error.message || '서버 오류가 발생했습니다')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,26 +449,149 @@ const handleForgotPassword = () => {
|
|||||||
forgotEmail.value = ''
|
forgotEmail.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSignup = () => {
|
// 회원가입 관련 메서드
|
||||||
appStore.showSnackbar('회원가입 기능은 곧 제공될 예정입니다', 'info')
|
const checkUserIdDuplicate = async () => {
|
||||||
|
console.log('아이디 중복 확인 시작:', signupData.value.userId)
|
||||||
|
|
||||||
|
if (!signupData.value.userId || signupData.value.userId.length < 3) {
|
||||||
|
signupError.value = '아이디를 3자 이상 입력해주세요'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingUserId.value = true
|
||||||
|
signupError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await memberApi.get(`/check-duplicate/user-id?userId=${encodeURIComponent(signupData.value.userId)}`)
|
||||||
|
|
||||||
|
console.log('아이디 중복 확인 응답:', response.data)
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
const isDuplicate = response.data.data.isDuplicate
|
||||||
|
if (isDuplicate) {
|
||||||
|
signupError.value = '이미 사용 중인 아이디입니다'
|
||||||
|
userIdChecked.value = false
|
||||||
|
} else {
|
||||||
|
userIdChecked.value = true
|
||||||
|
appStore.showSnackbar('사용 가능한 아이디입니다', 'success')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('아이디 중복 확인 실패:', error)
|
||||||
|
signupError.value = '아이디 중복 확인에 실패했습니다'
|
||||||
|
userIdChecked.value = false
|
||||||
|
} finally {
|
||||||
|
checkingUserId.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkEmailDuplicate = async () => {
|
||||||
|
console.log('이메일 중복 확인 시작:', signupData.value.email)
|
||||||
|
|
||||||
|
if (!signupData.value.email || !/.+@.+\..+/.test(signupData.value.email)) {
|
||||||
|
signupError.value = '올바른 이메일을 입력해주세요'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingEmail.value = true
|
||||||
|
signupError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await memberApi.get(`/check-duplicate/email?email=${encodeURIComponent(signupData.value.email)}`)
|
||||||
|
|
||||||
|
console.log('이메일 중복 확인 응답:', response.data)
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
const isDuplicate = response.data.data.isDuplicate
|
||||||
|
if (isDuplicate) {
|
||||||
|
signupError.value = '이미 사용 중인 이메일입니다'
|
||||||
|
emailChecked.value = false
|
||||||
|
} else {
|
||||||
|
emailChecked.value = true
|
||||||
|
appStore.showSnackbar('사용 가능한 이메일입니다', 'success')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('이메일 중복 확인 실패:', error)
|
||||||
|
signupError.value = '이메일 중복 확인에 실패했습니다'
|
||||||
|
emailChecked.value = false
|
||||||
|
} finally {
|
||||||
|
checkingEmail.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSignup = async () => {
|
||||||
|
console.log('회원가입 시도 시작')
|
||||||
|
console.log('가입 데이터:', signupData.value)
|
||||||
|
// console.log('중복 확인 상태 - 아이디:', userIdChecked.value, '이메일:', emailChecked.value) // 임시 주석
|
||||||
|
|
||||||
|
if (!canSignup.value) {
|
||||||
|
signupError.value = '모든 필드를 올바르게 입력해주세요'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signupLoading.value = true
|
||||||
|
signupError.value = ''
|
||||||
|
signupSuccess.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestData = {
|
||||||
|
userId: signupData.value.userId,
|
||||||
|
password: signupData.value.password,
|
||||||
|
name: signupData.value.name,
|
||||||
|
email: signupData.value.email,
|
||||||
|
businessNumber: signupData.value.businessNumber || null,
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('회원가입 요청 데이터:', requestData)
|
||||||
|
|
||||||
|
const response = await memberApi.post('/register', requestData)
|
||||||
|
|
||||||
|
console.log('회원가입 응답:', response.data)
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
signupSuccess.value = '회원가입이 완료되었습니다! 로그인해주세요'
|
||||||
|
|
||||||
|
// 3초 후 다이얼로그 닫기
|
||||||
|
setTimeout(() => {
|
||||||
|
closeSignupDialog()
|
||||||
|
appStore.showSnackbar('회원가입이 완료되었습니다', 'success')
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('회원가입 실패:', error)
|
||||||
|
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
signupError.value = error.response.data.message
|
||||||
|
} else {
|
||||||
|
signupError.value = '회원가입에 실패했습니다. 다시 시도해주세요'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
signupLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSignupDialog = () => {
|
||||||
showSignup.value = false
|
showSignup.value = false
|
||||||
|
signupData.value = {
|
||||||
|
userId: '',
|
||||||
|
password: '',
|
||||||
|
passwordConfirm: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
businessNumber: '',
|
||||||
|
}
|
||||||
|
signupError.value = ''
|
||||||
|
signupSuccess.value = ''
|
||||||
|
userIdChecked.value = false
|
||||||
|
emailChecked.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 실행
|
// 컴포넌트 마운트 시 실행
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('LoginView 마운트됨')
|
|
||||||
console.log('초기 자격증명:', credentials.value)
|
|
||||||
|
|
||||||
// 이미 로그인된 상태라면 대시보드로 리다이렉트
|
|
||||||
if (authStore.isAuthenticated) {
|
if (authStore.isAuthenticated) {
|
||||||
console.log('이미 로그인된 상태 - 대시보드로 이동')
|
|
||||||
router.push('/dashboard')
|
router.push('/dashboard')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 디버그용 - 3초 후 디버그 정보 숨기기
|
|
||||||
setTimeout(() => {
|
|
||||||
showDebugInfo.value = false
|
|
||||||
}, 10000)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -336,6 +622,10 @@ onMounted(() => {
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-2 {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.login-card {
|
.login-card {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
@ -344,5 +634,14 @@ onMounted(() => {
|
|||||||
.logo-section {
|
.logo-section {
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.d-flex.align-center.gap-2 {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex.align-center.gap-2 .v-btn {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user