frontend backend connection

This commit is contained in:
unknown 2025-06-16 11:11:13 +09:00
parent 0f291383a1
commit 9f64f93ed8
3 changed files with 427 additions and 114 deletions

View File

@ -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__);

View File

@ -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
} }
} }

View File

@ -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>