Merge remote-tracking branch 'origin/main' into board-0117
This commit is contained in:
commit
d2f7aed762
@ -84,6 +84,9 @@
|
|||||||
<!-- Page JS -->
|
<!-- Page JS -->
|
||||||
<!-- <script src="/js/dashboards-analytics.js"></script> -->
|
<!-- <script src="/js/dashboards-analytics.js"></script> -->
|
||||||
|
|
||||||
|
<!-- daum address -->
|
||||||
|
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
BIN
public/img/avatars/default-Profile.jpg
Normal file
BIN
public/img/avatars/default-Profile.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
128
src/components/input/ArrInput.vue
Normal file
128
src/components/input/ArrInput.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<label :for="name" class="col-md-2 col-form-label">
|
||||||
|
{{ title }}
|
||||||
|
<span :class="isEssential ? 'link-danger' : 'd-none'">*</span>
|
||||||
|
</label>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" @click="openAddressSearch">주소찾기</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="d-flex mb-3">
|
||||||
|
<input
|
||||||
|
:id="name"
|
||||||
|
class="form-control me-2 w-25"
|
||||||
|
type="text"
|
||||||
|
v-model="postcode"
|
||||||
|
placeholder="우편번호"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-model="address"
|
||||||
|
placeholder="기본주소"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-model="detailAddress"
|
||||||
|
placeholder="상세주소"
|
||||||
|
@input="updateDetailAddress"
|
||||||
|
:maxLength="maxlength"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">{{ title }}를 확인해주세요.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '라벨',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'nameplz',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isEssential: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
maxlength: {
|
||||||
|
type: Number,
|
||||||
|
default: 30,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
isAlert: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:data', 'update:alert']);
|
||||||
|
|
||||||
|
// 주소 관련 상태 관리
|
||||||
|
const postcode = ref('');
|
||||||
|
const address = ref('');
|
||||||
|
const detailAddress = ref('');
|
||||||
|
|
||||||
|
// 주소 검색 팝업 열기
|
||||||
|
const openAddressSearch = () => {
|
||||||
|
new window.daum.Postcode({
|
||||||
|
oncomplete: (data) => {
|
||||||
|
postcode.value = data.zonecode;
|
||||||
|
address.value = data.address;
|
||||||
|
|
||||||
|
// 전체 주소 데이터 내보내기
|
||||||
|
emitAddressData();
|
||||||
|
|
||||||
|
// 상세주소 입력 필드로 포커스 이동
|
||||||
|
setTimeout(() => {
|
||||||
|
const detailInput = document.querySelector('input[placeholder="상세주소"]');
|
||||||
|
if (detailInput) detailInput.focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}).open();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 상세주소 업데이트
|
||||||
|
const updateDetailAddress = (event) => {
|
||||||
|
detailAddress.value = event.target.value;
|
||||||
|
emitAddressData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 전체 주소 데이터 내보내기
|
||||||
|
const emitAddressData = () => {
|
||||||
|
const fullAddress = {
|
||||||
|
postcode: postcode.value,
|
||||||
|
address: address.value,
|
||||||
|
detailAddress: detailAddress.value,
|
||||||
|
fullAddress: `${address.value} ${detailAddress.value}`.trim()
|
||||||
|
};
|
||||||
|
emits('update:data', fullAddress);
|
||||||
|
};
|
||||||
|
|
||||||
|
// isAlert를 false로 설정
|
||||||
|
watch([postcode, address], ([newPostcode, newAddress]) => {
|
||||||
|
if (newPostcode && newAddress) {
|
||||||
|
emits('update:alert', false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-4 row">
|
<div class="mb-2" :class="isRow ?'row' : ''">
|
||||||
<label for="input-ss" class="col-md-2 col-form-label" :class="isLabel ? 'd-block' : 'd-none'">
|
<label :for="name" class="col-md-2 col-form-label" :class="isLabel ? 'd-block' : 'd-none'">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
<span :class="isEssential ? 'text-red' : 'none'">*</span>
|
<span :class="isEssential ? 'link-danger' : 'none'">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="col-md-10">
|
<div :class="isRow ?'col-md-10' : 'col-md-12'">
|
||||||
<select class="form-select" id="input-ss" v-model="selectData">
|
<select class="form-select" :id="name" v-model="selectData">
|
||||||
<option v-for="(item , i) in data" :key="item" :value="i" :selected="value == i">{{ item }}</option>
|
<option v-for="(item , i) in data" :key="item" :value="i" :selected="value == i">{{ item }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +50,12 @@ const props = defineProps({
|
|||||||
isLabel : {
|
isLabel : {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
required: true,
|
required: false,
|
||||||
|
},
|
||||||
|
isRow : {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
required: false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label
|
<label :for="name" class="col-md-2 col-form-label">
|
||||||
:for="name"
|
|
||||||
class="col-md-2 col-form-label"
|
|
||||||
>
|
|
||||||
{{ title }}
|
{{ title }}
|
||||||
<span :class="isEssential ? 'text-red' : 'none'">*</span>
|
<span :class="isEssential ? 'link-danger' : 'd-none'">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div v-if="useInputGroup" class="input-group mb-3">
|
<div v-if="useInputGroup" class="input-group mb-3">
|
||||||
@ -17,10 +14,8 @@
|
|||||||
:value="value"
|
:value="value"
|
||||||
:maxLength="maxlength"
|
:maxLength="maxlength"
|
||||||
:placeholder="title"
|
:placeholder="title"
|
||||||
aria-label="Recipient's username"
|
|
||||||
:aria-describedby="`${name}-addon`"
|
|
||||||
/>
|
/>
|
||||||
<span class="input-group-text" :id="`${name}-addon`">@ localhost.co.kr</span>
|
<span class="input-group-text">@ localhost.co.kr</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<input
|
<input
|
||||||
@ -34,7 +29,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">{{ title }}를 확인해주세요.</div>
|
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">{{ title }}를 확인해주세요.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -85,7 +80,7 @@ const prop = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(['update:data'])
|
const emits = defineEmits(['update:data', 'update:alert'])
|
||||||
|
|
||||||
const updateInput = function (event) {
|
const updateInput = function (event) {
|
||||||
//Type Number 일때 maxlength 적용 안됨 방지
|
//Type Number 일때 maxlength 적용 안됨 방지
|
||||||
@ -93,11 +88,13 @@ const updateInput = function (event) {
|
|||||||
event.target.value = event.target.value.slice(0, prop.maxlength);
|
event.target.value = event.target.value.slice(0, prop.maxlength);
|
||||||
}
|
}
|
||||||
emits('update:data', event.target.value);
|
emits('update:data', event.target.value);
|
||||||
|
|
||||||
|
// 값이 입력될 때 isAlert를 false로 설정
|
||||||
|
if (event.target.value.trim() !== '') { emits('update:alert', false); }
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.none {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
67
src/components/user/LoginForm.vue
Normal file
67
src/components/user/LoginForm.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="col-xl-12">
|
||||||
|
<UserFormInput
|
||||||
|
title="아이디"
|
||||||
|
name="id"
|
||||||
|
:is-alert="idAlert"
|
||||||
|
:useInputGroup="true"
|
||||||
|
@update:data="handleIdChange"
|
||||||
|
:value="id"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UserFormInput
|
||||||
|
title="비밀번호"
|
||||||
|
name="pw"
|
||||||
|
type="password"
|
||||||
|
:is-alert="passwordAlert"
|
||||||
|
@update:data="handlePasswordChange"
|
||||||
|
:value="password"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="d-grid gap-2 mt-7 mb-5">
|
||||||
|
<button type="submit" class="btn btn-primary">로그인</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 d-flex justify-content-around">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="form-check-input" id="rememberCheck" />
|
||||||
|
<label class="form-check-label fw-bold" for="rememberCheck"> 자동로그인</label>
|
||||||
|
</div>
|
||||||
|
<RouterLink class="text-dark fw-bold" to="/register">등록신청</RouterLink>
|
||||||
|
<RouterLink class="text-dark fw-bold" to="/pw">비밀번호 찾기</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import UserFormInput from '@c/input/UserFormInput.vue';
|
||||||
|
|
||||||
|
const id = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
const idAlert = ref(false);
|
||||||
|
const passwordAlert = ref(false);
|
||||||
|
|
||||||
|
const emit = defineEmits(['submit']);
|
||||||
|
|
||||||
|
const handleIdChange = value => {
|
||||||
|
id.value = value;
|
||||||
|
idAlert.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordChange = value => {
|
||||||
|
password.value = value;
|
||||||
|
passwordAlert.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
idAlert.value = id.value.trim() === '';
|
||||||
|
passwordAlert.value = password.value.trim() === '';
|
||||||
|
|
||||||
|
if (!idAlert.value && !passwordAlert.value) {
|
||||||
|
emit('submit', { id: id.value, password: password.value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
32
src/components/user/LogoHeader.vue
Normal file
32
src/components/user/LogoHeader.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-center">
|
||||||
|
<span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
xlink:href="data:image/png;base64,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"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const prop = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '라벨',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
216
src/components/user/RegisterForm.vue
Normal file
216
src/components/user/RegisterForm.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="text-center">
|
||||||
|
<label
|
||||||
|
for="profilePic"
|
||||||
|
class="rounded-circle m-auto border-label-secondary ui-bg-cover position-relative cursor-pointer"
|
||||||
|
id="profileLabel"
|
||||||
|
style="width: 100px; height: 100px; background-image: url(public/img/avatars/default-Profile.jpg)"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<span class="link-danger position-absolute">*</span>
|
||||||
|
<input type="file" id="profilePic" class="d-none" @change="profileUpload" />
|
||||||
|
<span v-if="profilerr" class="invalid-feedback d-block">{{ profilerr }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-12">
|
||||||
|
<UserFormInput
|
||||||
|
title="아이디"
|
||||||
|
name="id"
|
||||||
|
:isEssential="true"
|
||||||
|
:is-alert="idAlert"
|
||||||
|
:useInputGroup="true"
|
||||||
|
@update:data="id = $event"
|
||||||
|
@update:alert="idAlert = $event"
|
||||||
|
:value="id"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
:value="passwordcheck"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UserFormInput
|
||||||
|
title="비밀번호 힌트"
|
||||||
|
name="pwhint"
|
||||||
|
:is-alert="passwordhintAlert"
|
||||||
|
@update:data="passwordhint = $event"
|
||||||
|
@update:alert="passwordhintAlert = $event"
|
||||||
|
:value="passwordhint"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<UserFormInput
|
||||||
|
title="이름"
|
||||||
|
name="name"
|
||||||
|
:is-essential="true"
|
||||||
|
:is-alert="nameAlert"
|
||||||
|
@update:data="name = $event"
|
||||||
|
@update:alert="nameAlert = $event"
|
||||||
|
:value="name"
|
||||||
|
class="me-2 w-50"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormSelect
|
||||||
|
title="컬러"
|
||||||
|
name="color"
|
||||||
|
:is-essential="true"
|
||||||
|
:is-row="false"
|
||||||
|
:is-label="true"
|
||||||
|
:data="colorList"
|
||||||
|
@update:data="color = $event"
|
||||||
|
class="w-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<UserFormInput
|
||||||
|
title="생년월일"
|
||||||
|
name="birth"
|
||||||
|
:is-essential="true"
|
||||||
|
:is-alert="birthAlert"
|
||||||
|
@update:data="birth = $event"
|
||||||
|
@update:alert="birthAlert = $event"
|
||||||
|
:value="birth"
|
||||||
|
class="me-2 w-50"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormSelect
|
||||||
|
title="MBTI"
|
||||||
|
name="mbti"
|
||||||
|
:is-essential="true"
|
||||||
|
:is-row="false"
|
||||||
|
:is-label="true"
|
||||||
|
:data="mbtiList"
|
||||||
|
@update:data="mbti = $event"
|
||||||
|
class="w-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ArrInput
|
||||||
|
title="주소"
|
||||||
|
name="arr"
|
||||||
|
:isEssential="true"
|
||||||
|
:is-alert="arrAlert"
|
||||||
|
@update:data="arr = $event"
|
||||||
|
@update:alert="arrAlert = $event"
|
||||||
|
:value="arr"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UserFormInput
|
||||||
|
title="휴대전화"
|
||||||
|
name="phone"
|
||||||
|
:isEssential="true"
|
||||||
|
:is-alert="phoneAlert"
|
||||||
|
@update:data="phone = $event"
|
||||||
|
@update:alert="phoneAlert = $event"
|
||||||
|
:maxlength="11"
|
||||||
|
:value="phone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="d-flex mt-5">
|
||||||
|
<RouterLink type="button" class="btn btn-secondary me-2 w-50" to="/login">취소</RouterLink>
|
||||||
|
<button type="submit" class="btn btn-primary w-50">등록신청</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import UserFormInput from '@c/input/UserFormInput.vue';
|
||||||
|
import FormSelect from '@/components/input/FormSelect.vue';
|
||||||
|
import ArrInput from '@/components/input/ArrInput.vue';
|
||||||
|
|
||||||
|
const profile = ref(null);
|
||||||
|
const profilerr = ref('');
|
||||||
|
|
||||||
|
const id = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
const passwordcheck = ref('');
|
||||||
|
const passwordhint = ref('');
|
||||||
|
const name = ref('');
|
||||||
|
const birth = ref('');
|
||||||
|
const arr = ref('');
|
||||||
|
const phone = ref('');
|
||||||
|
const color = ref('');
|
||||||
|
const mbti = ref('');
|
||||||
|
|
||||||
|
const idAlert = ref(false);
|
||||||
|
const passwordAlert = ref(false);
|
||||||
|
const passwordcheckAlert = ref(false);
|
||||||
|
const passwordhintAlert = ref(false);
|
||||||
|
const nameAlert = ref(false);
|
||||||
|
const birthAlert = ref(false);
|
||||||
|
const arrAlert = ref(false);
|
||||||
|
const phoneAlert = ref(false);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
idAlert.value = id.value.trim() === '';
|
||||||
|
passwordAlert.value = password.value.trim() === '';
|
||||||
|
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
||||||
|
passwordhintAlert.value = passwordhint.value.trim() === '';
|
||||||
|
nameAlert.value = name.value.trim() === '';
|
||||||
|
birthAlert.value = birth.value.trim() === '';
|
||||||
|
arrAlert.value = arr.value.trim() === '';
|
||||||
|
phoneAlert.value = phone.value.trim() === '';
|
||||||
|
|
||||||
|
if (!profile.value) {
|
||||||
|
profilerr.value = '프로필 이미지를 선택해주세요.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const profileValid = (size, type) => {
|
||||||
|
const maxSize = 5 * 1024 * 1024;
|
||||||
|
const validTypes = ['image/jpeg', 'image/png'];
|
||||||
|
if (size > maxSize) {
|
||||||
|
profilerr.value = '5MB 미만의 파일만 업로드할 수 있습니다.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!validTypes.includes(type)) {
|
||||||
|
profilerr.value = '지원되지 않는 파일 형식입니다. JPEG 또는 PNG만 가능합니다.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
profilerr.value = '';
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const profileUpload = e => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const profileLabel = document.getElementById('profileLabel');
|
||||||
|
|
||||||
|
if (!profileValid(file.size, file.type)) {
|
||||||
|
e.target.value = '';
|
||||||
|
profileLabel.style.backgroundImage = 'url("public/img/avatars/default-Profile.jpg")';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const profilePic = e.target;
|
||||||
|
const url = URL.createObjectURL(profilePic.files[0]);
|
||||||
|
profileLabel.style.backgroundImage = `url(${url})`;
|
||||||
|
profile.value = file;
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorList = ['blue', 'red', 'pink'];
|
||||||
|
const mbtiList = ['ISTP', 'ENFP', 'INTJ'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
@ -32,16 +32,24 @@ const routes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// 레이아웃 필요없을 때 예시
|
{
|
||||||
// {
|
path: '/wordDict',
|
||||||
// path: '/login',
|
component: () => import('@v/wordDict/wordDict.vue'),
|
||||||
// component: () => import('@v/user/TheLogin.vue'),
|
},
|
||||||
// meta: { layout: 'NoLayout' },
|
{
|
||||||
// },
|
path: '/login',
|
||||||
|
component: () => import('@v/user/TheLogin.vue'),
|
||||||
|
meta: { layout: 'NoLayout' },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/vacation',
|
path: '/vacation',
|
||||||
component: () => import('@v/vacation/VacationManagement.vue'),
|
component: () => import('@v/vacation/VacationManagement.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
component: () => import('@v/user/TheRegister.vue'),
|
||||||
|
meta: { layout: 'NoLayout' },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/sample',
|
path: '/sample',
|
||||||
component: () => import('@c/calendar/SampleCalendar.vue'),
|
component: () => import('@c/calendar/SampleCalendar.vue'),
|
||||||
|
|||||||
@ -127,7 +127,7 @@ const write = async () => {
|
|||||||
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file); // 첨부 파일
|
formData.append("MEMBERSEQ",registrantId); // 첨부 파일
|
||||||
formData.append("CMNFLEPAT", fileInfo.path); // 파일 경로
|
formData.append("CMNFLEPAT", fileInfo.path); // 파일 경로
|
||||||
formData.append("CMNFLENAM", fileInfo.originalName); // 파일 명(확장자제외)
|
formData.append("CMNFLENAM", fileInfo.originalName); // 파일 명(확장자제외)
|
||||||
formData.append("CMNFLEORG", fileInfo.originalName); // 원본 파일명(확장자제외)
|
formData.append("CMNFLEORG", fileInfo.originalName); // 원본 파일명(확장자제외)
|
||||||
|
|||||||
19
src/views/user/TheLogin.vue
Normal file
19
src/views/user/TheLogin.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="d-flex justify-content-center align-items-center vh-100">
|
||||||
|
<div class="container container-p-y rounded bg-white" style="max-width: 500px">
|
||||||
|
<LogoHeader title="LOCALNET" />
|
||||||
|
<LoginForm @submit="handleSubmit" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import LoginForm from '@c/user/LoginForm.vue';
|
||||||
|
import LogoHeader from '@c/user/LogoHeader.vue';
|
||||||
|
|
||||||
|
const handleSubmit = async ({ id, password }) => {
|
||||||
|
console.log('Login');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
17
src/views/user/TheRegister.vue
Normal file
17
src/views/user/TheRegister.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<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">
|
||||||
|
<RegisterForm @submit="handleSubmit" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import RegisterForm from '@c/user/RegisterForm.vue';
|
||||||
|
|
||||||
|
const handleSubmit = async (formData) => {
|
||||||
|
console.log('Register');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
207
src/views/wordDict/wordDict.vue
Normal file
207
src/views/wordDict/wordDict.vue
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<!-- 타이틀, 검색 -->
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-6">
|
||||||
|
<h5 class="mb-0">용어집</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<SearchBar @update:data="search"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 단어 갯수, 작성하기 -->
|
||||||
|
<div class="mt-4">
|
||||||
|
단어 : {{ filteredList.length }}
|
||||||
|
<WriteButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="nav-align-top">
|
||||||
|
<ul class="nav nav-pills" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button type="button" class="nav-link">ㄱ</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button type="button" class="nav-link">ㄴ</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button type="button" class="nav-link">ㄷ</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav nav-pills" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button type="button" class="nav-link">a</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button type="button" class="nav-link">b</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button type="button" class="nav-link">c</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 카테고리 -->
|
||||||
|
<div>
|
||||||
|
<ul v-if="cateList.length">
|
||||||
|
<li v-for="item in cateList" :key="item.CMNCODVAL">
|
||||||
|
<button>{{ item.CMNCODNAM }}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 용어 리스트 -->
|
||||||
|
<div>
|
||||||
|
<!-- 로딩 중일 때 -->
|
||||||
|
<div v-if="loading">로딩 중...</div>
|
||||||
|
|
||||||
|
<!-- 에러 메시지 -->
|
||||||
|
<div v-if="error" class="error">{{ error }}</div>
|
||||||
|
|
||||||
|
<!-- 단어 목록 -->
|
||||||
|
<ul v-if="filteredList.length" class="word-list">
|
||||||
|
<li v-for="item in filteredList" :key="item.WRDDICSEQ">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div>
|
||||||
|
({{ item.category }}) <strong>{{ item.WRDDICTTL }}</strong>
|
||||||
|
</div>
|
||||||
|
<WriteButton />
|
||||||
|
</div>
|
||||||
|
<p>{{ item.WRDDICCON }}</p>
|
||||||
|
<div>
|
||||||
|
<img :src="'/img/avatars/' + item.author.profileImage" alt="최초 작성자" />
|
||||||
|
최초 작성자 {{ item.author.name }}
|
||||||
|
작성일 {{ new Date(item.author.createdAt).toLocaleString() }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img :src="'/img/avatars/' + item.lastEditor.profileImage" alt="최근 수정자" />
|
||||||
|
최근 수정자 {{ item.lastEditor.name }}
|
||||||
|
수정일 {{ new Date(item.lastEditor.updatedAt).toLocaleString() }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- 데이터가 없을 때 -->
|
||||||
|
<div v-else-if="!loading && !error">용어집의 용어가 없습니다.</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 작성 및 수정 -->
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<FormSelect/>
|
||||||
|
<button>쇼핑몰</button>
|
||||||
|
<button>저장</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<FormInput/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<QEditor/>
|
||||||
|
<button>저장</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watchEffect, computed } from 'vue';
|
||||||
|
import axios from '@api';
|
||||||
|
import SearchBar from '@c/search/SearchBar.vue';
|
||||||
|
import WriteButton from '@c/button/WriteBtn.vue';
|
||||||
|
import QEditor from '@/components/editor/QEditor.vue';
|
||||||
|
import FormInput from '@/components/input/FormInput.vue';
|
||||||
|
import FormSelect from '@/components/input/FormSelect.vue';
|
||||||
|
|
||||||
|
// /** 상세로 이동 */
|
||||||
|
// const goDetail = idx => {
|
||||||
|
// router.push(`/board/get/${idx}`);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 용어집
|
||||||
|
const wordList = ref([]);
|
||||||
|
// 카테고리
|
||||||
|
const cateList = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref('');
|
||||||
|
|
||||||
|
// 검색
|
||||||
|
const searchText = ref('');
|
||||||
|
|
||||||
|
// 단어 및 카테고리 목록 통합 API 호출
|
||||||
|
const fetchAllData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
try {
|
||||||
|
// 단어
|
||||||
|
const wordResponse = await axios.get('worddict/getWordList');
|
||||||
|
wordList.value = wordResponse.data.data.data;
|
||||||
|
console.log('단어 데이터:', wordList.value);
|
||||||
|
|
||||||
|
// 카테고리
|
||||||
|
const categoryResponse = await axios.get('worddict/getWordCategory');
|
||||||
|
cateList.value = categoryResponse.data.data;
|
||||||
|
console.log('카테고리 데이터:', cateList.value);
|
||||||
|
} catch (err) {
|
||||||
|
error.value = '데이터를 가져오는 중 문제가 발생했습니다.';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 검색
|
||||||
|
const search = (e) => {
|
||||||
|
const trimmedSearchText = e.trim();
|
||||||
|
if (trimmedSearchText.length === 0) {
|
||||||
|
alert('검색어를 입력해주세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchText.value = trimmedSearchText;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 검색
|
||||||
|
const filteredList = computed(() =>
|
||||||
|
wordList.value.filter(item =>
|
||||||
|
item.WRDDICTTL.toLowerCase().includes(searchText.value.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// `watchEffect`로 API 호출
|
||||||
|
watchEffect(() => {
|
||||||
|
fetchAllData();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.card > div {
|
||||||
|
padding: 0 30px
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-list {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.word-list li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-list li ~ li {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user