Merge branch 'main' into commuters

This commit is contained in:
yoon 2025-03-13 11:15:08 +09:00
commit 495c714b80
6 changed files with 94 additions and 74 deletions

View File

@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import { useRoute } from 'vue-router'; import router from '@/router';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useLoadingStore } from "@s/loadingStore"; import { useLoadingStore } from '@s/loadingStore';
const $api = axios.create({ const $api = axios.create({
baseURL: import.meta.env.VITE_API_URL, baseURL: import.meta.env.VITE_API_URL,
@ -46,8 +46,8 @@ $api.interceptors.response.use(
function (error) { function (error) {
const loadingStore = useLoadingStore(); const loadingStore = useLoadingStore();
loadingStore.stopLoading(); loadingStore.stopLoading();
const toastStore = useToastStore(); const toastStore = useToastStore();
// 오류 응답 처리 // 오류 응답 처리
if (error.response) { if (error.response) {
switch (error.response.status) { switch (error.response.status) {
@ -55,6 +55,7 @@ $api.interceptors.response.use(
if (!error.config.headers.isLogin) { if (!error.config.headers.isLogin) {
// toastStore.onToast('인증이 필요합니다.', 'e'); // toastStore.onToast('인증이 필요합니다.', 'e');
} }
router.push('/login');
break; break;
case 403: case 403:
toastStore.onToast('접근 권한이 없습니다.', 'e'); toastStore.onToast('접근 권한이 없습니다.', 'e');

View File

@ -1,18 +1,20 @@
/* /*
작성자 : 공현지 작성자 : 공현지
작성일 : 2025-01-17 작성일 : 2025-01-17
수정자 : 수정자 : 박성용
수정일 : 수정일 : 2025-03-11
설명 : 공통 스크립트 설명 : 공통 스크립트
*/ */
import Quill from 'quill'; import Quill from 'quill';
/* /*
*템플릿 사용법 : $common.변수 *템플릿 사용법 : $common.변수
*setup() 사용법 : *setup() 사용법 :
const { appContext } = getCurrentInstance(); const { appContext } = getCurrentInstance();
const $common = appContext.config.globalProperties.$common; const $common = appContext.config.globalProperties.$common;
$common.변수 or
import { inject } from 'vue';
const $common = inject('common');
*/ */
const common = { const common = {
// JSON 문자열로 Delta 타입을 변환 // JSON 문자열로 Delta 타입을 변환
@ -74,6 +76,12 @@ const common = {
}; };
}, },
/**
* 빈값 확인
*
* @param {} obj
* @returns
*/
isNotEmpty(obj) { isNotEmpty(obj) {
if (obj === null || obj === undefined) return false; if (obj === null || obj === undefined) return false;
if (typeof obj === 'string' && obj.trim() === '') return false; if (typeof obj === 'string' && obj.trim() === '') return false;
@ -103,12 +111,25 @@ const common = {
/** /**
* 확인 * 확인
* *
* @param {ref} text ex) inNotValidInput(data.value); * @param { ref } text ex) inNotValidInput(data.value);
* @returns * @returns
*/ */
isNotValidInput(text) { isNotValidInput(text) {
return text.trim().length === 0; return text.trim().length === 0;
}, },
/**
* 프로필 이미지 반환
*
* @param { String } profileImg
* @returns
*/
getProfileImage(profileImg) {
let profileImgUrl = '/img/icons/icon.png'; // 기본 프로필 이미지 경로
const UserProfile = `${import.meta.env.VITE_SERVER}upload/img/profile/${profileImg}`;
return !profileImg || profileImg === '' ? profileImgUrl : UserProfile;
},
}; };
export default { export default {

View File

@ -31,17 +31,11 @@
</template> </template>
<script setup> <script setup>
import { computed, defineProps, defineEmits } from 'vue'; import { computed, defineProps, defineEmits, inject } from 'vue';
import DeleteButton from '../button/DeleteBtn.vue'; import DeleteButton from '../button/DeleteBtn.vue';
import EditButton from '../button/EditBtn.vue'; import EditButton from '../button/EditBtn.vue';
import BoardRecommendBtn from '../button/BoardRecommendBtn.vue'; import BoardRecommendBtn from '../button/BoardRecommendBtn.vue';
//
const defaultProfile = '/img/icons/icon.png';
// (Vue )
const baseUrl = import.meta.env.VITE_SERVER; // API URL
// Props // Props
const props = defineProps({ const props = defineProps({
comment: { comment: {
@ -97,6 +91,7 @@
}); });
const emit = defineEmits(['updateReaction', 'editClick', 'deleteClick']); const emit = defineEmits(['updateReaction', 'editClick', 'deleteClick']);
const $common = inject('common');
const isDeletedComment = computed(() => { const isDeletedComment = computed(() => {
return props.comment?.content === '삭제된 댓글입니다' && props.comment?.updateAtRaw !== props.comment?.createdAtRaw; return props.comment?.content === '삭제된 댓글입니다' && props.comment?.updateAtRaw !== props.comment?.createdAtRaw;
@ -123,6 +118,6 @@
// //
const getProfileImage = profileImg => { const getProfileImage = profileImg => {
return profileImg && profileImg != '' ? `${baseUrl}upload/img/profile/${profileImg}` : defaultProfile; return $common.getProfileImage(profileImg);
}; };
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class=""> <div class="">
<ul class="row gx-2 mb-2 list-inline"> <ul class="row gx-2 mb-0 list-inline">
<li <li
v-for="(user, index) in sortedUserList" v-for="(user, index) in sortedUserList"
:key="index" :key="index"
@ -10,7 +10,7 @@
data-bs-placement="top" data-bs-placement="top"
:aria-label="user.MEMBERSEQ" :aria-label="user.MEMBERSEQ"
> >
<div class="ratio ratio-1x1 mb-2 profile-list"> <div class="ratio ratio-1x1 mb-0 profile-list">
<img <img
class="rounded-circle profile-img" class="rounded-circle profile-img"
:src="getUserProfileImage(user.MEMBERPRF)" :src="getUserProfileImage(user.MEMBERPRF)"

View File

@ -1,43 +1,39 @@
<template> <template>
<div class="container flex-grow-1 container-p-y"> <div class="container flex-grow-1 container-p-y">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header d-flex flex-column">
<!-- 검색창 --> <!-- 검색창 -->
<div class="container col-6 mt-12 mb-8"> <div class="mb-3 w-100">
<search-bar @update:data="search" @keyup.enter="searchOnEnter"/> <search-bar @update:data="search" @keyup.enter="searchOnEnter" class="flex-grow-1" />
</div>
<div class="d-flex align-items-center" style="gap: 15px;">
<!-- 리스트 갯수 선택 -->
<select class="form-select w-auto" v-model="selectedSize" @change="handleSizeChange" style="margin-left: 0;">
<option value="10">10개씩</option>
<option value="20">20개씩</option>
<option value="30">30개씩</option>
<option value="50">50개씩</option>
</select>
<!-- 셀렉트 박스 -->
<select class="form-select w-auto" v-model="selectedOrder" @change="handleSortChange">
<option value="date">최신날짜</option>
<option value="views">조회수</option>
</select>
<!-- 공지 접기 기능 -->
<div class="form-check mb-0">
<input class="form-check-input" type="checkbox" v-model="showNotices" id="hideNotices" />
<label class="form-check-label" for="hideNotices">공지 숨기기</label>
</div>
<!-- 글쓰기 버튼 -->
<router-link to="/board/write" class="ms-auto">
<WriteButton />
</router-link>
</div> </div>
</div> </div>
<div class="card-datatable"> <div class="card-datatable m">
<div class="row mx-6 my-6 justify-content-between g-3 align-items-center">
<div class="col-md-6 d-flex flex-column flex-md-row align-items-md-center gap-2 mt-0">
<!-- 리스트 갯수 선택 -->
<select class="form-select w-25 w-md-100" v-model="selectedSize" @change="handleSizeChange">
<option value="10">10개씩</option>
<option value="20">20개씩</option>
<option value="30">30개씩</option>
<option value="50">50개씩</option>
</select>
<!-- 셀렉트 박스 -->
<select class="form-select w-25 w-md-100" v-model="selectedOrder" @change="handleSortChange">
<option value="date">최신날짜</option>
<option value="views">조회수</option>
</select>
<!-- 공지 접기 기능 -->
<div class="form-check mb-0 ms-2">
<input class="form-check-input" type="checkbox" v-model="showNotices" id="hideNotices" />
<label class="form-check-label" for="hideNotices">공지 숨기기</label>
</div>
</div>
<div class="col-md-6 d-flex flex-column flex-md-row align-items-md-center justify-content-md-end gap-2">
<!-- 글쓰기 -->
<router-link to="/board/write" class="ms-2">
<WriteButton class="btn add-new btn-primary"/>
</router-link>
</div>
</div>
<!-- 게시판 -->
<div class="table-responsive"> <div class="table-responsive">
<table class="datatables-users table border-top dataTable dtr-column"> <table class="datatables-users table border-top dataTable dtr-column">
<thead> <thead>
@ -289,12 +285,11 @@ onMounted(() => {
width: 100% !important; width: 100% !important;
} }
} }
/* 댓글 개수 스타일 */ .comment-count {
.comment-count { font-size: 0.9rem;
font-size: 0.9rem; /* 글씨 크기 증가 */ font-weight: bold;
font-weight: bold; color: #ff5733;
color: #ff5733; /* 강조 색상 (붉은 계열) */ border-radius: 4px;
border-radius: 4px; /* 둥근 모서리 */ padding: 2px 6px;
padding: 2px 6px; /* 내부 패딩 */ }
}
</style> </style>

View File

@ -514,7 +514,7 @@
toggleCommentPassword(comment, 'edit'); toggleCommentPassword(comment, 'edit');
} }
} else { } else {
alert('수정이 불가능합니다'); toastStore.onToast('수정에 실패하였습니다.', 'e');
} }
}; };
@ -644,7 +644,7 @@
passwordCommentAlert.value = ''; passwordCommentAlert.value = '';
currentPasswordCommentId.value = null; currentPasswordCommentId.value = null;
} else { } else {
alert('수정 취소를 실패했습니다.'); toastStore.onToast('수정 취소를 실패하였습니다.', 'e');
} }
// //
} else if (lastCommentClickedButton.value === 'delete') { } else if (lastCommentClickedButton.value === 'delete') {
@ -676,13 +676,15 @@
toastStore.onToast('게시물이 삭제되었습니다.'); toastStore.onToast('게시물이 삭제되었습니다.');
router.push({ name: 'BoardList' }); router.push({ name: 'BoardList' });
} else { } else {
alert('삭제 실패: ' + response.data.message); //alert(' : ' + response.data.message);
toastStore.onToast('삭제 실패: ' + response.data.message, 'e');
} }
} catch (error) { } catch (error) {
if (error.response) { if (error.response) {
alert(`삭제 실패: ${error.response.data.message || '서버 오류'}`); const errMsg = `삭제 실패: ${error.response.data.message || '서버 오류'}`;
toastStore.onToast(errMsg, 'e');
} else { } else {
alert('네트워크 오류가 발생했습니다. 다시 시도해주세요.'); toastStore.onToast('네트워크 오류가 발생했습니다. 다시 시도해주세요.', 'e');
} }
} }
} }
@ -710,10 +712,10 @@
targetComment.isDeleted = true; // targetComment.isDeleted = true; //
} }
} else { } else {
alert('댓글 삭제에 실패했습니다.'); toastStore.onToast('댓글 삭제에 실패했습니다.', 'e');
} }
} catch (error) { } catch (error) {
alert('댓글 삭제 중 오류가 발생했습니다.'); toastStore.onToast('댓글 삭제 중 오류가 발생했습니다.', 'e');
} }
}; };
@ -732,13 +734,13 @@
targetComment.content = editedContent; // targetComment.content = editedContent; //
targetComment.isEditTextarea = false; // targetComment.isEditTextarea = false; //
} else { } else {
alert('수정할 댓글을 찾을 수 없습니다.'); toastStore.onToast('수정할 댓글을 찾을 수 없습니다.', 'e');
} }
} else { } else {
alert('댓글 수정 실패했습니다.'); toastStore.onToast('댓글 수정 실패했습니다.', 'e');
} }
} catch (error) { } catch (error) {
alert('댓글 수정 중 오류 발생했습니다.'); toastStore.onToast('댓글 수정 중 오류가 발생하였습니다.', 'e');
} }
}; };
@ -749,7 +751,7 @@
if (targetComment) { if (targetComment) {
targetComment.isEditTextarea = false; targetComment.isEditTextarea = false;
} else { } else {
alert('수정 취소를 실패했습니다.'); toastStore.onToast('수정 취소를 실패했습니다.', 'e');
} }
}; };
@ -796,3 +798,9 @@
fetchComments(); fetchComments();
}); });
</script> </script>
<style>
.board-content img {
max-width: 100%;
height: auto;
}
</style>