Merge branch 'main' into workMain
This commit is contained in:
commit
6a8d1ff042
@ -617,6 +617,10 @@
|
||||
.layout-page {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
#app-calendar-sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 세로모드 모바일 디바이스 (가로 해상도가 576px 보다 작은 화면에 적용) */
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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("/");
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -109,172 +116,169 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Pagination from '@c/pagination/Pagination.vue';
|
||||
import SearchBar from '@c/search/SearchBar.vue';
|
||||
import router from '@/router';
|
||||
import WriteButton from '@c/button/WriteBtn.vue';
|
||||
import axios from '@api';
|
||||
import dayjs from 'dayjs';
|
||||
import isToday from 'dayjs/plugin/isToday';
|
||||
import isYesterday from 'dayjs/plugin/isYesterday';
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Pagination from '@c/pagination/Pagination.vue';
|
||||
import SearchBar from '@c/search/SearchBar.vue';
|
||||
import router from '@/router';
|
||||
import WriteButton from '@c/button/WriteBtn.vue';
|
||||
import axios from '@api';
|
||||
import dayjs from 'dayjs';
|
||||
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);
|
||||
|
||||
dayjs.extend(isToday);
|
||||
dayjs.extend(isYesterday);
|
||||
// 데이터 초기화
|
||||
const generalList = ref([]);
|
||||
const noticeList = ref([]);
|
||||
const searchText = ref('');
|
||||
const selectedOrder = ref('date');
|
||||
const selectedSize = ref(10);
|
||||
const showNotices = ref(false);
|
||||
|
||||
// 데이터 초기화
|
||||
const generalList = ref([]);
|
||||
const noticeList = ref([]);
|
||||
const searchText = ref('');
|
||||
const selectedOrder = ref('date');
|
||||
const selectedSize = ref(10);
|
||||
const showNotices = ref(false);
|
||||
const pagination = ref({
|
||||
currentPage: 1,
|
||||
pages: 1,
|
||||
prePage: 0,
|
||||
nextPage: 1,
|
||||
isFirstPage: true,
|
||||
isLastPage: false,
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
navigatePages: 10,
|
||||
navigatepageNums: [1],
|
||||
navigateFirstPage: 1,
|
||||
navigateLastPage: 1,
|
||||
});
|
||||
|
||||
const pagination = ref({
|
||||
currentPage: 1,
|
||||
pages: 1,
|
||||
prePage: 0,
|
||||
nextPage: 1,
|
||||
isFirstPage: true,
|
||||
isLastPage: false,
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
navigatePages: 10,
|
||||
navigatepageNums: [1],
|
||||
navigateFirstPage: 1,
|
||||
navigateLastPage: 1
|
||||
});
|
||||
// 상세 페이지 이동
|
||||
const goDetail = id => {
|
||||
router.push({ name: 'BoardDetail', params: { id: id } });
|
||||
};
|
||||
|
||||
// 상세 페이지 이동
|
||||
const goDetail = (id) => {
|
||||
router.push({ name: 'BoardDetail', params: { id: id } });
|
||||
};
|
||||
// 날짜 포맷 변환 함수 (오늘이면 HH:mm, 아니면 YYYY-MM-DD)
|
||||
const formatDate = dateString => {
|
||||
const date = dayjs(dateString);
|
||||
return date.isToday() ? date.format('HH:mm') : date.format('YYYY-MM-DD');
|
||||
};
|
||||
|
||||
// 날짜 포맷 변환 함수 (오늘이면 HH:mm, 아니면 YYYY-MM-DD)
|
||||
const formatDate = (dateString) => {
|
||||
const date = dayjs(dateString);
|
||||
return date.isToday() ? date.format('HH:mm') : date.format('YYYY-MM-DD');
|
||||
};
|
||||
// 새로 올라온 게시물 여부 판단 (오늘 또는 어제 작성된 경우)
|
||||
const isNewPost = dateString => {
|
||||
const date = dayjs(dateString);
|
||||
return date.isToday() || date.isYesterday();
|
||||
};
|
||||
|
||||
// 새로 올라온 게시물 여부 판단 (오늘 또는 어제 작성된 경우)
|
||||
const isNewPost = (dateString) => {
|
||||
const date = dayjs(dateString);
|
||||
return date.isToday() || date.isYesterday();
|
||||
};
|
||||
// 검색 처리
|
||||
const search = e => {
|
||||
searchText.value = e.trim();
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
|
||||
// 검색 처리
|
||||
const search = (e) => {
|
||||
searchText.value = e.trim();
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
// 정렬 변경 핸들러
|
||||
const handleSortChange = () => {
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
|
||||
// 정렬 변경 핸들러
|
||||
const handleSortChange = () => {
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
// 리스트 개수 변경 핸들러
|
||||
const handleSizeChange = () => {
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
|
||||
// 리스트 개수 변경 핸들러
|
||||
const handleSizeChange = () => {
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
// 일반 게시물 데이터 로드
|
||||
const fetchGeneralPosts = async (page = 1) => {
|
||||
try {
|
||||
const { data } = await axios.get('board/general', {
|
||||
params: {
|
||||
page,
|
||||
size: selectedSize.value,
|
||||
orderBy: selectedOrder.value,
|
||||
searchKeyword: searchText.value,
|
||||
},
|
||||
});
|
||||
|
||||
// 일반 게시물 데이터 로드
|
||||
const fetchGeneralPosts = async (page = 1) => {
|
||||
try {
|
||||
const { data } = await axios.get("board/general", {
|
||||
params: {
|
||||
page,
|
||||
size: selectedSize.value,
|
||||
orderBy: selectedOrder.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,
|
||||
title: post.title,
|
||||
author: post.author || '익명',
|
||||
rawDate: post.date,
|
||||
date: formatDate(post.date), // 날짜 변환 적용
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount: post.commentCount,
|
||||
}));
|
||||
|
||||
pagination.value = {
|
||||
...pagination.value,
|
||||
currentPage: data.data.pageNum,
|
||||
pages: data.data.pages,
|
||||
prePage: data.data.prePage,
|
||||
nextPage: data.data.nextPage,
|
||||
isFirstPage: data.data.isFirstPage,
|
||||
isLastPage: data.data.isLastPage,
|
||||
hasPreviousPage: data.data.hasPreviousPage,
|
||||
hasNextPage: data.data.hasNextPage,
|
||||
navigatePages: data.data.navigatePages,
|
||||
navigatepageNums: data.data.navigatepageNums,
|
||||
navigateFirstPage: data.data.navigateFirstPage,
|
||||
navigateLastPage: data.data.navigateLastPage,
|
||||
};
|
||||
}
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
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,
|
||||
title: post.title,
|
||||
author: post.author || '익명',
|
||||
rawDate: post.date,
|
||||
date: formatDate(post.date), // 날짜 변환 적용
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount : post.commentCount
|
||||
}));
|
||||
// 공지사항 데이터 로드
|
||||
const fetchNoticePosts = async () => {
|
||||
try {
|
||||
const { data } = await axios.get('board/notices', {
|
||||
params: { searchKeyword: searchText.value },
|
||||
});
|
||||
|
||||
pagination.value = {
|
||||
...pagination.value,
|
||||
currentPage: data.data.pageNum,
|
||||
pages: data.data.pages,
|
||||
prePage: data.data.prePage,
|
||||
nextPage: data.data.nextPage,
|
||||
isFirstPage: data.data.isFirstPage,
|
||||
isLastPage: data.data.isLastPage,
|
||||
hasPreviousPage: data.data.hasPreviousPage,
|
||||
hasNextPage: data.data.hasNextPage,
|
||||
navigatePages: data.data.navigatePages,
|
||||
navigatepageNums: data.data.navigatepageNums,
|
||||
navigateFirstPage: data.data.navigateFirstPage,
|
||||
navigateLastPage: data.data.navigateLastPage
|
||||
};
|
||||
if (data?.data) {
|
||||
noticeList.value = data.data.map(post => ({
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
author: post.author || '관리자',
|
||||
date: formatDate(post.date),
|
||||
rawDate: post.date,
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount: post.commentCount,
|
||||
}));
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
// Enter 키를 눌렀을 때
|
||||
const searchOnEnter = event => {
|
||||
const searchTextValue = event.target.value.trim();
|
||||
|
||||
if (!searchTextValue || searchTextValue[0] === ' ') {
|
||||
return; // 검색어가 비어있거나 첫 글자가 공백이면 실행 안 함
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
};
|
||||
|
||||
// 공지사항 데이터 로드
|
||||
const fetchNoticePosts = async () => {
|
||||
try {
|
||||
const { data } = await axios.get("board/notices", {
|
||||
params: { searchKeyword: searchText.value }
|
||||
});
|
||||
searchText.value = searchTextValue;
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
|
||||
if (data?.data) {
|
||||
noticeList.value = data.data.map(post => ({
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
author: post.author || '관리자',
|
||||
date: formatDate(post.date),
|
||||
rawDate: post.date,
|
||||
views: post.cnt || 0,
|
||||
hasAttachment: post.hasAttachment,
|
||||
img: post.firstImageUrl || null,
|
||||
commentCount : post.commentCount
|
||||
}));
|
||||
// 페이지 변경
|
||||
const handlePageChange = page => {
|
||||
if (page !== pagination.value.currentPage) {
|
||||
fetchGeneralPosts(page);
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Enter 키를 눌렀을 때
|
||||
const searchOnEnter = (event) => {
|
||||
const searchTextValue = event.target.value.trim();
|
||||
|
||||
if (!searchTextValue || searchTextValue[0] === ' ') {
|
||||
return; // 검색어가 비어있거나 첫 글자가 공백이면 실행 안 함
|
||||
}
|
||||
|
||||
searchText.value = searchTextValue;
|
||||
fetchGeneralPosts(1);
|
||||
};
|
||||
|
||||
// 페이지 변경
|
||||
const handlePageChange = (page) => {
|
||||
if (page !== pagination.value.currentPage) {
|
||||
fetchGeneralPosts(page);
|
||||
}
|
||||
};
|
||||
|
||||
// 데이터 로드
|
||||
onMounted(() => {
|
||||
fetchNoticePosts();
|
||||
fetchGeneralPosts();
|
||||
});
|
||||
// 데이터 로드
|
||||
onMounted(() => {
|
||||
fetchNoticePosts();
|
||||
fetchGeneralPosts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user