로그인 백엔드 구현 및 로그인 프론트 기능 구현
This commit is contained in:
parent
001bba7cb1
commit
fcfbd1fd96
|
|
@ -1,8 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
// import 'ant-design-vue/dist/reset.css';
|
||||
// import '~/assets/font/PretendardGOV/font.css';
|
||||
// import '~/assets/css/index.css';
|
||||
// import 'dayjs/locale/ko';
|
||||
import 'uno.css';
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import axios, { type AxiosError, type AxiosResponse } from 'axios';
|
||||
// import { useAuthStore } from '~/stores/login';
|
||||
// import { useDefaultStore, useLoadingStore } from '~/stores';
|
||||
|
||||
const baseURL = import.meta.env.VITE_API_URL as string;
|
||||
|
||||
export const useAxios = () => {
|
||||
// const loadingStore = useLoadingStore();
|
||||
// const defaultStore = useDefaultStore();
|
||||
// const { siteInfo } = storeToRefs(defaultStore);
|
||||
// const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
return Promise.resolve(config);
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response: AxiosResponse<any, any>) => {
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
if (error.status === 403) {
|
||||
return router.push('/');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import type { LoginReqType, LoginResType } from '~/types/login';
|
||||
|
||||
export const DEFAULT_AUTHENTICATION_VALUE: LoginReqType = {
|
||||
memberId: '',
|
||||
password: '',
|
||||
remember: false
|
||||
};
|
||||
|
||||
export const DEFAULT_AUTHORIZATION_VALUE: LoginResType = {
|
||||
memberName: '',
|
||||
deptNm: '',
|
||||
instNm: '',
|
||||
menuList: [],
|
||||
permitApiList: [],
|
||||
authenticated: false
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import type { CellRendererProps } from 'tui-grid/types/renderer';
|
||||
|
||||
export class ConditionButtonRenderer {
|
||||
el: HTMLElement;
|
||||
|
||||
constructor(props: CellRendererProps) {
|
||||
const { rowKey, grid } = props;
|
||||
const { options } = props.columnInfo.renderer;
|
||||
const data = grid.getRow(rowKey);
|
||||
|
||||
console.log(!data.baAnswerYn);
|
||||
if (!data.baAnswerYn) {
|
||||
const el = document.createElement('button');
|
||||
el.className = 'ant-btn ant-btn-primary';
|
||||
el.onclick = () => options?.onClick(data);
|
||||
el.innerHTML = `<span>${options?.buttonName}</span>`;
|
||||
this.el = el;
|
||||
} else {
|
||||
const el = document.createElement('span');
|
||||
el.innerHTML = options?.spanName;
|
||||
this.el = el;
|
||||
}
|
||||
}
|
||||
|
||||
beforeDestroy(): void {}
|
||||
|
||||
focused(): void {}
|
||||
|
||||
getElement(): Element {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
mounted(parent: HTMLElement): void {}
|
||||
|
||||
render(props: CellRendererProps): void {}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import type { CellRendererProps } from 'tui-grid/types/renderer';
|
||||
|
||||
export class ConditionIconButtonRenderer {
|
||||
el: HTMLElement;
|
||||
|
||||
constructor(props: CellRendererProps) {
|
||||
const { options } = props.columnInfo.renderer;
|
||||
const data = props.grid.getRow(props.rowKey);
|
||||
|
||||
if (options?.condition(data)) {
|
||||
const el = document.createElement('a');
|
||||
// @ts-ignore
|
||||
const { icon } = options;
|
||||
el.innerHTML = `
|
||||
<svg focusable="false" data-icon="${icon.name}" width="1em" height="2em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896">
|
||||
<path d="${icon.icon.children[0].attrs.d}" />
|
||||
</svg>
|
||||
`;
|
||||
el.className = 'ant-btn ant-btn-primary';
|
||||
el.onclick = () => options?.onClick(data);
|
||||
|
||||
this.el = el;
|
||||
} else {
|
||||
this.el = document.createElement('span');
|
||||
}
|
||||
}
|
||||
|
||||
beforeDestroy(): void {}
|
||||
|
||||
focused(): void {}
|
||||
|
||||
getElement(): Element {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
mounted(parent: HTMLElement): void {}
|
||||
|
||||
render(props: CellRendererProps): void {}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import type { CellRendererProps } from 'tui-grid/types/renderer';
|
||||
|
||||
export class FunctionalButtonRenderer {
|
||||
el: HTMLElement;
|
||||
|
||||
constructor(props: CellRendererProps) {
|
||||
const el = document.createElement('button');
|
||||
const options = props.columnInfo.renderer.options;
|
||||
const data = props.grid.getRow(props.rowKey);
|
||||
el.className = 'ant-btn ant-btn-primary';
|
||||
el.onclick = () => options?.onClick(data);
|
||||
el.innerHTML = `<span>${options?.buttonName}</span>`;
|
||||
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
beforeDestroy(): void {}
|
||||
|
||||
focused(): void {}
|
||||
|
||||
getElement(): Element {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
mounted(parent: HTMLElement): void {}
|
||||
|
||||
render(props: CellRendererProps): void {}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import type { CellEditorProps } from 'tui-grid/types/editor';
|
||||
|
||||
export class MaxLengthTextEditor {
|
||||
el: HTMLInputElement;
|
||||
|
||||
constructor(props: CellEditorProps) {
|
||||
const el = document.createElement('input');
|
||||
el.type = 'text';
|
||||
el.value = props.value as string;
|
||||
el.maxLength = props.columnInfo.editor?.options?.maxlength;
|
||||
el.placeholder = props.columnInfo.editor?.options?.placeholder;
|
||||
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.el.value;
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.el.select();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import type { CellRendererProps } from 'tui-grid/types/renderer';
|
||||
import type { MenuType } from '~/types/sys/menu';
|
||||
|
||||
export class MenuSatisChargerRenderer {
|
||||
el: HTMLElement;
|
||||
|
||||
constructor(props: CellRendererProps) {
|
||||
const el = document.createElement('div');
|
||||
|
||||
el.style['gap'] = '8px';
|
||||
el.className = 'flex justify-center';
|
||||
|
||||
const { rowKey, grid } = props;
|
||||
const options = props.columnInfo.renderer.options as any;
|
||||
|
||||
const data = grid.getRow(rowKey) as unknown as MenuType;
|
||||
if (!data.menuType.endsWith('API')) {
|
||||
if (data?.menuMngId) {
|
||||
const { onView, onDelete } = options;
|
||||
const viewLink = document.createElement('a');
|
||||
const deleteLink = document.createElement('a');
|
||||
|
||||
viewLink.innerHTML = '보기';
|
||||
viewLink.onclick = () => onView(data);
|
||||
|
||||
deleteLink.innerHTML = '삭제';
|
||||
deleteLink.onclick = () => onDelete(data);
|
||||
|
||||
el.append(viewLink, deleteLink);
|
||||
} else {
|
||||
const { onEdit } = options;
|
||||
const link = document.createElement('a');
|
||||
link.innerHTML = '등록';
|
||||
link.onclick = () => onEdit(data);
|
||||
|
||||
el.append(link);
|
||||
}
|
||||
}
|
||||
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
beforeDestroy(): void {}
|
||||
|
||||
focused(): void {}
|
||||
|
||||
getElement(): Element {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
mounted(parent: HTMLElement): void {}
|
||||
|
||||
render(props: CellRendererProps): void {
|
||||
this.el.innerHTML = '';
|
||||
const { rowKey, grid } = props;
|
||||
const options = props.columnInfo.renderer.options as any;
|
||||
|
||||
const data = grid.getRow(rowKey) as unknown as MenuType;
|
||||
if (!data.menuType.endsWith('API')) {
|
||||
if (data?.menuMngId) {
|
||||
const { onView, onDelete } = options;
|
||||
const viewLink = document.createElement('a');
|
||||
const deleteLink = document.createElement('a');
|
||||
|
||||
viewLink.innerHTML = '보기';
|
||||
viewLink.onclick = () => onView(data);
|
||||
|
||||
deleteLink.innerHTML = '삭제';
|
||||
deleteLink.onclick = () => onDelete(data);
|
||||
|
||||
this.el.append(viewLink, deleteLink);
|
||||
} else {
|
||||
const { onEdit } = options;
|
||||
const link = document.createElement('a');
|
||||
link.innerHTML = '등록';
|
||||
link.onclick = () => onEdit(data);
|
||||
|
||||
this.el.append(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type { CellRendererProps } from 'tui-grid/types/renderer';
|
||||
|
||||
export class RadioHeaderRenderer {
|
||||
el: HTMLElement;
|
||||
|
||||
constructor(props: CellRendererProps) {
|
||||
const { rowKey, grid } = props;
|
||||
|
||||
const { options } = props.columnInfo.renderer;
|
||||
const data = grid.getRow(rowKey);
|
||||
|
||||
const el = document.createElement('input');
|
||||
el.name = 'gridRadio';
|
||||
el.type = 'radio';
|
||||
el.className = '';
|
||||
el.addEventListener('change', () => options?.onChange(data));
|
||||
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
beforeDestroy(): void {}
|
||||
|
||||
focused(): void {}
|
||||
|
||||
getElement(): Element {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
mounted(parent: HTMLElement): void {}
|
||||
|
||||
render(props: CellRendererProps): void {}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import type { OptPreset } from 'tui-grid/types/options';
|
||||
|
||||
export const TUI_GRID_THEME: OptPreset = {
|
||||
selection: {
|
||||
background: '#4daaf9',
|
||||
border: '#004082'
|
||||
},
|
||||
scrollbar: {
|
||||
background: '#f5f5f5',
|
||||
thumb: '#d9d9d9',
|
||||
active: '#c1c1c1'
|
||||
},
|
||||
outline: {
|
||||
border: '#e1e2e5'
|
||||
},
|
||||
area: {
|
||||
header: {
|
||||
border: '#e1e2e5',
|
||||
background: '#f8f8f9'
|
||||
}
|
||||
},
|
||||
row: {
|
||||
even: {
|
||||
background: '#EFFAFF'
|
||||
}
|
||||
},
|
||||
cell: {
|
||||
normal: {
|
||||
background: 'white',
|
||||
border: '#eee',
|
||||
showVerticalBorder: true
|
||||
},
|
||||
header: {
|
||||
background: '#f8f8f9',
|
||||
showHorizontalBorder: true,
|
||||
showVerticalBorder: true
|
||||
},
|
||||
rowHeader: {
|
||||
border: '#e1e2e5',
|
||||
background: '#f8f8f9',
|
||||
showHorizontalBorder: false,
|
||||
showVerticalBorder: false
|
||||
},
|
||||
editable: {
|
||||
// 수정 가능 셀 색상은 아래에
|
||||
background: 'white'
|
||||
},
|
||||
selectedHeader: {
|
||||
background: '#e0e0e0'
|
||||
},
|
||||
focused: {
|
||||
border: '#418ed4'
|
||||
},
|
||||
disabled: {
|
||||
text: '#333',
|
||||
background: 'white'
|
||||
},
|
||||
invalid: {
|
||||
background: '#D60440'
|
||||
},
|
||||
required: {
|
||||
background: 'white'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'empty'
|
||||
});
|
||||
|
||||
const dummy = ref('phone');
|
||||
const domain = ref(''); // 도메인 선택 부분
|
||||
|
||||
const domains = [
|
||||
{ value: 'gmail.com', label: 'gmail.com' },
|
||||
{ value: 'naver.com', label: 'naver.com' },
|
||||
{ value: 'daum.net', label: 'daum.net' },
|
||||
{ value: '직접 입력', label: '직접 입력' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row justify="center" class="w-full h-full items-center">
|
||||
<a-col :span="12">
|
||||
<a-card
|
||||
bordered
|
||||
style="padding: 24px; background-color: #f9f9f9; border-radius: 8px"
|
||||
>
|
||||
<!-- 아이디 찾기 타이틀 -->
|
||||
<h2 style="text-align: center; font-weight: bold; font-size: 24px">
|
||||
아이디 찾기
|
||||
</h2>
|
||||
|
||||
<!-- 검색 방법 선택 -->
|
||||
<div style="display: flex; justify-content: center; margin: 16px 0">
|
||||
<a-radio-group v-model:value="dummy">
|
||||
<a-radio value="phone">휴대폰으로 찾기</a-radio>
|
||||
<a-radio value="email">이메일로 찾기</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<a-form :colon="false" label-align="left">
|
||||
<!-- 이름 -->
|
||||
<a-form-item
|
||||
label="이름"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-input placeholder="이름을 입력하세요" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 자동입력방지문자 (라디오 값이 'phone'일 때만 표시) -->
|
||||
<a-form-item
|
||||
v-if="dummy === 'phone'"
|
||||
label="자동입력방지문자"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<a-input
|
||||
disabled
|
||||
value="564866"
|
||||
style="width: 100px; text-align: center"
|
||||
/>
|
||||
<a-button>새로고침</a-button>
|
||||
<a-button>음성듣기</a-button>
|
||||
</div>
|
||||
<a-input
|
||||
placeholder="자동입력 방지문자를 입력하세요."
|
||||
style="margin-top: 8px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 휴대전화 (라디오 값이 'phone'일 때만 표시) -->
|
||||
<a-form-item
|
||||
v-if="dummy === 'phone'"
|
||||
label="휴대전화"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<a-input style="width: 60px" maxlength="4" />
|
||||
<a-input style="width: 60px" maxlength="4" />
|
||||
<a-input style="width: 60px" maxlength="4" />
|
||||
<a-button>인증번호</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="dummy === 'email'"
|
||||
label="이메일"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<!-- 이메일 입력 필드 -->
|
||||
<a-input v-model="dummy" style="width: 120px" />
|
||||
<span> @ </span>
|
||||
<!-- 도메인 입력 필드 -->
|
||||
<a-input v-model="dummy" style="width: 150px; margin-right: 18px" />
|
||||
<!-- 직접 입력 드롭다운 -->
|
||||
<a-select
|
||||
v-model="domains"
|
||||
style="width: 150px"
|
||||
@change="handleDomainChange"
|
||||
>
|
||||
<a-select-option value="직접 입력">직접 입력</a-select-option>
|
||||
<a-select-option value="gmail.com">gmail.com</a-select-option>
|
||||
<a-select-option value="naver.com">naver.com</a-select-option>
|
||||
<a-select-option value="daum.net">daum.net</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 인증번호 (라디오 값이 'phone'일 때만 표시) -->
|
||||
<a-form-item
|
||||
v-if="dummy === 'phone'"
|
||||
label="인증번호"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-input placeholder="인증번호를 입력하세요" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 아이디 찾기 버튼 -->
|
||||
<div style="text-align: center; margin-top: 16px">
|
||||
<a-button
|
||||
type="primary"
|
||||
style="background-color: #1e90ff; color: white; width: 100px"
|
||||
>
|
||||
아이디 찾기
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
|
@ -1,49 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
const useAuthStore = ref('');
|
||||
const remember = ref(false);
|
||||
const memberId = ref('');
|
||||
const password = ref('');
|
||||
import { useAuthStore } from '~/stores/login';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'empty'
|
||||
});
|
||||
|
||||
// const router = useRouter();
|
||||
// const store = useAuthStore();
|
||||
// const { authentication } = storeToRefs(store);
|
||||
//
|
||||
// onBeforeMount(() => {
|
||||
// store.loadRemember();
|
||||
// });
|
||||
//
|
||||
// onBeforeUnmount(() => {
|
||||
// authentication.value = {
|
||||
// ...authentication.value,
|
||||
// memberId: ''
|
||||
// };
|
||||
// });
|
||||
//
|
||||
// watch(authentication.value, (newValue) => {
|
||||
// if (newValue.remember) {
|
||||
// store.setRemember();
|
||||
// } else {
|
||||
// store.initRemember();
|
||||
// }
|
||||
// });
|
||||
const router = useRouter();
|
||||
const store = useAuthStore();
|
||||
const { loginRequest } = storeToRefs(store);
|
||||
|
||||
const login = async () => {
|
||||
// try {
|
||||
// const { data } = await store.authenticate();
|
||||
// store.authorize(data);
|
||||
//
|
||||
// await router.push('/');
|
||||
// } catch (e) {
|
||||
// message.error('아이디 또는 비밀번호를 확인해주세요.');
|
||||
// }
|
||||
try {
|
||||
const { data } = await store.LoginAPI();
|
||||
store.loginResponse = data;
|
||||
console.log(JSON.stringify(store.loginResponse));
|
||||
|
||||
await router.push('/');
|
||||
} catch (e) {
|
||||
message.error('아이디 또는 비밀번호를 확인해주세요.');
|
||||
}
|
||||
};
|
||||
|
||||
const validateLogin = computed(() => {
|
||||
return false;
|
||||
return loginRequest.value.memberId && loginRequest.value.password;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -64,7 +43,7 @@ const validateLogin = computed(() => {
|
|||
<div style="height: 18px"></div>
|
||||
|
||||
<a-form-item>
|
||||
<a-checkbox v-model:checked="remember">
|
||||
<a-checkbox v-model:checked="loginRequest.remember">
|
||||
키보드보안 프로그램적용
|
||||
</a-checkbox>
|
||||
<div style="height: 10px"></div>
|
||||
|
|
@ -78,7 +57,7 @@ const validateLogin = computed(() => {
|
|||
<a-form :colon="false" label-align="left">
|
||||
<a-form-item label="아이디" :label-col="{ span: 5 }">
|
||||
<a-input
|
||||
v-model:value="memberId"
|
||||
v-model:value="loginRequest.memberId"
|
||||
placeholder="아이디를 입력하세요"
|
||||
maxLength="20"
|
||||
/>
|
||||
|
|
@ -86,10 +65,10 @@ const validateLogin = computed(() => {
|
|||
|
||||
<a-form-item label="비밀번호" :label-col="{ span: 5 }">
|
||||
<a-input-password
|
||||
v-model:value="password"
|
||||
v-model:value="loginRequest.password"
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
maxLength="20"
|
||||
@keyup.enter="login"
|
||||
@keyup.enter="login()"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
|
|
@ -97,7 +76,7 @@ const validateLogin = computed(() => {
|
|||
<a-button
|
||||
type="primary"
|
||||
block
|
||||
@click="login"
|
||||
@click="login()"
|
||||
:disabled="!validateLogin"
|
||||
style="font-weight: bold"
|
||||
>
|
||||
|
|
@ -106,15 +85,15 @@ const validateLogin = computed(() => {
|
|||
</a-form-item>
|
||||
|
||||
<div style="display: flex; justify-content: center; margin-top: 16px">
|
||||
<router-link to="/login/id" style="color: #1890ff"
|
||||
<router-link to="#" style="color: #1890ff"
|
||||
>아이디 찾기</router-link
|
||||
>
|
||||
<span style="margin: 0 8px; color: #888">|</span>
|
||||
<router-link to="/login/pw" style="color: #1890ff"
|
||||
<router-link to="#" style="color: #1890ff"
|
||||
>비밀번호 찾기</router-link
|
||||
>
|
||||
<span style="margin: 0 8px; color: #888">|</span>
|
||||
<router-link to="/login/join" style="color: #1890ff"
|
||||
<router-link to="#" style="color: #1890ff"
|
||||
>회원가입</router-link
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import locale from 'ant-design-vue/es/locale/ko_KR';
|
||||
import { DEFAULT_THEME } from '~/constants/theme/ui';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const adminJoinStore = ref('');
|
||||
const member = ref('');
|
||||
|
||||
definePageMeta({
|
||||
layout: 'empty'
|
||||
});
|
||||
|
||||
const changeUrl = (activeKey: string) => {
|
||||
router.push(activeKey);
|
||||
};
|
||||
|
||||
function activeKey(key) {
|
||||
console.log('Active Tab:', key);
|
||||
}
|
||||
|
||||
const disabledInst = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const disabledCert = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const disabledForm = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const disabledCmptn = computed(() => {
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-config-provider :locale="locale" :theme="DEFAULT_THEME">
|
||||
<a-row class="w-full h-full">
|
||||
<a-row justify="center" class="w-full h-full items-center">
|
||||
<a-col :span="15">
|
||||
<a-card bordered class="p-5">
|
||||
<a-typography-title :level="4" type="secondary" class="text-center">
|
||||
참여기관 회원가입
|
||||
</a-typography-title>
|
||||
|
||||
<a-tabs default-active-key="1" v-model:active-key="activeKey">
|
||||
<a-tab-pane key="/admin/login/join/trms" tab="01.약관동의" />
|
||||
<a-tab-pane
|
||||
key="/admin/login/join/inst"
|
||||
tab="02.기관선택"
|
||||
:disabled="disabledInst"
|
||||
/>
|
||||
<a-tab-pane
|
||||
key="/admin/login/join/cert"
|
||||
tab="03.본인인증"
|
||||
:disabled="disabledCert"
|
||||
/>
|
||||
<a-tab-pane
|
||||
key="/admin/login/join"
|
||||
tab="04.정보입력"
|
||||
:disabled="disabledForm"
|
||||
/>
|
||||
<a-tab-pane
|
||||
key="/admin/login/join/cmptn"
|
||||
tab="05.가입완료"
|
||||
:disabled="disabledCmptn"
|
||||
/>
|
||||
</a-tabs>
|
||||
|
||||
<slot />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-row>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'empty'
|
||||
});
|
||||
|
||||
const dummy = ref<string>('phone');
|
||||
const domain = ref(''); // 도메인 선택 부분
|
||||
|
||||
const domains = [
|
||||
{ value: 'gmail.com', label: 'gmail.com' },
|
||||
{ value: 'naver.com', label: 'naver.com' },
|
||||
{ value: 'daum.net', label: 'daum.net' },
|
||||
{ value: '직접 입력', label: '직접 입력' }
|
||||
];
|
||||
|
||||
const handleDomainChange = (value) => {
|
||||
if (value !== '직접 입력') {
|
||||
domain.value = value;
|
||||
} else {
|
||||
domain.value = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row justify="center" class="w-full h-full items-center">
|
||||
<a-col :span="12">
|
||||
<a-card
|
||||
bordered
|
||||
style="padding: 24px; background-color: #f9f9f9; border-radius: 8px"
|
||||
>
|
||||
<!-- 아이디 찾기 타이틀 -->
|
||||
<h2 style="text-align: center; font-weight: bold; font-size: 24px">
|
||||
비밀번호 재설정
|
||||
</h2>
|
||||
|
||||
<!-- 검색 방법 선택 -->
|
||||
<div style="display: flex; justify-content: center; margin: 16px 0">
|
||||
<a-radio-group v-model:value="dummy">
|
||||
<a-radio defaultValue="phone">휴대폰으로 찾기</a-radio>
|
||||
<a-radio value="email">이메일로 찾기</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<a-form :colon="false" label-align="left">
|
||||
<!-- 이름 -->
|
||||
<a-form-item
|
||||
label="이름"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-input placeholder="이름을 입력하세요" />
|
||||
</a-form-item>
|
||||
<!-- 아이디 -->
|
||||
<a-form-item
|
||||
label="아이디"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-input placeholder="아이디를 입력하세요" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 휴대전화 (라디오 값이 'phone'일 때만 표시) -->
|
||||
<a-form-item
|
||||
v-if="dummy === 'phone'"
|
||||
label="휴대전화"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<a-input style="width: 90px" maxlength="4" />
|
||||
<a-input style="width: 90px" maxlength="4" />
|
||||
<a-input style="width: 90px" maxlength="4" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="dummy === 'email'"
|
||||
label="이메일"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<!-- 이메일 입력 필드 -->
|
||||
<a-input v-model="dummy" style="width: 120px" />
|
||||
<span> @ </span>
|
||||
<!-- 도메인 입력 필드 -->
|
||||
<a-input v-model="dummy" style="width: 150px; margin-right: 18px" />
|
||||
<!-- 직접 입력 드롭다운 -->
|
||||
<a-select
|
||||
v-model="domains"
|
||||
style="width: 150px"
|
||||
@change="handleDomainChange"
|
||||
>
|
||||
<a-select-option value="직접 입력">직접 입력</a-select-option>
|
||||
<a-select-option value="gmail.com">gmail.com</a-select-option>
|
||||
<a-select-option value="naver.com">naver.com</a-select-option>
|
||||
<a-select-option value="daum.net">daum.net</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 암호 찾기 버튼 -->
|
||||
<div style="text-align: center; margin-top: 16px">
|
||||
<a-button
|
||||
v-if="dummy === 'phone'"
|
||||
type="primary"
|
||||
style="background-color: #1e90ff; color: white; width: 100px"
|
||||
>
|
||||
휴대폰 인증
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
type="primary"
|
||||
style="background-color: #1e90ff; color: white; width: 100px"
|
||||
>
|
||||
이메일 인증
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import {useAxios} from "~/composables/useAxios";
|
||||
import type {LoginRequestType, LoginResponseType} from "~/types/login";
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
DEFAULT_AUTHENTICATION_VALUE,
|
||||
DEFAULT_AUTHORIZATION_VALUE
|
||||
} from '~/constants/login';
|
||||
|
||||
export const useAuthStore = defineStore('authStore', () => {
|
||||
const loginRequest = ref<LoginRequestType>(
|
||||
cloneDeep(DEFAULT_AUTHENTICATION_VALUE)
|
||||
);
|
||||
|
||||
const loginResponse = ref<LoginResponseType>(
|
||||
cloneDeep(DEFAULT_AUTHORIZATION_VALUE)
|
||||
);
|
||||
|
||||
const LoginAPI = async () => {
|
||||
return await useAxios().post(`/api/login`, loginRequest.value);
|
||||
};
|
||||
|
||||
return {
|
||||
loginRequest,
|
||||
loginResponse,
|
||||
LoginAPI
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import type { MenuType } from '../sys/menu';
|
||||
|
||||
export type LoginRequestType = {
|
||||
memberId: string;
|
||||
password: string;
|
||||
remember: boolean;
|
||||
};
|
||||
|
||||
export type LoginResponseType = {
|
||||
memberName: string;
|
||||
instNm: string;
|
||||
deptNm: string;
|
||||
menuList: AuthorizationMenuType[];
|
||||
permitApiList: PermitApiType[];
|
||||
authenticated: boolean;
|
||||
};
|
||||
|
||||
export type AuthorizationMenuType = {
|
||||
menuId: string;
|
||||
upMenuId: string;
|
||||
menuDepth: number;
|
||||
menuName: string;
|
||||
menuType: MenuType;
|
||||
menuUrl: string;
|
||||
bcId: string;
|
||||
contentId: string;
|
||||
|
||||
children: AuthorizationMenuType[];
|
||||
};
|
||||
|
||||
export type PermitApiType = {
|
||||
menuUrl: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
export type MenuType = {
|
||||
menuId: string;
|
||||
siteId: string;
|
||||
upMenuId: string;
|
||||
menuDepth: number;
|
||||
menuOrder: number;
|
||||
menuName: string;
|
||||
menuType: 'MENU' | 'PAGE' | 'API' | 'TAB' | 'COMMON_MENU' | 'COMMON_API';
|
||||
menuFeature: 'PAGE' | 'LIST' | 'DETAIL' | 'CREATE' | 'UPDATE' | 'DELETE';
|
||||
menuLayout: string;
|
||||
menuUrl: string;
|
||||
menuMethod: string;
|
||||
menuDescription: string;
|
||||
menuLinkTarget: 'CURRENT' | 'BLANK';
|
||||
menuUseSatisfaction: boolean;
|
||||
menuUseMngInfo: boolean;
|
||||
menuMngId: string;
|
||||
menuStatus: 'ENABLED' | 'HIDDEN' | 'DISABLED';
|
||||
delYn: boolean;
|
||||
useYn: boolean;
|
||||
frstRgtrId: string;
|
||||
frstRegDt: string;
|
||||
lastMdfrId: string;
|
||||
lastMdfcnDt: string;
|
||||
};
|
||||
Loading…
Reference in New Issue