인증 관련 토스트 제거 및 브라우저 비밀번호 저장안뜨게

This commit is contained in:
nevermoregb 2025-03-07 14:16:57 +09:00
parent 2af99d9b51
commit 627e29ec7a
4 changed files with 244 additions and 253 deletions

View File

@ -42,7 +42,7 @@ $api.interceptors.response.use(
switch (error.response.status) { switch (error.response.status) {
case 401: case 401:
if (!error.config.headers.isLogin) { if (!error.config.headers.isLogin) {
toastStore.onToast('인증이 필요합니다.', 'e'); // toastStore.onToast('인증이 필요합니다.', 'e');
} }
break; break;
case 403: case 403:

View File

@ -14,25 +14,22 @@
:placeholder="title" :placeholder="title"
:disabled="disabled" :disabled="disabled"
:min="min" :min="min"
autocomplete="off"
@focusout="$emit('focusout', modelValue)" @focusout="$emit('focusout', modelValue)"
@input="handleInput" @input="handleInput"
/> />
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''"> <div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">{{ title }} 확인해주세요.</div>
{{ title }} 확인해주세요.
</div>
<!-- 카테고리 중복 --> <!-- 카테고리 중복 -->
<div class="invalid-feedback" :class="isCateAlert ? 'd-block' : ''"> <div class="invalid-feedback" :class="isCateAlert ? 'd-block' : ''">카테고리 중복입니다.</div>
카테고리 중복입니다.
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
// Props // Props
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
default: '라벨', default: '라벨',
@ -63,11 +60,11 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isCateAlert : { isCateAlert: {
type :Boolean, type: Boolean,
default: false, default: false,
}, },
isLabel : { isLabel: {
type: Boolean, type: Boolean,
default: true, default: true,
required: false, required: false,
@ -80,35 +77,35 @@ const props = defineProps({
type: String, type: String,
default: '', default: '',
required: false, required: false,
} },
}); });
// Emits // Emits
const emits = defineEmits(['update:modelValue', 'focusout', 'update:alert']); const emits = defineEmits(['update:modelValue', 'focusout', 'update:alert']);
// `inputValue`
const inputValue = ref(props.modelValue);
// `inputValue` //
const inputValue = ref(props.modelValue); watch(inputValue, newValue => {
//
watch(inputValue, (newValue) => {
emits('update:modelValue', newValue); emits('update:modelValue', newValue);
}); });
// //
watch(() => props.modelValue, (newValue) => { watch(
() => props.modelValue,
newValue => {
if (inputValue.value !== newValue) { if (inputValue.value !== newValue) {
inputValue.value = newValue; inputValue.value = newValue;
} }
}); },
);
const handleInput = (event) => { const handleInput = event => {
const newValue = event.target.value.slice(0, props.maxlength); const newValue = event.target.value.slice(0, props.maxlength);
if (newValue.trim() !== '') { if (newValue.trim() !== '') {
emits('update:alert', false); emits('update:alert', false);
} }
}; };
</script> </script>

View File

@ -26,9 +26,13 @@
<input <input
type="password" type="password"
class="form-control" class="form-control"
autocomplete="off"
v-model="password" v-model="password"
placeholder="비밀번호 입력" placeholder="비밀번호 입력"
@input="password = password.replace(/\s/g, '')" @input="
password = password.replace(/\s/g, '');
inputCheck();
"
/> />
<button class="btn btn-primary" @click="submitPassword">확인</button> <button class="btn btn-primary" @click="submitPassword">확인</button>
</div> </div>
@ -131,8 +135,9 @@
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useToastStore } from '@s/toastStore';
import axios from '@api'; import axios from '@api';
import LoadingSpinner from "@v/LoadingPage.vue"; import LoadingSpinner from '@v/LoadingPage.vue';
const isLoading = ref(true); const isLoading = ref(true);
// //
const profileName = ref(''); const profileName = ref('');
@ -151,6 +156,7 @@
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const toastStore = useToastStore();
const currentBoardId = ref(Number(route.params.id)); const currentBoardId = ref(Number(route.params.id));
const unknown = computed(() => profileName.value === '익명'); const unknown = computed(() => profileName.value === '익명');
const currentUserId = computed(() => userStore.user.id); // id const currentUserId = computed(() => userStore.user.id); // id
@ -224,6 +230,9 @@
navigateLastPage: 1, navigateLastPage: 1,
}); });
const inputCheck = () => {
passwordAlert.value = '';
};
// //
const fetchBoardDetails = async () => { const fetchBoardDetails = async () => {
try { try {
@ -568,6 +577,7 @@
const submitPassword = async () => { const submitPassword = async () => {
if (!password.value.trim()) { if (!password.value.trim()) {
passwordAlert.value = '비밀번호를 입력해주세요.'; passwordAlert.value = '비밀번호를 입력해주세요.';
return; return;
} }
@ -576,7 +586,7 @@
LOCBRDPWD: password.value, LOCBRDPWD: password.value,
LOCBRDSEQ: currentBoardId.value, LOCBRDSEQ: currentBoardId.value,
}); });
console.log('response: ', response);
if (response.data.code === 200 && response.data.data === true) { if (response.data.code === 200 && response.data.data === true) {
password.value = ''; password.value = '';
isPassword.value = false; isPassword.value = false;
@ -591,18 +601,7 @@
passwordAlert.value = '비밀번호가 일치하지 않습니다.'; passwordAlert.value = '비밀번호가 일치하지 않습니다.';
} }
} catch (error) { } catch (error) {
if (error.reponse && error.reponse.status === 401) passwordAlert.value = '비밀번호가 일치하지 않습니다.'; if (error.response && error.response.status === 401) passwordAlert.value = '비밀번호가 일치하지 않습니다.';
// if (error.response) {
// if (error.response.status === 401) {
// passwordAlert.value = ' .';
// } else {
// passwordAlert.value = error.response.data?.message || ' .';
// }
// } else if (error.request) {
// passwordAlert.value = ' . .';
// } else {
// passwordAlert.value = ' .';
// }
} }
}; };
@ -664,7 +663,7 @@
}); });
if (response.data.code === 200) { if (response.data.code === 200) {
alert('게시물이 삭제되었습니다.'); toastStore.onToast('게시물이 삭제되었습니다.');
router.push({ name: 'BoardList' }); router.push({ name: 'BoardList' });
} else { } else {
alert('삭제 실패: ' + response.data.message); alert('삭제 실패: ' + response.data.message);

View File

@ -23,11 +23,7 @@
<div class="mb-4 d-flex align-items-center"> <div class="mb-4 d-flex align-items-center">
<label class="col-md-2 col-form-label">카테고리 <span class="text-danger">*</span></label> <label class="col-md-2 col-form-label">카테고리 <span class="text-danger">*</span></label>
<div class="d-flex flex-wrap align-items-center mt-3 ms-1"> <div class="d-flex flex-wrap align-items-center mt-3 ms-1">
<div <div v-for="(category, index) in categoryList" :key="index" class="form-check me-3">
v-for="(category, index) in categoryList"
:key="index"
class="form-check me-3"
>
<input <input
class="form-check-input" class="form-check-input"
type="radio" type="radio"
@ -41,9 +37,7 @@
</label> </label>
</div> </div>
</div> </div>
<div class="invalid-feedback" :class="categoryAlert ? 'd-block' : 'd-none'"> <div class="invalid-feedback" :class="categoryAlert ? 'd-block' : 'd-none'">카테고리를 선택해주세요.</div>
카테고리를 선택해주세요.
</div>
</div> </div>
<!-- 비밀번호 필드 (익명게시판 선택 활성화) --> <!-- 비밀번호 필드 (익명게시판 선택 활성화) -->
@ -52,6 +46,7 @@
title="비밀번호" title="비밀번호"
name="pw" name="pw"
type="password" type="password"
autocomplete="new-password"
:is-essential="true" :is-essential="true"
:is-alert="passwordAlert" :is-alert="passwordAlert"
v-model="password" v-model="password"
@ -74,7 +69,11 @@
<p v-if="fileError" class="text-danger">{{ fileError }}</p> <p v-if="fileError" class="text-danger">{{ fileError }}</p>
<ul class="list-group mt-2" v-if="attachFiles.length"> <ul class="list-group mt-2" v-if="attachFiles.length">
<li v-for="(file, index) in attachFiles" :key="index" class="list-group-item d-flex justify-content-between align-items-center"> <li
v-for="(file, index) in attachFiles"
:key="index"
class="list-group-item d-flex justify-content-between align-items-center"
>
{{ file.name }} {{ file.name }}
<button class="close-btn" @click="removeFile(index)"></button> <button class="close-btn" @click="removeFile(index)"></button>
</li> </li>
@ -82,15 +81,11 @@
<!-- 내용 입력 (에디터) --> <!-- 내용 입력 (에디터) -->
<div class="mb-4"> <div class="mb-4">
<label class="col-md-2 col-form-label"> <label class="col-md-2 col-form-label"> 내용 <span class="text-danger">*</span> </label>
내용 <span class="text-danger">*</span>
</label>
<div class="col-md-12"> <div class="col-md-12">
<QEditor @update:data="content = $event" /> <QEditor @update:data="content = $event" />
</div> </div>
<div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'"> <div class="invalid-feedback mt-1" :class="contentAlert ? 'd-block' : 'd-none'">내용을 입력해주세요.</div>
내용을 입력해주세요.
</div>
</div> </div>
<div class="mb-4 d-flex justify-content-end"> <div class="mb-4 d-flex justify-content-end">
@ -104,36 +99,36 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, getCurrentInstance, watch, computed } from 'vue'; import { ref, onMounted, getCurrentInstance, watch, computed } from 'vue';
import QEditor from '@c/editor/QEditor.vue'; import QEditor from '@c/editor/QEditor.vue';
import FormInput from '@c/input/FormInput.vue'; import FormInput from '@c/input/FormInput.vue';
import FormFile from '@c/input/FormFile.vue'; import FormFile from '@c/input/FormFile.vue';
import SaveButton from '@c/button/SaveBtn.vue'; import SaveButton from '@c/button/SaveBtn.vue';
import BackButton from '@c/button/BackBtn.vue'; import BackButton from '@c/button/BackBtn.vue';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import router from '@/router'; import router from '@/router';
import axios from '@api'; import axios from '@api';
const toastStore = useToastStore(); const toastStore = useToastStore();
const categoryList = ref([]); const categoryList = ref([]);
const title = ref(''); const title = ref('');
const password = ref(''); const password = ref('');
const categoryValue = ref(null); const categoryValue = ref(null);
const content = ref({ ops: [] }); const content = ref({ ops: [] });
const isFileValid = ref(true); const isFileValid = ref(true);
const titleAlert = ref(false); const titleAlert = ref(false);
const passwordAlert = ref(false); const passwordAlert = ref(false);
const contentAlert = ref(false); const contentAlert = ref(false);
const categoryAlert = ref(false); const categoryAlert = ref(false);
const attachFilesAlert = ref(false); const attachFilesAlert = ref(false);
const attachFiles = ref([]); const attachFiles = ref([]);
const maxFiles = 5; const maxFiles = 5;
const maxSize = 10 * 1024 * 1024; const maxSize = 10 * 1024 * 1024;
const fileError = ref(''); const fileError = ref('');
const fetchCategories = async () => { const fetchCategories = async () => {
try { try {
const response = await axios.get('board/categories'); const response = await axios.get('board/categories');
categoryList.value = response.data.data; categoryList.value = response.data.data;
@ -144,15 +139,15 @@ const fetchCategories = async () => {
} catch (error) { } catch (error) {
console.error('카테고리 불러오기 오류:', error); console.error('카테고리 불러오기 오류:', error);
} }
}; };
onMounted(() => { onMounted(() => {
fetchCategories(); fetchCategories();
}); });
const fileCount = computed(() => attachFiles.value.length); const fileCount = computed(() => attachFiles.value.length);
const handleFileUpload = (files) => { const handleFileUpload = files => {
const validFiles = files.filter(file => file.size <= maxSize); const validFiles = files.filter(file => file.size <= maxSize);
if (files.some(file => file.size > maxSize)) { if (files.some(file => file.size > maxSize)) {
fileError.value = '파일 크기가 10MB를 초과할 수 없습니다.'; fileError.value = '파일 크기가 10MB를 초과할 수 없습니다.';
@ -164,33 +159,33 @@ const handleFileUpload = (files) => {
} }
fileError.value = ''; fileError.value = '';
attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles); attachFiles.value = [...attachFiles.value, ...validFiles].slice(0, maxFiles);
}; };
const removeFile = (index) => { const removeFile = index => {
attachFiles.value.splice(index, 1); attachFiles.value.splice(index, 1);
if (attachFiles.value.length <= maxFiles) { if (attachFiles.value.length <= maxFiles) {
fileError.value = ''; fileError.value = '';
} }
}; };
watch(attachFiles, () => { watch(attachFiles, () => {
isFileValid.value = attachFiles.value.length <= maxFiles; isFileValid.value = attachFiles.value.length <= maxFiles;
}); });
const validateTitle = () => { const validateTitle = () => {
titleAlert.value = title.value.trim().length === 0; titleAlert.value = title.value.trim().length === 0;
}; };
const validatePassword = () => { const validatePassword = () => {
if (categoryValue.value === 300102) { if (categoryValue.value === 300102) {
password.value = password.value.replace(/\s/g, ''); // password.value = password.value.replace(/\s/g, ''); //
passwordAlert.value = password.value.length === 0; passwordAlert.value = password.value.length === 0;
} else { } else {
passwordAlert.value = false; passwordAlert.value = false;
} }
}; };
const validateContent = () => { const validateContent = () => {
if (!content.value?.ops?.length) { if (!content.value?.ops?.length) {
contentAlert.value = true; contentAlert.value = true;
return; return;
@ -203,10 +198,10 @@ const validateContent = () => {
// //
contentAlert.value = !(hasText || hasImage); contentAlert.value = !(hasText || hasImage);
}; };
/** 글쓰기 */ /** 글쓰기 */
const write = async () => { const write = async () => {
validateTitle(); validateTitle();
validatePassword(); validatePassword();
validateContent(); validateContent();
@ -221,14 +216,15 @@ const write = async () => {
LOCBRDTTL: title.value, LOCBRDTTL: title.value,
LOCBRDCON: JSON.stringify(content.value), // Delta JSON LOCBRDCON: JSON.stringify(content.value), // Delta JSON
LOCBRDPWD: categoryValue.value === 300102 ? password.value : null, LOCBRDPWD: categoryValue.value === 300102 ? password.value : null,
LOCBRDTYP: categoryValue.value LOCBRDTYP: categoryValue.value,
}; };
const { data: boardResponse } = await axios.post('board', boardData); const { data: boardResponse } = await axios.post('board', boardData);
const boardId = boardResponse.data; const boardId = boardResponse.data;
// ( ) // ( )
if (attachFiles.value && attachFiles.value.length > 0) { if (attachFiles.value && attachFiles.value.length > 0) {
await Promise.all(attachFiles.value.map(async (file) => { await Promise.all(
attachFiles.value.map(async file => {
console.log(file); console.log(file);
const formData = new FormData(); const formData = new FormData();
const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, ''); const fileNameWithoutExt = file.name.replace(/\.[^/.]+$/, '');
@ -239,10 +235,9 @@ const write = async () => {
formData.append('CMNFLESIZ', file.size); formData.append('CMNFLESIZ', file.size);
formData.append('file', file); // 📌 formData.append('file', file); // 📌
await axios.post(`board/${boardId}/attachments`, formData, await axios.post(`board/${boardId}/attachments`, formData, { isFormData: true });
{ isFormData : true } }),
); );
}));
} }
toastStore.onToast('게시물이 작성되었습니다.', 's'); toastStore.onToast('게시물이 작성되었습니다.', 's');
@ -251,15 +246,15 @@ const write = async () => {
console.error(error); console.error(error);
toastStore.onToast('게시물 작성 중 오류가 발생했습니다.', 'e'); toastStore.onToast('게시물 작성 중 오류가 발생했습니다.', 'e');
} }
}; };
/** 목록으로 이동 */ /** 목록으로 이동 */
const goList = () => { const goList = () => {
router.push('/board'); router.push('/board');
}; };
/** `content` 변경 감지하여 자동 유효성 검사 실행 */ /** `content` 변경 감지하여 자동 유효성 검사 실행 */
watch(content, () => { watch(content, () => {
validateContent(); validateContent();
}); });
</script> </script>