Merge branch 'main' into wordDict

This commit is contained in:
yoon 2025-02-07 09:39:57 +09:00
commit 646c775d6a
12 changed files with 465 additions and 366 deletions

38
src/common/commonApi.js Normal file
View File

@ -0,0 +1,38 @@
/*
작성자 : 박지윤
작성일 : 2025-02-04
수정자 :
수정일 :
설명 : 공통 api
*/
import { ref, onMounted } from "vue";
import $api from '@api';
const commonApi = () => {
const colorList = ref([]);
const mbtiList = ref([]);
const pwhintList = ref([]);
const CommonCode = async (endpoint, targetList) => {
try {
const response = await $api.get(`/user/${endpoint}`);
targetList.value = response.data.data.map(item => ({
label: item.CMNCODNAM,
value: item.CMNCODVAL
}));
} catch (error) {
console.error(`Error fetching ${endpoint}:`, error);
targetList.value = [];
}
};
onMounted(async () => {
await CommonCode("color", colorList);
await CommonCode("mbti", mbtiList);
await CommonCode("pwhint", pwhintList);
});
return { colorList, mbtiList, pwhintList };
};
export default commonApi;

View File

@ -16,7 +16,7 @@
</template>
<script setup>
import { ref, watch, watchEffect } from 'vue';
import { ref, watch } from 'vue';
const props = defineProps({
title: {
@ -69,13 +69,16 @@ const props = defineProps({
const emit = defineEmits(['update:data']);
const selectData = ref(props.value);
watchEffect(() => {
if (props.isCommon && props.data.length > 0) {
selectData.value = props.data[0].value; //
} else {
selectData.value = props.value; //
// data
watch(() => props.data, (newData) => {
if (props.isCommon && newData.length > 0) {
selectData.value = newData[0].value;
emit('update:data', selectData.value);
}
emit('update:data', selectData.value);
})
}, { immediate: true });
// selectData
watch(selectData, (newValue) => {
emit('update:data', newValue);
});
</script>

View File

@ -23,6 +23,7 @@
:id="name"
class="form-control"
:type="type"
:max="type === 'date' ? today : null"
@input="updateInput"
:value="computedValue"
:maxLength="maxlength"

View File

@ -0,0 +1,173 @@
<template>
<div class="col-xl-12">
<UserFormInput
title="아이디"
name="id"
:is-alert="idAlert"
:is-essential="true"
:useInputGroup="true"
@update:data="handleIdChange"
:value="id"
/>
<UserFormInput
title="생년월일"
name="birth"
:type="'date'"
:is-essential="true"
:is-alert="birthAlert"
@update:data="birth = $event"
@update:alert="birthAlert = $event"
:value="birth"
/>
<FormSelect
title="비밀번호 힌트"
name="pwhint"
:is-essential="true"
:is-row="false"
:is-label="true"
:is-common="true"
:data="pwhintList"
@update:data="pwhint = $event"
/>
<UserFormInput
title="답변"
name="pwhintRes"
:is-essential="true"
:is-alert="pwhintResAlert"
@update:data="pwhintRes = $event"
@update:alert="pwhintResAlert = $event"
:value="pwhintRes"
/>
<div class="d-flex mt-5">
<RouterLink type="button" class="btn btn-secondary me-2 w-50" to="/login">취소</RouterLink>
<button type="button" @click="handleSubmit" class="btn btn-primary w-50">확인</button>
</div>
</div>
<div v-if="resetForm" class="mt-4">
<UserFormInput
title="새 비밀번호"
name="pw"
type="password"
:isEssential="true"
:is-alert="passwordAlert"
@update:data="password = $event"
@update:alert="passwordAlert = $event"
:value="password"
/>
<UserFormInput
title="비밀번호 확인"
name="pwch"
type="password"
:isEssential="true"
:is-alert="passwordcheckAlert"
@update:data="passwordcheck = $event"
@update:alert="passwordcheckAlert = $event"
@blur="checkPw"
:value="passwordcheck"
/>
<span v-if="passwordcheckError" class="invalid-feedback d-block">{{ passwordcheckError }}</span>
<div class="d-grid gap-2 mt-5 mb-5">
<button type="button" @click="handleNewPassword" class="btn btn-primary">확인</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import $api from '@api';
import commonApi from '@/common/commonApi';
import { useRouter } from 'vue-router';
import { useToastStore } from '@s/toastStore';
import UserFormInput from '@c/input/UserFormInput.vue';
import FormSelect from '../input/FormSelect.vue';
const router = useRouter();
const toastStore = useToastStore();
const id = ref('');
const birth = ref('');
const pwhint = ref('');
const pwhintRes = ref('');
const idAlert = ref(false);
const birthAlert = ref(false);
const pwhintResAlert = ref(false);
const resetForm = ref(false);
const password = ref('');
const passwordcheck = ref('');
const passwordcheckError = ref('');
const passwordAlert = ref(false);
const passwordcheckAlert = ref(false);
const passwordcheckErrorAlert = ref(false);
const { pwhintList } = commonApi();
const handleIdChange = value => {
id.value = value;
idAlert.value = false;
};
// , , , member input
const handleSubmit = async () => {
idAlert.value = id.value.trim() === '';
pwhintResAlert.value = pwhintRes.value.trim() === '';
birthAlert.value = birth.value.trim() === '';
if (idAlert.value || pwhintResAlert.value || birthAlert.value) {
return;
}
const response = await $api.post('user/pwReset', {
id: id.value,
birth: birth.value,
pwhint: pwhint.value,
pwhintRes: pwhintRes.value,
});
if (response.status === 200 && response.data.data === true) {
resetForm.value = true;
} else {
toastStore.onToast('일치하는 정보가 없습니다.', 'e');
}
};
const checkPw = async () => {
if (password.value !== passwordcheck.value) {
passwordcheckError.value = '비밀번호가 일치하지 않습니다.';
passwordcheckErrorAlert.value = true;
} else {
passwordcheckError.value = '';
passwordcheckErrorAlert.value = false;
}
};
//
const handleNewPassword = async () => {
passwordAlert.value = password.value.trim() === '';
passwordcheckAlert.value = passwordcheck.value.trim() === '';
if (passwordAlert.value || passwordcheckAlert.value || passwordcheckErrorAlert.value) {
return;
}
const response = await $api.patch('user/pwNew', {
id: id.value,
password: password.value
});
if (response.status === 200 && response.data.data === true) {
toastStore.onToast('비밀번호가 재설정 되었습니다.', 's');
router.push('/login');
}
};
</script>

View File

@ -25,7 +25,7 @@
<div class="mb-3 d-flex justify-content-around">
<div>
<input type="checkbox" class="form-check-input" id="rememberCheck" />
<input type="checkbox" class="form-check-input" id="rememberCheck" v-model="remember" />
<label class="form-check-label fw-bold" for="rememberCheck">&nbsp;자동로그인</label>
</div>
<RouterLink class="text-dark fw-bold" to="/register">등록신청</RouterLink>
@ -40,11 +40,17 @@
import router from '@/router';
import { ref } from 'vue';
import UserFormInput from '@c/input/UserFormInput.vue';
import { useUserStore } from '@s/useUserStore';
import { useToastStore } from '@s/toastStore';
const id = ref('');
const password = ref('');
const idAlert = ref(false);
const passwordAlert = ref(false);
const remember = ref(false);
const userStore = useUserStore();
const toastStore = useToastStore();
const handleIdChange = value => {
id.value = value;
@ -57,21 +63,19 @@
};
const handleSubmit = async () => {
const response = await $api.post('user/login', {
loginId: id.value,
password: password.value,
remember: remember.value,
});
try {
const response = await $api.post('user/login', {
loginId: id.value,
password: password.value,
remember: false,
});
if (response.status === 200) {
console.log('로그인 성공', response.data);
router.push('/');
}
} catch (error) {
console.error('로그인 실패', error);
if (response.status === 200) {
await userStore.userInfo();
router.push('/');
} else {
toastStore.onToast('아이디 혹은 비밀번호가 틀렸습니다.', 'e');
}
};

View File

@ -17,7 +17,7 @@
/>
</svg>
</span>
<h2>{{ title }}</h2>
<h3 class="fw-bold">{{ title }}</h3>
</div>
</template>

View File

@ -58,9 +58,11 @@
:is-essential="true"
:is-row="false"
:is-label="true"
:is-common="true"
:data="pwhintList"
@update:data="pwhint = $event"
/>
<UserFormInput
title="답변"
name="pwhintRes"
@ -152,8 +154,9 @@
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref } from 'vue';
import $api from '@api';
import commonApi from '@/common/commonApi'
import UserFormInput from '@c/input/UserFormInput.vue';
import FormSelect from '@c/input/FormSelect.vue';
import ArrInput from '@c/input/ArrInput.vue';
@ -161,8 +164,6 @@
import { useRouter } from 'vue-router';
import { useToastStore } from '@s/toastStore';
const pwhintList = ['현재 살고 있는 동네', '가장 기억에 남는 책', '좋아하는 음식'];
const router = useRouter();
const profile = ref(null);
@ -173,7 +174,6 @@
const password = ref('');
const passwordcheck = ref('');
const passwordcheckError = ref('');
const pwhint = ref(0);
const pwhintRes = ref('');
const name = ref('');
const birth = ref('');
@ -181,10 +181,9 @@
const detailAddress = ref('');
const postcode = ref(''); //
const phone = ref('');
const colorList = ref([]);
const mbtiList = ref([]);
const color = ref(''); // color
const mbti = ref(''); // MBTI
const pwhint = ref(''); // pwhint
const profilAlert = ref(false);
const idAlert = ref(false);
@ -252,27 +251,8 @@
}
};
const Colors = async () => {
const response = await $api.get('/user/color');
colorList.value = response.data.data.map(item => ({
label: item.CMNCODNAM,
value: item.CMNCODVAL
}));
};
const Mbtis = async () => {
const response = await $api.get('/user/mbti');
mbtiList.value = response.data.data.map(item => ({
label: item.CMNCODNAM,
value: item.CMNCODVAL
}));
};
onMounted(() => {
Colors();
Mbtis();
});
// , mbti,
const { colorList, mbtiList, pwhintList } = commonApi();
//
const handleAddressUpdate = (addressData) => {
@ -295,50 +275,50 @@
//
const handleSubmit = async () => {
idAlert.value = id.value.trim() === '';
passwordAlert.value = password.value.trim() === '';
passwordcheckAlert.value = passwordcheck.value.trim() === '';
pwhintResAlert.value = pwhintRes.value.trim() === '';
nameAlert.value = name.value.trim() === '';
birthAlert.value = birth.value.trim() === '';
addressAlert.value = address.value.trim() === '';
phoneAlert.value = phone.value.trim() === '';
idAlert.value = id.value.trim() === '';
passwordAlert.value = password.value.trim() === '';
passwordcheckAlert.value = passwordcheck.value.trim() === '';
pwhintResAlert.value = pwhintRes.value.trim() === '';
nameAlert.value = name.value.trim() === '';
birthAlert.value = birth.value.trim() === '';
addressAlert.value = address.value.trim() === '';
phoneAlert.value = phone.value.trim() === '';
//
if (!profile.value) {
profilerr.value = '프로필 이미지를 선택해주세요.';
profilAlert.value = true;
} else {
profilerr.value = '';
profilAlert.value = false;
}
//
if (!profile.value) {
profilerr.value = '프로필 이미지를 선택해주세요.';
profilAlert.value = true;
} else {
profilerr.value = '';
profilAlert.value = false;
}
if (profilAlert.value || idAlert.value || idErrorAlert.value || passwordAlert.value || passwordcheckAlert.value ||
passwordcheckErrorAlert.value || pwhintResAlert.value || nameAlert.value || birthAlert.value || addressAlert.value || phoneAlert.value) {
return;
}
if (profilAlert.value || idAlert.value || idErrorAlert.value || passwordAlert.value || passwordcheckAlert.value ||
passwordcheckErrorAlert.value || pwhintResAlert.value || nameAlert.value || birthAlert.value || addressAlert.value || phoneAlert.value) {
return;
}
const formData = new FormData();
formData.append('memberIds', id.value);
formData.append('memberPwd', password.value);
formData.append('memberPwh', pwhintList[pwhint.value]);
formData.append('memberPwr', pwhintRes.value);
formData.append('memberNam', name.value);
formData.append('memberArr', address.value);
formData.append('memberDtl', detailAddress.value);
formData.append('memberZip', postcode.value);
formData.append('memberBth', birth.value);
formData.append('memberTel', phone.value);
formData.append('memberCol', color.value);
formData.append('memberMbt', mbti.value);
formData.append('memberPrf', profile.value);
const formData = new FormData();
formData.append('memberIds', id.value);
formData.append('memberPwd', password.value);
formData.append('memberPwh', pwhint.value);
formData.append('memberPwr', pwhintRes.value);
formData.append('memberNam', name.value);
formData.append('memberArr', address.value);
formData.append('memberDtl', detailAddress.value);
formData.append('memberZip', postcode.value);
formData.append('memberBth', birth.value);
formData.append('memberTel', phone.value);
formData.append('memberCol', color.value);
formData.append('memberMbt', mbti.value);
formData.append('memberPrf', profile.value);
const response = await $api.post('/user/join', formData, { isFormData : true });
const response = await $api.post('/user/join', formData, { isFormData : true });
if (response.status === 200) {
toastStore.onToast('등록신청이 완료되었습니다. 관리자 승인 후 이용가능합니다.', 's');
router.push('/login');
}
if (response.status === 200) {
toastStore.onToast('등록신청이 완료되었습니다. 관리자 승인 후 이용가능합니다.', 's');
router.push('/login');
}
};
</script>

View File

@ -8,161 +8,14 @@
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
<ul class="navbar-nav flex-row align-items-center ms-auto">
<!-- Language -->
<li class="nav-item dropdown-language dropdown me-2 me-xl-0">
<a class="nav-link dropdown-toggle hide-arrow" href="javascript:void(0);" data-bs-toggle="dropdown">
<i class="bx bx-globe bx-md"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="javascript:void(0);" data-language="en" data-text-direction="ltr">
<span>English</span>
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0);" data-language="fr" data-text-direction="ltr">
<span>French</span>
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0);" data-language="ar" data-text-direction="rtl">
<span>Arabic</span>
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0);" data-language="de" data-text-direction="ltr">
<span>German</span>
</a>
</li>
</ul>
</li>
<!-- /Language -->
<!-- Style Switcher -->
<li class="nav-item dropdown-style-switcher dropdown me-2 me-xl-0">
<a class="nav-link dropdown-toggle hide-arrow" href="javascript:void(0);" data-bs-toggle="dropdown">
<i class="bx bx-md"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end dropdown-styles">
<li>
<a class="dropdown-item" href="javascript:void(0);" data-theme="light">
<span><i class="bx bx-sun bx-md me-3"></i>Light</span>
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0);" data-theme="dark">
<span><i class="bx bx-moon bx-md me-3"></i>Dark</span>
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0);" data-theme="system">
<span><i class="bx bx-desktop bx-md me-3"></i>System</span>
</a>
</li>
</ul>
</li>
<!-- / Style Switcher-->
<button class="btn p-1" @click="switchToLightMode"><i class='bx bxs-sun link-warning'></i></button>
<button class="btn p-1" @click="switchToDarkMode"><i class='bx bxs-moon' ></i></button>
<!-- Quick links -->
<li class="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">
<a
class="nav-link dropdown-toggle hide-arrow"
href="javascript:void(0);"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
>
<i class="bx bx-grid-alt bx-md"></i>
</a>
<div class="dropdown-menu dropdown-menu-end p-0">
<div class="dropdown-menu-header border-bottom">
<div class="dropdown-header d-flex align-items-center py-3">
<h6 class="mb-0 me-auto">Shortcuts</h6>
<a
href="javascript:void(0)"
class="dropdown-shortcuts-add py-2"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Add shortcuts"
><i class="bx bx-plus-circle text-heading"></i
></a>
</div>
</div>
<div class="dropdown-shortcuts-list scrollable-container">
<div class="row row-bordered overflow-visible g-0">
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-calendar bx-26px text-heading"></i>
</span>
<a href="app-calendar.html" class="stretched-link">Calendar</a>
<small>Appointments</small>
</div>
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-food-menu bx-26px text-heading"></i>
</span>
<a href="app-invoice-list.html" class="stretched-link">Invoice App</a>
<small>Manage Accounts</small>
</div>
</div>
<div class="row row-bordered overflow-visible g-0">
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-user bx-26px text-heading"></i>
</span>
<a href="app-user-list.html" class="stretched-link">User App</a>
<small>Manage Users</small>
</div>
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-check-shield bx-26px text-heading"></i>
</span>
<a href="app-access-roles.html" class="stretched-link">Role Management</a>
<small>Permission</small>
</div>
</div>
<div class="row row-bordered overflow-visible g-0">
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-pie-chart-alt-2 bx-26px text-heading"></i>
</span>
<a href="index.html" class="stretched-link">Dashboard</a>
<small>User Dashboard</small>
</div>
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-cog bx-26px text-heading"></i>
</span>
<a href="pages-account-settings-account.html" class="stretched-link">Setting</a>
<small>Account Settings</small>
</div>
</div>
<div class="row row-bordered overflow-visible g-0">
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-help-circle bx-26px text-heading"></i>
</span>
<a href="pages-faq.html" class="stretched-link">FAQs</a>
<small>FAQs & Articles</small>
</div>
<div class="dropdown-shortcuts-item col">
<span class="dropdown-shortcuts-icon rounded-circle mb-3">
<i class="bx bx-window-open bx-26px text-heading"></i>
</span>
<a href="modal-examples.html" class="stretched-link">Modals</a>
<small>Useful Popups</small>
</div>
</div>
</div>
</div>
</li>
<!-- Quick links -->
<button @click="switchToDarkMode">다크 모드</button>
<button @click="switchToLightMode">라이트 모드</button>
<i class="bx bx-bell bx-md bx-log-out cursor-pointer p-1" @click="handleLogout"></i>
<!-- Notification -->
<li class="nav-item dropdown-notifications navbar-dropdown dropdown me-3 me-xl-2">
<li class="nav-item dropdown-notifications navbar-dropdown dropdown me-3 me-xl-2 p-1">
<a
class="nav-link dropdown-toggle hide-arrow"
href="javascript:void(0);"
@ -284,120 +137,6 @@
</div>
</div>
</li>
<li class="list-group-item list-group-item-action dropdown-notifications-item marked-as-read">
<div class="d-flex">
<div class="flex-shrink-0 me-3">
<div class="avatar">
<img src="/img/avatars/9.png" class="rounded-circle" />
</div>
</div>
<div class="flex-grow-1">
<h6 class="small mb-0">Application has been approved 🚀</h6>
<small class="mb-1 d-block text-body">Your ABC project application has been approved.</small>
<small class="text-muted">2 days ago</small>
</div>
<div class="flex-shrink-0 dropdown-notifications-actions">
<a href="javascript:void(0)" class="dropdown-notifications-read"
><span class="badge badge-dot"></span
></a>
<a href="javascript:void(0)" class="dropdown-notifications-archive"
><span class="bx bx-x"></span
></a>
</div>
</div>
</li>
<li class="list-group-item list-group-item-action dropdown-notifications-item marked-as-read">
<div class="d-flex">
<div class="flex-shrink-0 me-3">
<div class="avatar">
<span class="avatar-initial rounded-circle bg-label-success"
><i class="bx bx-pie-chart-alt"></i
></span>
</div>
</div>
<div class="flex-grow-1">
<h6 class="small mb-0">Monthly report is generated</h6>
<small class="mb-1 d-block text-body">July monthly financial report is generated </small>
<small class="text-muted">3 days ago</small>
</div>
<div class="flex-shrink-0 dropdown-notifications-actions">
<a href="javascript:void(0)" class="dropdown-notifications-read"
><span class="badge badge-dot"></span
></a>
<a href="javascript:void(0)" class="dropdown-notifications-archive"
><span class="bx bx-x"></span
></a>
</div>
</div>
</li>
<li class="list-group-item list-group-item-action dropdown-notifications-item marked-as-read">
<div class="d-flex">
<div class="flex-shrink-0 me-3">
<div class="avatar">
<img src="/img/avatars/5.png" class="rounded-circle" />
</div>
</div>
<div class="flex-grow-1">
<h6 class="small mb-0">Send connection request</h6>
<small class="mb-1 d-block text-body">Peter sent you connection request</small>
<small class="text-muted">4 days ago</small>
</div>
<div class="flex-shrink-0 dropdown-notifications-actions">
<a href="javascript:void(0)" class="dropdown-notifications-read"
><span class="badge badge-dot"></span
></a>
<a href="javascript:void(0)" class="dropdown-notifications-archive"
><span class="bx bx-x"></span
></a>
</div>
</div>
</li>
<li class="list-group-item list-group-item-action dropdown-notifications-item">
<div class="d-flex">
<div class="flex-shrink-0 me-3">
<div class="avatar">
<img src="/img/avatars/6.png" class="rounded-circle" />
</div>
</div>
<div class="flex-grow-1">
<h6 class="small mb-0">New message from Jane</h6>
<small class="mb-1 d-block text-body">Your have new message from Jane</small>
<small class="text-muted">5 days ago</small>
</div>
<div class="flex-shrink-0 dropdown-notifications-actions">
<a href="javascript:void(0)" class="dropdown-notifications-read"
><span class="badge badge-dot"></span
></a>
<a href="javascript:void(0)" class="dropdown-notifications-archive"
><span class="bx bx-x"></span
></a>
</div>
</div>
</li>
<li class="list-group-item list-group-item-action dropdown-notifications-item marked-as-read">
<div class="d-flex">
<div class="flex-shrink-0 me-3">
<div class="avatar">
<span class="avatar-initial rounded-circle bg-label-warning"
><i class="bx bx-error"></i
></span>
</div>
</div>
<div class="flex-grow-1">
<h6 class="small mb-0">CPU is running high</h6>
<small class="mb-1 d-block text-body">CPU Utilization Percent is currently at 88.63%,</small>
<small class="text-muted">5 days ago</small>
</div>
<div class="flex-shrink-0 dropdown-notifications-actions">
<a href="javascript:void(0)" class="dropdown-notifications-read"
><span class="badge badge-dot"></span
></a>
<a href="javascript:void(0)" class="dropdown-notifications-archive"
><span class="bx bx-x"></span
></a>
</div>
</div>
</li>
</ul>
</li>
<li class="border-top">
@ -413,9 +152,7 @@
<!-- User -->
<li class="nav-item navbar-dropdown dropdown-user dropdown">
<a class="nav-link dropdown-toggle hide-arrow p-0" href="javascript:void(0);" data-bs-toggle="dropdown">
<div class="avatar avatar-online">
<img src="/img/avatars/1.png" class="w-px-40 h-auto rounded-circle" />
</div>
<img v-if="user" :src="`http://localhost:10325/upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-40 h-auto rounded-circle"/>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
@ -490,17 +227,38 @@
</nav>
</template>
<script setup>
import { useAuthStore } from '@s/useAuthStore';
import { useUserStore } from '@s/useUserStore';
import { useRouter } from 'vue-router';
import { useThemeStore } from '@s/darkmode';
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
const user = ref(null);
const authStore = useAuthStore();
const userStore = useUserStore();
const router = useRouter();
const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();
onMounted(() => {
onMounted(async () => {
if (isDarkMode) {
switchToDarkMode();
} else {
switchToLightMode();
}
await userStore.userInfo();
user.value = userStore.user;
});
const handleLogout = async () => {
await authStore.logout();
router.push('/login');
};
</script>
<style></style>

View File

@ -1,9 +1,12 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@s/useAuthStore';
// 초기 렌더링 속도를 위해 지연 로딩 사용
const routes = [
{
path: '/',
name: "Home",
component: () => import('@v/MainView.vue'),
// meta: { requiresAuth: true }
},
@ -38,18 +41,26 @@ const routes = [
},
{
path: '/login',
name: 'Login',
component: () => import('@v/user/TheLogin.vue'),
meta: { layout: 'NoLayout' },
meta: { layout: 'NoLayout', requiresGuest: true },
},
{
path: '/register',
name: 'Register',
component: () => import('@v/user/TheRegister.vue'),
meta: { layout: 'NoLayout', requiresGuest: true },
},
{
path: '/pw',
name: 'Password',
component: () => import('@v/user/ThePassword.vue'),
meta: { layout: 'NoLayout', requiresGuest: true },
},
{
path: '/vacation',
component: () => import('@v/vacation/VacationManagement.vue'),
},
{
path: '/register',
component: () => import('@v/user/TheRegister.vue'),
meta: { layout: 'NoLayout' },
},
{
path: '/voteboard',
component: () => import('@v/voteboard/TheVoteBoard.vue'),
@ -80,4 +91,41 @@ const router = createRouter({
routes: routes,
})
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
await authStore.checkAuthStatus(); // 로그인 상태 확인
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
// 로그인이 필요한 페이지인데 로그인되지 않은 경우 → 로그인 페이지로 이동
next({ name: 'Login' });
} else if (to.meta.requiresGuest && authStore.isAuthenticated) {
// 비로그인 사용자만 접근 가능한 페이지인데 로그인된 경우 → 홈으로 이동
next({ name: 'Home' });
} else {
next();
}
});
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
// 최초 앱 로드 시 인증 상태 체크
await authStore.checkAuthStatus()
// 현재 라우트에 인증이 필요한지 확인
const requiresAuth = to.meta.requiresAuth === true
if (requiresAuth && !authStore.isAuthenticated) {
// 인증되지 않은 사용자를 로그인 페이지로 리다이렉트
// 원래 가려던 페이지를 쿼리 파라미터로 전달
next({
name: 'Login',
query: { redirect: to.fullPath }
})
} else {
next()
}
})
export default router

View File

@ -0,0 +1,50 @@
/*
작성자 : 박지윤
작성일 : 2025-02-04
수정자 :
수정일 :
설명 : 로그인 상태, 로그아웃
*/
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
import $api from "@api";
export const useAuthStore = defineStore('auth', () => {
const user = ref(null);
// 로그인 여부 확인
const isAuthenticated = computed(() => user.value !== null);
// 로그인 상태 확인
const checkAuthStatus = async () => {
const response = await $api.get('user/isLogin');
if (response.data.status === "OK" && response.data.data) {
user.value = response.data.data;
} else {
user.value = null;
}
};
// 로그아웃
const logout = async () => {
const response = await $api.get('user/logout');
// 로그아웃 성공 시 사용자 상태 초기화
if (response.data.status === "OK") {
user.value = null;
return true;
} else {
console.error("로그아웃 실패:", response.data.data);
return false;
}
};
return {
user,
isAuthenticated,
checkAuthStatus,
logout
};
});

View File

@ -0,0 +1,28 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
import $api from "@api";
export const useUserStore = defineStore('userInfo', () => {
const user = ref(null);
// 사용자 정보 가져오기
const userInfo = async () => {
try {
const response = await $api.get('user/userInfo');
if (response.data.status === "OK") {
user.value = response.data.data;
console.log(user.value);
return true;
}
return false;
} catch (error) {
console.error("사용자 정보 조회 실패:", error);
return false;
}
};
return {
user,
userInfo,
};
});

View File

@ -0,0 +1,16 @@
<template>
<div class="d-flex justify-content-center align-items-center">
<div class="container rounded bg-white my-10 py-10" style="max-width: 500px">
<LogoHeader title="비밀번호 재설정"/>
<FindPassword />
</div>
</div>
</template>
<script setup>
import FindPassword from '@/components/user/FindPassword.vue';
import LogoHeader from '@c/user/LogoHeader.vue';
</script>
<style></style>