Merge branch 'main' into workMain

This commit is contained in:
nevermoregb 2025-03-18 21:34:02 +09:00
commit 6a8d1ff042
7 changed files with 192 additions and 181 deletions

View File

@ -617,6 +617,10 @@
.layout-page {
margin-right: 0 !important;
}
#app-calendar-sidebar {
width: 100%;
}
}
/* 세로모드 모바일 디바이스 (가로 해상도가 576px 보다 작은 화면에 적용) */

View File

@ -41,22 +41,18 @@ $api.interceptors.response.use(
const loadingStore = useLoadingStore();
loadingStore.stopLoading();
// 테스트 부탁
// 로그인 실패, 커스텀 에러 응답 처리 (status는 200 success가 false인 경우)
if (response.data && response.data.success === false) {
if (response.data.code > 10000) {
const toastStore = useToastStore();
const errorCode = response.data.code;
const errorMessage = response.data.message || '알 수 없는 오류가 발생했습니다.';
// 로그인 요청일 경우 (헤더에 isLogin이 true로 설정된 경우)
if (response.config.headers && response.config.headers.isLogin) {
return response;
}
// 서버에서 보낸 메시지 사용
toastStore.onToast(errorMessage, 'e');
// 특정 에러 코드에 대한 추가 처리만 수행
if (errorCode === 'USER_NOT_FOUND') {
if (errorCode === 10001) {
router.push('/login');
}

View File

@ -2,7 +2,7 @@
<div class="container-xxl flex-grow-1 container-p-y pb-0">
<div class="card app-calendar-wrapper">
<div class="row g-0">
<div class="col-3 border-end text-center">
<div class="col-3 border-end text-center" id="app-calendar-sidebar">
<div class="card-body">
<img v-if="user" :src="`${baseUrl}upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-50 h-px-50 rounded-circle" @error="$event.target.src = '/img/icons/icon.png'"/>
<p class="mt-2 fw-bold">

View File

@ -14,6 +14,7 @@
:value="computedValue"
:disabled="disabled"
:maxLength="maxlength"
:minLength="minlength"
:placeholder="title"
@blur="$emit('blur')"
/>
@ -29,6 +30,7 @@
:value="computedValue"
:disabled="disabled"
:maxLength="maxlength"
:minLength="minlength"
:placeholder="title"
@blur="$emit('blur')"
/>
@ -77,6 +79,11 @@
default: 30,
required: false,
},
minlength: {
type: Number,
default: 4,
required: false,
},
isAlert: {
type: Boolean,
default: false,

View File

@ -96,7 +96,7 @@ import { computed } from "vue";
import { useUserInfoStore } from '@s/useUserInfoStore';
const userStore = useUserInfoStore();
const allowedUserId = 26; // ID (!!)
const allowedUserId = 1; // ID (!!)
const userId = computed(() => userStore.user?.id ?? null);
</script>

View File

@ -111,7 +111,7 @@ const router = createRouter({
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
await authStore.checkAuthStatus(); // 로그인 상태 확인
const allowedUserId = 26; // 특정 ID (변경필요!!)
const allowedUserId = 1; // 특정 ID (변경필요!!)
const userStore = useUserInfoStore();
const userId = userStore.user?.id ?? null;
@ -120,7 +120,7 @@ router.beforeEach(async (to, from, next) => {
return next({ name: 'Login', query: { redirect: to.fullPath } });
}
// Authorization 페이지는 ID가 26이 아니면 접근 차단
// Authorization 페이지는 ID가 1이 아니면 접근 차단
if (to.path === "/authorization" && userId !== allowedUserId) {
return next("/");
}

View File

@ -6,18 +6,19 @@
<div class="mb-3 w-100">
<search-bar @update:data="search" @keyup.enter="searchOnEnter" class="flex-grow-1" />
</div>
<div class="d-flex align-items-center" style="gap: 15px;">
<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;">
<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>
<option value="100">100개씩</option>
</select>
<!-- 셀렉트 박스 -->
<select class="form-select w-auto" v-model="selectedOrder" @change="handleSortChange">
<option value="date">최신날짜</option>
<option value="date">날짜</option>
<option value="views">조회수</option>
</select>
@ -38,26 +39,33 @@
<table class="datatables-users table border-top dataTable dtr-column">
<thead>
<tr>
<th style="width: 11%;" class="text-center fw-bold">번호</th>
<th style="width: 45%;" class="text-center fw-bold">제목</th>
<th style="width: 10%;" class="text-center fw-bold">작성자</th>
<th style="width: 15%;" class="text-center fw-bold">작성일</th>
<th style="width: 9%;" class="text-center fw-bold">조회수</th>
<th style="width: 11%" class="text-center fw-bold">번호</th>
<th style="width: 45%" class="text-center fw-bold">제목</th>
<th style="width: 10%" class="text-center fw-bold">작성자</th>
<th style="width: 15%" class="text-center fw-bold">작성일</th>
<th style="width: 9%" class="text-center fw-bold">조회수</th>
</tr>
</thead>
<tbody>
<!-- 공지사항 -->
<template v-if="pagination.currentPage === 1 && !showNotices">
<tr v-for="(notice, index) in noticeList"
<tr
v-for="(notice, index) in noticeList"
:key="'notice-' + index"
class="bg-label-gray fw-bold"
@click="goDetail(notice.id)">
@click="goDetail(notice.id)"
>
<td class="text-center">공지</td>
<td class="cursor-pointer">
📌 {{ notice.title }}
<span v-if="notice.commentCount" class="text-danger fw-bold me-1">[ {{ notice.commentCount }} ]</span>
<span v-if="notice.commentCount" class="text-danger fw-bold me-1"
>[ {{ notice.commentCount }} ]</span
>
<i v-if="notice.img" class="bi bi-image me-1 align-middle"></i>
<i v-if="Array.isArray(notice.hasAttachment) && notice.hasAttachment.length > 0" class="bi bi-paperclip"></i>
<i
v-if="Array.isArray(notice.hasAttachment) && notice.hasAttachment.length > 0"
class="bi bi-paperclip"
></i>
<span v-if="isNewPost(notice.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
</td>
<td class="text-center">{{ notice.author }}</td>
@ -66,16 +74,21 @@
</tr>
</template>
<!-- 일반 게시물 -->
<tr v-for="(post, index) in generalList"
<tr
v-for="(post, index) in generalList"
:key="'post-' + index"
class="invert-bg-white"
@click="goDetail(post.realId)">
@click="goDetail(post.realId)"
>
<td class="text-center">{{ post.id }}</td>
<td class="cursor-pointer">
{{ post.title }}
<span v-if="post.commentCount" class="comment-count">[ {{ post.commentCount }} ]</span>
<i v-if="post.img" class="bi bi-image me-1"></i>
<i v-if="Array.isArray(post.hasAttachment) && post.hasAttachment.length > 0" class="bi bi-paperclip"></i>
<i
v-if="Array.isArray(post.hasAttachment) && post.hasAttachment.length > 0"
class="bi bi-paperclip"
></i>
<span v-if="isNewPost(post.rawDate)" class="badge bg-danger text-white ms-2 fs-tiny">N</span>
</td>
<td class="text-center">{{ post.author }}</td>
@ -86,9 +99,7 @@
</table>
<!-- 게시물이 없을 -->
<div v-if="generalList.length === 0">
<p class="text-center pt-10 mt-2 mb-0 text-muted">
게시물이 없습니다.
</p>
<p class="text-center pt-10 mt-2 mb-0 text-muted">게시물이 없습니다.</p>
</div>
</div>
</div>
@ -96,11 +107,7 @@
<!-- 페이지네이션 -->
<div class="row g-3">
<div class="mt-8">
<Pagination
v-if="pagination.pages"
v-bind="pagination"
@update:currentPage="handlePageChange"
/>
<Pagination v-if="pagination.pages" v-bind="pagination" @update:currentPage="handlePageChange" />
</div>
</div>
</div>
@ -120,7 +127,6 @@ import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday';
import 'bootstrap-icons/font/bootstrap-icons.css';
dayjs.extend(isToday);
dayjs.extend(isYesterday);
@ -144,28 +150,28 @@ const pagination = ref({
navigatePages: 10,
navigatepageNums: [1],
navigateFirstPage: 1,
navigateLastPage: 1
navigateLastPage: 1,
});
//
const goDetail = (id) => {
const goDetail = id => {
router.push({ name: 'BoardDetail', params: { id: id } });
};
// ( HH:mm, YYYY-MM-DD)
const formatDate = (dateString) => {
const formatDate = dateString => {
const date = dayjs(dateString);
return date.isToday() ? date.format('HH:mm') : date.format('YYYY-MM-DD');
};
// ( )
const isNewPost = (dateString) => {
const isNewPost = dateString => {
const date = dayjs(dateString);
return date.isToday() || date.isYesterday();
};
//
const search = (e) => {
const search = e => {
searchText.value = e.trim();
fetchGeneralPosts(1);
};
@ -183,20 +189,20 @@ const handleSizeChange = () => {
//
const fetchGeneralPosts = async (page = 1) => {
try {
const { data } = await axios.get("board/general", {
const { data } = await axios.get('board/general', {
params: {
page,
size: selectedSize.value,
orderBy: selectedOrder.value,
searchKeyword: searchText.value
}
searchKeyword: searchText.value,
},
});
if (data?.data) {
const totalPosts = data.data.total;
generalList.value = data.data.list.map((post, index) => ({
realId: post.id,
id: totalPosts - ((page - 1) * selectedSize.value) - index,
id: totalPosts - (page - 1) * selectedSize.value - index,
title: post.title,
author: post.author || '익명',
rawDate: post.date,
@ -204,7 +210,7 @@ const fetchGeneralPosts = async (page = 1) => {
views: post.cnt || 0,
hasAttachment: post.hasAttachment,
img: post.firstImageUrl || null,
commentCount : post.commentCount
commentCount: post.commentCount,
}));
pagination.value = {
@ -220,18 +226,17 @@ const fetchGeneralPosts = async (page = 1) => {
navigatePages: data.data.navigatePages,
navigatepageNums: data.data.navigatepageNums,
navigateFirstPage: data.data.navigateFirstPage,
navigateLastPage: data.data.navigateLastPage
navigateLastPage: data.data.navigateLastPage,
};
}
} catch (error) {
}
} catch (error) {}
};
//
const fetchNoticePosts = async () => {
try {
const { data } = await axios.get("board/notices", {
params: { searchKeyword: searchText.value }
const { data } = await axios.get('board/notices', {
params: { searchKeyword: searchText.value },
});
if (data?.data) {
@ -244,15 +249,14 @@ const fetchNoticePosts = async () => {
views: post.cnt || 0,
hasAttachment: post.hasAttachment,
img: post.firstImageUrl || null,
commentCount : post.commentCount
commentCount: post.commentCount,
}));
}
} catch (error) {
}
} catch (error) {}
};
// Enter
const searchOnEnter = (event) => {
const searchOnEnter = event => {
const searchTextValue = event.target.value.trim();
if (!searchTextValue || searchTextValue[0] === ' ') {
@ -264,7 +268,7 @@ const searchOnEnter = (event) => {
};
//
const handlePageChange = (page) => {
const handlePageChange = page => {
if (page !== pagination.value.currentPage) {
fetchGeneralPosts(page);
}