Merge branch 'main' into login
This commit is contained in:
commit
f3ca41f9ad
17
package-lock.json
generated
17
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"@vueup/vue-quill": "^1.2.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
|
"bootstrap-icons": "^1.11.3",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dompurify": "^3.2.3",
|
"dompurify": "^3.2.3",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
@ -1890,6 +1891,22 @@
|
|||||||
"@popperjs/core": "^2.11.8"
|
"@popperjs/core": "^2.11.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bootstrap-icons": {
|
||||||
|
"version": "1.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
|
||||||
|
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/twbs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
"@vueup/vue-quill": "^1.2.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
|
"bootstrap-icons": "^1.11.3",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dompurify": "^3.2.3",
|
"dompurify": "^3.2.3",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
|
|||||||
@ -7,16 +7,13 @@
|
|||||||
<div class="me-2">
|
<div class="me-2">
|
||||||
<h6 class="mb-0">{{ profileName }}</h6>
|
<h6 class="mb-0">{{ profileName }}</h6>
|
||||||
<div class="profile-detail">
|
<div class="profile-detail">
|
||||||
<span>2024.12.10 10:46</span>
|
<span>{{ date }}</span>
|
||||||
<template v-if="showDetail">
|
<template v-if="showDetail">
|
||||||
<span>
|
<span class="ms-2">
|
||||||
<i class="fa-regular fa-eye"></i> {{ views }}
|
<i class="fa-regular fa-eye"></i> {{ views }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<i class="fa-regular fa-thumbs-up"></i> {{ likes }}
|
<i class="bx bx-comment"></i> {{ comments }}
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<i class="fa-regular fa-thumbs-down"></i> {{ dislikes }}
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -45,6 +42,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed, defineProps } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
import DeleteButton from '../button/DeleteBtn.vue';
|
import DeleteButton from '../button/DeleteBtn.vue';
|
||||||
@ -72,17 +70,17 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
date: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
views: {
|
views: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
likes: {
|
comments: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null,
|
default: 0,
|
||||||
},
|
|
||||||
dislikes: {
|
|
||||||
type: Number,
|
|
||||||
default: null,
|
|
||||||
},
|
},
|
||||||
isChild: {
|
isChild: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@ -62,27 +62,28 @@ watch(() => props.dislikeCount, (newVal) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleLike = () => {
|
const handleLike = () => {
|
||||||
likeClicked.value = !likeClicked.value;
|
// console.log('adadasd')
|
||||||
likeCount.value += likeClicked.value ? 1 : -1;
|
// likeClicked.value = !likeClicked.value;
|
||||||
|
// likeCount.value += likeClicked.value ? 1 : -1;
|
||||||
emit('updateReaction', { type: 'like', boardId: props.boardId, commentId: props.commentId });
|
emit('updateReaction', { type: 'like', boardId: props.boardId, commentId: props.commentId });
|
||||||
if(likeClicked.value === true){
|
// if(likeClicked.value === true){
|
||||||
if(dislikeClicked.value === true) {
|
// if(dislikeClicked.value === true) {
|
||||||
dislikeClicked.value = false;
|
// dislikeClicked.value = false;
|
||||||
dislikeCount.value += -1
|
// dislikeCount.value += -1
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDislike = () => {
|
const handleDislike = () => {
|
||||||
dislikeClicked.value = !dislikeClicked.value;
|
// dislikeClicked.value = !dislikeClicked.value;
|
||||||
dislikeCount.value += dislikeClicked.value ? 1 : -1;
|
// dislikeCount.value += dislikeClicked.value ? 1 : -1;
|
||||||
emit('updateReaction', { type: 'dislike', boardId: props.boardId, commentId: props.commentId });
|
emit('updateReaction', { type: 'dislike', boardId: props.boardId, commentId: props.commentId });
|
||||||
if(dislikeClicked.value === true){
|
// if(dislikeClicked.value === true){
|
||||||
if(likeClicked.value === true) {
|
// if(likeClicked.value === true) {
|
||||||
likeClicked.value = false;
|
// likeClicked.value = false;
|
||||||
likeCount.value += -1
|
// likeCount.value += -1
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="btn"
|
class="btn"
|
||||||
:class="{
|
:class="{
|
||||||
'btn-outline-primary': category !== selectedCategory,
|
'btn-outline-primary': category.CMNCODVAL !== selectedCategory,
|
||||||
'btn-primary': category === selectedCategory
|
'btn-primary': category.CMNCODVAL === selectedCategory
|
||||||
}"
|
}"
|
||||||
@click="selectCategory(category)"
|
@click="selectCategory(category.CMNCODVAL)"
|
||||||
>{{ category.CMNCODNAM }}</button>
|
>{{ category.CMNCODNAM }}</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -31,9 +31,10 @@ const props = defineProps({
|
|||||||
|
|
||||||
// 카테고리 선택
|
// 카테고리 선택
|
||||||
const selectedCategory = ref(null);
|
const selectedCategory = ref(null);
|
||||||
|
const emit = defineEmits();
|
||||||
const selectCategory = (category) => {
|
const selectCategory = (cate) => {
|
||||||
selectedCategory.value = category;
|
selectedCategory.value = selectedCategory.value === cate ? null : cate;
|
||||||
|
emit('update:data', selectedCategory.value);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -42,8 +43,4 @@ const selectCategory = (category) => {
|
|||||||
.cate-list {
|
.cate-list {
|
||||||
margin-left: -0.25rem;
|
margin-left: -0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width:450px) {
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@ -1,45 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card mb-3 shadow-sm fixed-card" style="width: 18rem; height: 17rem">
|
<div class="card mb-3 shadow-sm">
|
||||||
<!-- 이미지가 있을 경우 card-img-top으로 표시 -->
|
<div class="row g-0">
|
||||||
|
<!-- 이미지 섹션 -->
|
||||||
|
<div v-if="img" class="col-sm-2">
|
||||||
<img
|
<img
|
||||||
v-if="img"
|
|
||||||
:src="img"
|
:src="img"
|
||||||
class="card-img-top"
|
|
||||||
alt="이미지"
|
alt="이미지"
|
||||||
style="object-fit: cover; height: 100px;"
|
class="img-fluid rounded-start"
|
||||||
|
style="object-fit: cover; height: 100%; width: 100%;"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 게시물 내용 섹션 -->
|
||||||
|
<div :class="contentColClass">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- 카테고리 태그 (존재할 경우) -->
|
<!-- 태그 -->
|
||||||
<h6 v-if="category" class="badge rounded-pill bg-primary text-white mb-2">
|
<h6 class="badge rounded-pill bg-primary text-white mb-2">
|
||||||
{{ category }}
|
{{ category }}
|
||||||
</h6>
|
</h6>
|
||||||
<!-- 제목과 첨부파일 아이콘 -->
|
<!-- 제목 -->
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
<span class="text-muted" v-if="attachment">
|
<span class="text-muted me-3" v-if="attachment">
|
||||||
<i class="fa-solid fa-paperclip"></i>
|
<i class="fa-solid fa-paperclip"></i>
|
||||||
</span>
|
</span>
|
||||||
</h5>
|
</h5>
|
||||||
<!-- 본문 -->
|
<!-- 본문 -->
|
||||||
<p class="card-text limit-two-lines">{{ content }}</p>
|
<div class="card-text str_wrap my-5">{{ content }}</div>
|
||||||
<!-- 날짜 및 조회수, 좋아요, 댓글 -->
|
<!-- 날짜 -->
|
||||||
<p class="card-text">
|
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-start">
|
||||||
<small class="text-muted">{{ formattedDate }}</small>
|
<small class="text-muted">{{ formattedDate }}</small>
|
||||||
|
<!-- 조회수, 좋아요, 댓글 -->
|
||||||
<div class="d-flex mt-2 mt-sm-0">
|
<div class="d-flex mt-2 mt-sm-0">
|
||||||
<span class="text-muted me-3">
|
<span class="text-muted me-3">
|
||||||
<i class="fa-regular fa-eye"></i> {{ views || 0 }}
|
<i class="fa-regular fa-eye"></i> {{ views || 0 }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-muted me-3" v-if="comments !== null">
|
|
||||||
<i class="bx bx-comment"></i> {{ comments }}
|
|
||||||
</span>
|
|
||||||
<span class="text-muted me-3" v-if="likes != null">
|
<span class="text-muted me-3" v-if="likes != null">
|
||||||
<i class="bx bx-like"></i> {{ likes }}
|
<i class="bx bx-like"></i> {{ likes }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-muted me-3" v-if="dislikes != null">
|
<span class="text-muted" v-if="comments !== null">
|
||||||
<i class="bx bx-dislike"></i> {{ dislikes }}
|
<i class="bx bx-comment"></i> {{ comments }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -48,6 +52,7 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { defineProps } from 'vue';
|
import { defineProps } from 'vue';
|
||||||
|
|
||||||
|
// Props 정의
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
img: {
|
img: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -77,10 +82,6 @@
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
dislikes: {
|
|
||||||
type: Number,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
comments: {
|
comments: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null,
|
default: null,
|
||||||
@ -91,13 +92,24 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// computed 속성
|
||||||
|
const contentColClass = computed(() => {
|
||||||
|
return props.img ? 'col-sm-10 col-12' : 'col-sm-12';
|
||||||
|
});
|
||||||
|
|
||||||
|
// formattedDate을 computed로 정의
|
||||||
const formattedDate = computed(() => {
|
const formattedDate = computed(() => {
|
||||||
const dateObj = new Date(props.date);
|
const date = new Date(props.date);
|
||||||
return `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')} ${String(dateObj.getHours()).padStart(2, '0')}:${String(dateObj.getMinutes()).padStart(2, '0')}`;
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
|
||||||
|
date.getDate()
|
||||||
|
).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(
|
||||||
|
date.getMinutes()
|
||||||
|
).padStart(2, "0")}`;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
|
/* 카드 스타일 */
|
||||||
.card {
|
.card {
|
||||||
border: 1px solid #e6e6e6;
|
border: 1px solid #e6e6e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -108,17 +120,23 @@
|
|||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* card-img-top의 모서리 둥글게 처리 */
|
/* 텍스트 줄임 표시 */
|
||||||
.card-img-top {
|
.str_wrap {
|
||||||
border-top-left-radius: 8px;
|
overflow: hidden;
|
||||||
border-top-right-radius: 8px;
|
text-overflow: ellipsis;
|
||||||
}
|
|
||||||
|
|
||||||
/* 내용 텍스트를 두 줄로 제한 */
|
|
||||||
.limit-two-lines {
|
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
}
|
||||||
|
|
||||||
|
/* 이미지 스타일 */
|
||||||
|
.img-fluid {
|
||||||
|
border-radius: 8px 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 태그 배지 스타일 */
|
||||||
|
.badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container my-4">
|
<div class="mt-4">
|
||||||
<div class="row">
|
|
||||||
<div v-if="posts.length === 0" class="text-center">
|
<div v-if="posts.length === 0" class="text-center">
|
||||||
<p class="text-muted mt-4">게시물이 없습니다.</p>
|
<p class="text-muted mt-4">게시물이 없습니다.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-4 d-flex justify-content-center" v-for="post in posts" :key="post.id" @click="handleClick(post.id)">
|
<div v-for="post in posts" :key="post.id" @click="handleClick(post.id)">
|
||||||
<BoardCard
|
<BoardCard
|
||||||
:img="post.img || null"
|
:img="post.img || null"
|
||||||
:category="post.category || ''"
|
:category="post.category || ''"
|
||||||
@ -12,15 +11,12 @@
|
|||||||
:content="post.content"
|
:content="post.content"
|
||||||
:date="post.date"
|
:date="post.date"
|
||||||
:views="post.views || 0"
|
:views="post.views || 0"
|
||||||
:likes="post.likes"
|
|
||||||
:dislikes="post.dislikes"
|
|
||||||
v-bind="getBoardCardProps(post)"
|
v-bind="getBoardCardProps(post)"
|
||||||
:attachment="post.attachment || false"
|
:attachment="post.attachment || false"
|
||||||
@click="() => goDetail(post.id)"
|
@click="() => goDetail(post.id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const selectAlphabet = (alphabet) => {
|
|||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.alphabet-list {
|
.alphabet-list {
|
||||||
overflow: scroll;
|
overflow-x: scroll;
|
||||||
flex-wrap: nowrap !important;
|
flex-wrap: nowrap !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<li class="mt-5 card p-5">
|
<li class="mt-5 card p-5">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="w-100 d-flex align-items-center">
|
<div class="w-100 d-flex align-items-center">
|
||||||
<span class="btn btn-primary">{{ item.category }}</span>
|
<span class="btn btn-primary pe-none">{{ item.category }}</span>
|
||||||
<strong class="mx-2 w-75">{{ item.WRDDICTTL }}</strong>
|
<strong class="mx-2 w-75">{{ item.WRDDICTTL }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<EditBtn />
|
<EditBtn />
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="d-flex">
|
||||||
<FormSelect/>
|
<FormSelect :isLabel="false" :data="formattedDataList" :isCommon="true" name="용어집 카테고리" title="용어집 카테고리"/>
|
||||||
<button>쇼핑몰</button>
|
<button @click="toggleInput">+</button>
|
||||||
<button>저장</button>
|
<div class="d-flex" v-if="showInput">
|
||||||
|
<FormInput :isLabel="false" title="용어집 입력" name="용어집 입력"/>
|
||||||
|
<button @click="saveInput">저장</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<FormInput/>
|
<FormInput title="내용" name="용어집 내용 입력"/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<QEditor/>
|
<QEditor/>
|
||||||
@ -15,7 +18,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { defineProps, computed, ref, defineEmits } from 'vue';
|
||||||
|
|
||||||
import QEditor from '@/components/editor/QEditor.vue';
|
import QEditor from '@/components/editor/QEditor.vue';
|
||||||
import FormInput from '@/components/input/FormInput.vue';
|
import FormInput from '@/components/input/FormInput.vue';
|
||||||
import FormSelect from '@/components/input/FormSelect.vue';
|
import FormSelect from '@/components/input/FormSelect.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dataList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 데이터 포맷 수정
|
||||||
|
const formattedDataList = computed(() =>
|
||||||
|
props.dataList.map(item => ({
|
||||||
|
label: item.CMNCODNAM,
|
||||||
|
value: item.CMNCODVAL
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// 카테고리 입력 창
|
||||||
|
const showInput = ref(false);
|
||||||
|
|
||||||
|
// 카테고리 입력 토글
|
||||||
|
const toggleInput = () => {
|
||||||
|
howInput.value = !showInput.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 카테고리 저장
|
||||||
|
const saveInput = () => {
|
||||||
|
console.log('입력값 저장됨!');
|
||||||
|
showInput.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -2,16 +2,15 @@
|
|||||||
<div class="container flex-grow-1 container-p-y">
|
<div class="container flex-grow-1 container-p-y">
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<!-- 검색창 -->
|
<!-- 검색창 -->
|
||||||
<div class="container col-11 px-10 ">
|
<div class="container col-8 px-3">
|
||||||
<search-bar @update:data="search" />
|
<search-bar @update:data="search" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 새 글쓰기 -->
|
<!-- 새 글쓰기 -->
|
||||||
<div class="container col-1 px-12 py-2">
|
<div class="container col-2 px-12 py-2">
|
||||||
<router-link to="/board/write">
|
<router-link to="/board/write">
|
||||||
<WriteButton />
|
<WriteButton />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<!-- 셀렉트 박스 -->
|
<!-- 셀렉트 박스 -->
|
||||||
@ -21,38 +20,80 @@
|
|||||||
<option value="views">조회수</option>
|
<option value="views">조회수</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 공지 접기 기능 -->
|
||||||
|
<div class="container col-1 px-0 py-2">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" v-model="showNotices" /> 공지 숨기기
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 리스트 갯수 선택 -->
|
||||||
<!-- 공지사항 리스트 -->
|
<div class="container col-1 px-0 py-2">
|
||||||
<div v-if="pagination.currentPage === 1" class="mb-8">
|
<select class="form-select" v-model="selectedSize" @change="handleSizeChange">
|
||||||
<div class="row">
|
<option value="10">10개씩</option>
|
||||||
<BoardCardList :posts="noticeList" @click="goDetail" />
|
<option value="20">20개씩</option>
|
||||||
|
<option value="30">30개씩</option>
|
||||||
|
<option value="50">50개씩</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 일반 리스트 -->
|
|
||||||
<div>
|
|
||||||
<div class="row">
|
|
||||||
<BoardCardList :posts="generalList" @click="goDetail" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
|
<!-- 게시판 -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 8%;">번호</th>
|
||||||
|
<th style="width: 50%;">제목</th>
|
||||||
|
<th style="width: 15%;">작성자</th>
|
||||||
|
<th style="width: 12%;">작성일</th>
|
||||||
|
<th style="width: 10%;">조회수</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- 공지사항 -->
|
||||||
|
<template v-if="pagination.currentPage === 1 && !showNotices">
|
||||||
|
<tr v-for="(notice, index) in noticeList"
|
||||||
|
:key="'notice-' + index"
|
||||||
|
class="notice-row clickable-row"
|
||||||
|
@click="goDetail(notice.id)">
|
||||||
|
<td>공지</td>
|
||||||
|
<td>
|
||||||
|
📌 {{ notice.title }}
|
||||||
|
<i v-if="notice.img" class="bi bi-image me-1"></i>
|
||||||
|
<i v-if="notice.hasAttachment" class="bi bi-paperclip"></i>
|
||||||
|
<span v-if="isNewPost(notice.date)" class="badge bg-danger text-white ms-2 new-badge">N</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ notice.author }}</td>
|
||||||
|
<td>{{ notice.date }}</td>
|
||||||
|
<td>{{ notice.views }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<!-- 일반 게시물 -->
|
||||||
|
<tr v-for="(post, index) in generalList"
|
||||||
|
:key="'post-' + index"
|
||||||
|
class="general-row clickable-row"
|
||||||
|
@click="goDetail(post.id)">
|
||||||
|
<td>{{ post.id }}</td>
|
||||||
|
<td>
|
||||||
|
{{ post.title }}
|
||||||
|
<i v-if="post.img" class="bi bi-image me-1"></i>
|
||||||
|
<i v-if="post.hasAttachment" class="bi bi-paperclip"></i>
|
||||||
|
<span v-if="isNewPost(post.date)" class="badge bg-danger text-white ms-2 new-badge">N</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ post.author }}</td>
|
||||||
|
<td>{{ post.date }}</td>
|
||||||
|
<td>{{ post.views }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 페이지네이션 -->
|
<!-- 페이지네이션 -->
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<Pagination
|
<Pagination
|
||||||
:currentPage="pagination.currentPage"
|
v-bind="pagination"
|
||||||
:pages="pagination.pages"
|
|
||||||
:prePage="pagination.prePage"
|
|
||||||
:nextPage="pagination.nextPage"
|
|
||||||
:isFirstPage="pagination.isFirstPage"
|
|
||||||
:isLastPage="pagination.isLastPage"
|
|
||||||
:hasPreviousPage="pagination.hasPreviousPage"
|
|
||||||
:hasNextPage="pagination.hasNextPage"
|
|
||||||
:navigatePages="pagination.navigatePages"
|
|
||||||
:navigatepageNums="pagination.navigatepageNums"
|
|
||||||
:navigateFirstPage="pagination.navigateFirstPage"
|
|
||||||
:navigateLastPage="pagination.navigateLastPage"
|
|
||||||
@update:currentPage="handlePageChange"
|
@update:currentPage="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -62,19 +103,28 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BoardCardList from '@/components/list/BoardCardList.vue';
|
|
||||||
import Pagination from '@c/pagination/Pagination.vue';
|
import Pagination from '@c/pagination/Pagination.vue';
|
||||||
import SearchBar from '@c/search/SearchBar.vue';
|
import SearchBar from '@c/search/SearchBar.vue';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import WriteButton from '@c/button/WriteBtn.vue';
|
import WriteButton from '@c/button/WriteBtn.vue';
|
||||||
import axios from '@api';
|
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);
|
||||||
|
|
||||||
// 데이터 초기화
|
// 데이터 초기화
|
||||||
const generalList = ref([]);
|
const generalList = ref([]);
|
||||||
const noticeList = ref([]);
|
const noticeList = ref([]);
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
const selectedOrder = ref('date');
|
const selectedOrder = ref('date');
|
||||||
const sortDirection = ref('desc');
|
const selectedSize = ref(10);
|
||||||
|
const showNotices = ref(false);
|
||||||
|
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pages: 1,
|
pages: 1,
|
||||||
@ -95,11 +145,22 @@ const goDetail = (id) => {
|
|||||||
router.push({ name: 'BoardDetail', params: { id } });
|
router.push({ name: 'BoardDetail', params: { 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');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 새로 올라온 게시물 여부 판단 (오늘 또는 어제 작성된 경우)
|
||||||
|
const isNewPost = (dateString) => {
|
||||||
|
const date = dayjs(dateString);
|
||||||
|
return date.isToday() || date.isYesterday();
|
||||||
|
};
|
||||||
|
|
||||||
// 검색 처리
|
// 검색 처리
|
||||||
const search = (e) => {
|
const search = (e) => {
|
||||||
searchText.value = e.trim();
|
searchText.value = e.trim();
|
||||||
fetchGeneralPosts(1);
|
fetchGeneralPosts(1);
|
||||||
fetchNoticePosts(searchText.value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 정렬 변경 핸들러
|
// 정렬 변경 핸들러
|
||||||
@ -107,71 +168,78 @@ const handleSortChange = () => {
|
|||||||
fetchGeneralPosts(1);
|
fetchGeneralPosts(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 게시물 데이터 로드(일반)
|
// 리스트 개수 변경 핸들러
|
||||||
|
const handleSizeChange = () => {
|
||||||
|
fetchGeneralPosts(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 일반 게시물 데이터 로드
|
||||||
const fetchGeneralPosts = async (page = 1) => {
|
const fetchGeneralPosts = async (page = 1) => {
|
||||||
const response = await axios.get("board/general", {
|
try {
|
||||||
|
const { data } = await axios.get("board/general", {
|
||||||
params: {
|
params: {
|
||||||
page: page,
|
page,
|
||||||
|
size: selectedSize.value,
|
||||||
orderBy: selectedOrder.value,
|
orderBy: selectedOrder.value,
|
||||||
sortDirection: sortDirection.value,
|
|
||||||
searchKeyword: searchText.value
|
searchKeyword: searchText.value
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data && response.data.data) {
|
if (data?.data) {
|
||||||
const data = response.data.data;
|
console.log(data)
|
||||||
// 게시물 리스트 업데이트
|
const totalPosts = data.data.total; // 전체 게시물 개수 받아오기 (API가 제공해야 함)
|
||||||
generalList.value = data.list.map((post, index) => ({
|
|
||||||
...post,
|
generalList.value = data.data.list.map((post, index) => ({
|
||||||
id: post.id || index,
|
id: totalPosts - ((page - 1) * selectedSize.value) - index,
|
||||||
img: post.firstImageUrl || null,
|
title: post.title,
|
||||||
|
author: post.author || '익명',
|
||||||
|
date: formatDate(post.date), // 날짜 변환 적용
|
||||||
views: post.cnt || 0,
|
views: post.cnt || 0,
|
||||||
likes: post.likeCount != null ? post.dislikeCount : null,
|
hasAttachment: post.hasAttachment || false,
|
||||||
dislikes: post.dislikeCount != null ? post.likeCount : null,
|
img: post.firstImageUrl || null
|
||||||
comments: post.commentCount != null ? post.commentCount : null,
|
|
||||||
attachment: post.hasAttachment || false,
|
|
||||||
content: post.plainContent || '',
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 페이지네이션 정보 업데이트
|
|
||||||
pagination.value = {
|
pagination.value = {
|
||||||
currentPage: data.pageNum,
|
...pagination.value,
|
||||||
pages: data.pages,
|
currentPage: data.data.pageNum,
|
||||||
prePage: data.prePage,
|
pages: data.data.pages,
|
||||||
nextPage: data.nextPage,
|
prePage: data.data.prePage,
|
||||||
isFirstPage: data.isFirstPage,
|
nextPage: data.data.nextPage,
|
||||||
isLastPage: data.isLastPage,
|
isFirstPage: data.data.isFirstPage,
|
||||||
hasPreviousPage: data.hasPreviousPage,
|
isLastPage: data.data.isLastPage,
|
||||||
hasNextPage: data.hasNextPage,
|
hasPreviousPage: data.data.hasPreviousPage,
|
||||||
navigatePages: data.navigatePages,
|
hasNextPage: data.data.hasNextPage,
|
||||||
navigatepageNums: data.navigatepageNums,
|
navigatePages: data.data.navigatePages,
|
||||||
navigateFirstPage: data.navigateFirstPage,
|
navigatepageNums: data.data.navigatepageNums,
|
||||||
navigateLastPage: data.navigateLastPage
|
navigateFirstPage: data.data.navigateFirstPage,
|
||||||
|
navigateLastPage: data.data.navigateLastPage
|
||||||
};
|
};
|
||||||
} else {
|
}
|
||||||
console.error("데이터 오류:", response.data);
|
} catch (error) {
|
||||||
|
console.error("데이터 오류:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 게시물 데이터 로드(공지사항)
|
// 공지사항 데이터 로드
|
||||||
const fetchNoticePosts = async () => {
|
const fetchNoticePosts = async () => {
|
||||||
const response = await axios.get("board/notices", {
|
try {
|
||||||
params: {
|
const { data } = await axios.get("board/notices", {
|
||||||
searchKeyword: searchText.value
|
params: { searchKeyword: searchText.value }
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data && response.data.data && Array.isArray(response.data.data)) {
|
if (data?.data) {
|
||||||
noticeList.value = response.data.data.map((post, index) => ({
|
noticeList.value = data.data.map(post => ({
|
||||||
...post,
|
id: post.id,
|
||||||
id: post.id || index,
|
title: post.title,
|
||||||
img: post.firstImageUrl || null,
|
author: post.author || '관리자',
|
||||||
|
date: formatDate(post.date),
|
||||||
views: post.cnt || 0,
|
views: post.cnt || 0,
|
||||||
attachment: post.hasAttachment || false,
|
hasAttachment: post.hasAttachment || false,
|
||||||
content: post.plainContent || '',
|
img: post.firstImageUrl || null
|
||||||
}));
|
}));
|
||||||
} else {
|
}
|
||||||
console.error("데이터 오류:", response.data);
|
} catch (error) {
|
||||||
|
console.error("데이터 오류:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -184,7 +252,39 @@ const handlePageChange = (page) => {
|
|||||||
|
|
||||||
// 데이터 로드
|
// 데이터 로드
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchGeneralPosts();
|
|
||||||
fetchNoticePosts();
|
fetchNoticePosts();
|
||||||
|
fetchGeneralPosts();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 공지사항 스타일 */
|
||||||
|
.notice-row {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.notice-row td {
|
||||||
|
color: #DC3545 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 일반 게시물 스타일 */
|
||||||
|
.general-row {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 행 전체 클릭 가능 */
|
||||||
|
.clickable-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.clickable-row:hover {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 새 글 아이콘 크기 조정 */
|
||||||
|
.new-badge {
|
||||||
|
font-size: 0.65rem; /* 원래 크기의 약 절반 */
|
||||||
|
padding: 0.2em 0.4em; /* 배지 크기 조정 */
|
||||||
|
vertical-align: middle; /* 텍스트 정렬 */
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
:boardId="currentBoardId"
|
:boardId="currentBoardId"
|
||||||
:profileName="profileName"
|
:profileName="profileName"
|
||||||
:views="views"
|
:views="views"
|
||||||
:likes="likes"
|
:comments="comments"
|
||||||
:dislikes="dislikes"
|
:date="formattedBoardDate"
|
||||||
class="pb-6 border-bottom"
|
class="pb-6 border-bottom"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -49,8 +49,8 @@
|
|||||||
:bigBtn="true"
|
:bigBtn="true"
|
||||||
:boardId="currentBoardId"
|
:boardId="currentBoardId"
|
||||||
:commentId="null"
|
:commentId="null"
|
||||||
:likeCount="currentLikeCount"
|
:likeCount="likes"
|
||||||
:dislikeCount="currentDislikeCount"
|
:dislikeCount="dislikes"
|
||||||
@updateReaction="handleUpdateReaction"
|
@updateReaction="handleUpdateReaction"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -63,7 +63,7 @@
|
|||||||
</ul> -->
|
</ul> -->
|
||||||
|
|
||||||
<!-- 댓글 영역 -->
|
<!-- 댓글 영역 -->
|
||||||
<BoardComentArea :comments="comments" />
|
<BoardComentArea />
|
||||||
|
|
||||||
<!-- 수정 버튼 -->
|
<!-- 수정 버튼 -->
|
||||||
<!-- <button class="btn btn-primary" @click="goToEditPage">
|
<!-- <button class="btn btn-primary" @click="goToEditPage">
|
||||||
@ -87,7 +87,7 @@ import BoardProfile from '@c/board/BoardProfile.vue';
|
|||||||
import BoardCommentList from '@/components/board/BoardCommentList.vue';
|
import BoardCommentList from '@/components/board/BoardCommentList.vue';
|
||||||
import BoardRecommendBtn from '@/components/button/BoardRecommendBtn.vue';
|
import BoardRecommendBtn from '@/components/button/BoardRecommendBtn.vue';
|
||||||
import Pagination from '@/components/pagination/Pagination.vue';
|
import Pagination from '@/components/pagination/Pagination.vue';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
|
|
||||||
@ -95,11 +95,14 @@ import axios from '@api';
|
|||||||
const profileName = ref('익명 사용자');
|
const profileName = ref('익명 사용자');
|
||||||
const boardTitle = ref('제목 없음');
|
const boardTitle = ref('제목 없음');
|
||||||
const boardContent = ref('');
|
const boardContent = ref('');
|
||||||
const comments = ref([]);
|
|
||||||
const attachments = ref([]);
|
const attachments = ref([]);
|
||||||
|
const date = ref('');
|
||||||
const views = ref(0);
|
const views = ref(0);
|
||||||
const likes = ref(0);
|
const likes = ref(0);
|
||||||
const dislikes = ref(0);
|
const dislikes = ref(0);
|
||||||
|
const likeClicked = ref(false);
|
||||||
|
const dislikeClicked = ref(false);
|
||||||
|
const comments = ref(0);
|
||||||
const attachment = ref(false);
|
const attachment = ref(false);
|
||||||
|
|
||||||
// 라우트에서 ID 가져오기
|
// 라우트에서 ID 가져오기
|
||||||
@ -119,38 +122,66 @@ const fetchBoardDetails = async () => {
|
|||||||
const data = response.data.data;
|
const data = response.data.data;
|
||||||
|
|
||||||
// API 응답 데이터 반영
|
// API 응답 데이터 반영
|
||||||
const boardDetail = data.boardDetail || {};
|
// const boardDetail = data.boardDetail || {};
|
||||||
// console.log('boardDetail:', boardDetail);
|
|
||||||
|
// console.log('API Response:', response.data);
|
||||||
|
|
||||||
profileName.value = data.author || '익명 사용자';
|
profileName.value = data.author || '익명 사용자';
|
||||||
boardTitle.value = data.title || '제목 없음';
|
boardTitle.value = data.title || '제목 없음';
|
||||||
boardContent.value = data.content || '';
|
boardContent.value = data.content || '';
|
||||||
|
date.value = data.date || '';
|
||||||
views.value = data.cnt || 0;
|
views.value = data.cnt || 0;
|
||||||
likes.value = data.likeCount || 0;
|
likes.value = data.likeCount || 0;
|
||||||
dislikes.value = data.dislikeCount || 0;
|
dislikes.value = data.dislikeCount || 0;
|
||||||
attachment.value = data.hasAttachment || null;
|
attachment.value = data.hasAttachment || null;
|
||||||
|
comments.value = data.commentCount || 0;
|
||||||
|
|
||||||
attachments.value = data.attachments || [];
|
|
||||||
comments.value = data.comments || [];
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentLikeCount = ref(10);
|
|
||||||
const currentDislikeCount = ref(2);
|
|
||||||
|
|
||||||
// 좋아요, 싫어요
|
// 좋아요, 싫어요
|
||||||
const handleUpdateReaction = async ({ type, boardId, commentId }) => {
|
const handleUpdateReaction = async ({ type, boardId, commentId }) => {
|
||||||
try {
|
try {
|
||||||
const cmtId = commentId !== null ? commentId : 0;
|
//이거 왜 한건지 찾아보자
|
||||||
|
const response = await axios.post(`/board/${boardId}/${commentId}/reaction`, { type });
|
||||||
|
|
||||||
const response = await axios.post(`/board/${boardId}/${cmtId}/reaction`, { type });
|
// console.log('API 응답:', response.data);
|
||||||
|
// console.log(commentId)
|
||||||
|
|
||||||
|
if (response.data.code === 200) {
|
||||||
|
if (type === 'like') {
|
||||||
|
likeClicked.value = !likeClicked.value;
|
||||||
|
likes.value += likeClicked.value ? 1 : -1;
|
||||||
|
} else if (type === 'dislike') {
|
||||||
|
dislikeClicked.value = !dislikeClicked.value;
|
||||||
|
dislikes.value += dislikeClicked.value ? 1 : -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('반응 업데이트 실패');
|
||||||
|
}
|
||||||
|
// likes.value = response.data.likeCount;
|
||||||
|
// dislikes.value = response.data.dislikeCount;
|
||||||
|
|
||||||
|
// if (type === 'like') {
|
||||||
|
// likeClicked.value = !likeClicked.value;
|
||||||
|
// console.log('aaaa' ,response.data)
|
||||||
|
|
||||||
|
// } else if (type === 'dislike') {
|
||||||
|
// console.log('싫어')
|
||||||
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('반응을 업데이트하는 중 오류 발생');
|
alert('반응을 업데이트하는 중 오류 발생');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 날짜
|
||||||
|
const formattedBoardDate = computed(() => {
|
||||||
|
const dateObj = new Date(date.value);
|
||||||
|
return `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')} ${String(dateObj.getHours()).padStart(2, '0')}:${String(dateObj.getMinutes()).padStart(2, '0')}`;
|
||||||
|
});
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchBoardDetails();
|
fetchBoardDetails();
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vacation-management">
|
<div class="vacation-management">
|
||||||
<div class="container-xxl flex-grow-1 container-p-y">
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
<!-- 저장 버튼 -->
|
|
||||||
<div class="save-button-container">
|
<div class="save-button-container">
|
||||||
<button class="btn btn-success" @click="addVacationRequests">✔ 저장</button>
|
<button class="btn btn-success" @click="addVacationRequests">✔ 저장</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 캘린더 -->
|
|
||||||
<div class="card app-calendar-wrapper">
|
<div class="card app-calendar-wrapper">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col app-calendar-content">
|
<div class="col app-calendar-content">
|
||||||
@ -13,28 +11,17 @@
|
|||||||
<div class="card-body pb-0">
|
<div class="card-body pb-0">
|
||||||
<full-calendar
|
<full-calendar
|
||||||
ref="fullCalendarRef"
|
ref="fullCalendarRef"
|
||||||
:events="calendarEvents"
|
|
||||||
:options="calendarOptions"
|
:options="calendarOptions"
|
||||||
defaultView="dayGridMonth"
|
|
||||||
class="flatpickr-calendar-only"
|
class="flatpickr-calendar-only"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 오전/오후 반차 버튼 -->
|
|
||||||
<div class="half-day-buttons">
|
<div class="half-day-buttons">
|
||||||
<button
|
<button class="btn btn-info" :class="{ active: halfDayType === 'AM' }" @click="toggleHalfDay('AM')">
|
||||||
class="btn btn-info"
|
오전반차
|
||||||
:class="{ active: halfDayType === 'AM' }"
|
|
||||||
@click="toggleHalfDay('AM')"
|
|
||||||
>
|
|
||||||
☀️ 오전반차
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn btn-warning" :class="{ active: halfDayType === 'PM' }" @click="toggleHalfDay('PM')">
|
||||||
class="btn btn-warning"
|
오후반차
|
||||||
:class="{ active: halfDayType === 'PM' }"
|
|
||||||
@click="toggleHalfDay('PM')"
|
|
||||||
>
|
|
||||||
🌙 오후반차
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -45,31 +32,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FullCalendar from '@fullcalendar/vue3';
|
import FullCalendar from "@fullcalendar/vue3";
|
||||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
import interactionPlugin from '@fullcalendar/interaction';
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
import 'flatpickr/dist/flatpickr.min.css';
|
import "flatpickr/dist/flatpickr.min.css";
|
||||||
import '@/assets/css/app-calendar.css';
|
import "@/assets/css/app-calendar.css";
|
||||||
import { reactive, ref, onMounted } from 'vue';
|
import { reactive, ref, onMounted } from "vue";
|
||||||
import axios from '@api'; // Axios 추가
|
import axios from "@api";
|
||||||
|
|
||||||
const fullCalendarRef = ref(null);
|
const fullCalendarRef = ref(null);
|
||||||
const calendarEvents = ref([]);
|
const calendarEvents = ref([]); // FullCalendar의 이벤트 리스트
|
||||||
const selectedDates = ref(new Map());
|
const selectedDates = ref(new Map());
|
||||||
const halfDayType = ref(null);
|
const halfDayType = ref(null);
|
||||||
const employeeId = ref(1);
|
const employeeId = ref(1);
|
||||||
|
|
||||||
const calendarOptions = reactive({
|
const calendarOptions = reactive({
|
||||||
plugins: [dayGridPlugin, interactionPlugin],
|
plugins: [dayGridPlugin, interactionPlugin],
|
||||||
initialView: 'dayGridMonth',
|
initialView: "dayGridMonth",
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: 'today',
|
left: "today",
|
||||||
center: 'title',
|
center: "title",
|
||||||
right: 'prev,next',
|
right: "prev,next",
|
||||||
},
|
},
|
||||||
locale: 'ko',
|
locale: "ko",
|
||||||
selectable: true,
|
selectable: true,
|
||||||
dateClick: handleDateClick,
|
dateClick: handleDateClick,
|
||||||
|
events: calendarEvents, // 이벤트 리스트를 직접 반영
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,25 +65,12 @@ const calendarOptions = reactive({
|
|||||||
*/
|
*/
|
||||||
function handleDateClick(info) {
|
function handleDateClick(info) {
|
||||||
const date = info.dateStr;
|
const date = info.dateStr;
|
||||||
const dayElement = info.dayEl;
|
|
||||||
|
|
||||||
if (!selectedDates.value.has(date)) {
|
if (!selectedDates.value.has(date)) {
|
||||||
const type = halfDayType.value ? (halfDayType.value === 'AM' ? 'D' : 'N') : 'F';
|
const type = halfDayType.value ? (halfDayType.value === "AM" ? "D" : "N") : "F";
|
||||||
selectedDates.value.set(date, type);
|
selectedDates.value.set(date, type);
|
||||||
|
|
||||||
if (type === 'D') {
|
|
||||||
dayElement.style.backgroundImage = 'linear-gradient(to bottom, #ade3ff 50%, transparent 50%)';
|
|
||||||
} else if (type === 'N') {
|
|
||||||
dayElement.style.backgroundImage = 'linear-gradient(to top, #ade3ff 50%, transparent 50%)';
|
|
||||||
} else {
|
|
||||||
dayElement.style.backgroundColor = '#ade3ff';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
selectedDates.value.delete(date);
|
selectedDates.value.delete(date);
|
||||||
dayElement.style.backgroundColor = '';
|
|
||||||
dayElement.style.backgroundImage = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
halfDayType.value = null;
|
halfDayType.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,72 +81,60 @@ function toggleHalfDay(type) {
|
|||||||
halfDayType.value = halfDayType.value === type ? null : type;
|
halfDayType.value = halfDayType.value === type ? null : type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 백엔드에서 휴가 데이터를 가져와 FullCalendar에 반영
|
||||||
|
*/
|
||||||
async function fetchVacationData() {
|
async function fetchVacationData() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('vacation/list');
|
const response = await axios.get("vacation/list");
|
||||||
if (response.data.status === 'OK') {
|
|
||||||
|
if (response.data.status === "OK") {
|
||||||
const vacationList = response.data.data;
|
const vacationList = response.data.data;
|
||||||
|
console.log("📌 백엔드 응답 데이터:", vacationList);
|
||||||
|
|
||||||
// 사원별로 날짜를 그룹화
|
if (!Array.isArray(vacationList)) {
|
||||||
const employeeVacations = new Map();
|
throw new Error("vacationList is not an array.");
|
||||||
|
|
||||||
vacationList.forEach(({ employeeId, date, type }) => {
|
|
||||||
if (!employeeVacations.has(employeeId)) {
|
|
||||||
employeeVacations.set(employeeId, []);
|
|
||||||
}
|
}
|
||||||
employeeVacations.get(employeeId).push({ date, type });
|
|
||||||
});
|
|
||||||
|
|
||||||
// 연속된 날짜 그룹화 및 스타일 적용
|
// 이벤트 리스트 변환
|
||||||
employeeVacations.forEach((dates, employeeId) => {
|
const events = vacationList.map((vac) => {
|
||||||
const sortedDates = dates.map(d => new Date(d.date)).sort((a, b) => a - b);
|
let dateStr = vac.LOCVACUDT.split("T")[0];
|
||||||
const color = getColorByEmployeeId(employeeId); // 사원별 색상 함수
|
let className = "fc-daygrid-event";
|
||||||
|
|
||||||
let previousDate = null;
|
let title = "연차";
|
||||||
|
if (vac.LOCVACTYP === "D") {
|
||||||
sortedDates.forEach(currentDate => {
|
title = "오전반차";
|
||||||
const dateStr = currentDate.toISOString().split('T')[0];
|
className += " half-day-am";
|
||||||
const dayElement = document.querySelector(`[data-date="${dateStr}"]`);
|
} else if (vac.LOCVACTYP === "N") {
|
||||||
|
title = "오후반차";
|
||||||
if (dayElement) {
|
className += " half-day-pm";
|
||||||
if (
|
} else if (vac.LOCVACTYP === "F") {
|
||||||
previousDate &&
|
title = "연차";
|
||||||
currentDate - previousDate === 86400000 // 하루 차이인지 확인
|
className += " full-day";
|
||||||
) {
|
|
||||||
// 연속된 날짜 스타일 설정
|
|
||||||
dayElement.style.backgroundColor = color;
|
|
||||||
} else {
|
|
||||||
// 새로운 시작점
|
|
||||||
dayElement.style.backgroundColor = color;
|
|
||||||
dayElement.style.borderLeft = `3px solid ${color}`;
|
|
||||||
}
|
}
|
||||||
previousDate = currentDate;
|
|
||||||
}
|
return {
|
||||||
});
|
title,
|
||||||
});
|
start: dateStr,
|
||||||
|
backgroundColor: getColorByEmployeeId(vac.MEMBERSEQ),
|
||||||
|
classNames: [className],
|
||||||
|
};
|
||||||
|
}).filter((event) => event !== null);
|
||||||
|
|
||||||
|
console.log("📌 변환된 이벤트:", events);
|
||||||
|
calendarEvents.value = events; // FullCalendar 이벤트 업데이트
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching vacation data:', error);
|
console.error("Error fetching vacation data:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 사원 ID별 색상 반환 함수
|
|
||||||
*/
|
|
||||||
function getColorByEmployeeId(employeeId) {
|
|
||||||
const colors = ['#ade3ff', '#ffade3', '#ade3ad', '#ffadad'];
|
|
||||||
return colors[employeeId % colors.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 데이터 불러오기 호출
|
|
||||||
fetchVacationData();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 휴가 요청 추가
|
* 휴가 요청 추가
|
||||||
*/
|
*/
|
||||||
async function addVacationRequests() {
|
async function addVacationRequests() {
|
||||||
if (selectedDates.value.size === 0) {
|
if (selectedDates.value.size === 0) {
|
||||||
alert('휴가를 선택해주세요.');
|
alert("휴가를 선택해주세요.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,50 +145,41 @@ async function addVacationRequests() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('vacation', vacationRequests);
|
const response = await axios.post("vacation", vacationRequests);
|
||||||
|
|
||||||
if (response.data && response.data.status === 'OK') {
|
if (response.data && response.data.status === "OK") {
|
||||||
alert('휴가가 저장되었습니다.');
|
alert("휴가가 저장되었습니다.");
|
||||||
|
fetchVacationData(); // 휴가 저장 후 데이터 다시 불러오기
|
||||||
const newEvents = vacationRequests.map(req => ({
|
|
||||||
title: req.type === 'D' ? '오전반차' : req.type === 'N' ? '오후반차' : '종일 휴가',
|
|
||||||
start: req.date,
|
|
||||||
allDay: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
calendarEvents.value = [...calendarEvents.value, ...newEvents];
|
|
||||||
selectedDates.value.clear();
|
selectedDates.value.clear();
|
||||||
resetCalendarStyles();
|
|
||||||
} else {
|
} else {
|
||||||
alert('휴가 저장 중 오류가 발생했습니다.');
|
alert("휴가 저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
alert('휴가 저장에 실패했습니다.');
|
alert("휴가 저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 초기화
|
* 사원 ID별 색상 반환
|
||||||
*/
|
*/
|
||||||
function resetCalendarStyles() {
|
function getColorByEmployeeId(employeeId) {
|
||||||
const calendarElements = document.querySelectorAll('.fc-daygrid-day');
|
const colors = ["#ade3ff", "#ffade3", "#ade3ad", "#ffadad"];
|
||||||
calendarElements.forEach(element => {
|
return colors[employeeId % colors.length];
|
||||||
element.style.backgroundColor = '';
|
|
||||||
element.style.backgroundImage = '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 데이터 불러오기 호출
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchVacationData();
|
fetchVacationData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
.vacation-management {
|
.vacation-management {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 버튼 스타일 */
|
||||||
.half-day-buttons {
|
.half-day-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -236,4 +190,48 @@ onMounted(() => {
|
|||||||
.half-day-buttons .btn.active {
|
.half-day-buttons .btn.active {
|
||||||
border: 2px solid black;
|
border: 2px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FullCalendar 이벤트 스타일 */
|
||||||
|
.fc-daygrid-event {
|
||||||
|
position: absolute !important;
|
||||||
|
height: 20px !important; /* 실선 두께 */
|
||||||
|
width: 90% !important;
|
||||||
|
left: 5% !important;
|
||||||
|
margin: 2px 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
background-color: inherit !important;
|
||||||
|
border: none !important; /* 기본 FullCalendar 테두리 제거 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 여러 이벤트가 같은 날짜에 있을 때 정렬 */
|
||||||
|
.fc-daygrid-event-harness {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 오전반차(왼쪽부터 중앙까지) */
|
||||||
|
.fc-daygrid-event.half-day-am {
|
||||||
|
width: 45% !important;
|
||||||
|
left: 0% !important;
|
||||||
|
background-color: #ffdd57 !important; /* 노란색 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 오후반차(오른쪽부터 중앙까지) */
|
||||||
|
.fc-daygrid-event.half-day-pm {
|
||||||
|
width: 45% !important;
|
||||||
|
left: auto !important;
|
||||||
|
right: 0% !important;
|
||||||
|
background-color: #57a5ff !important; /* 파란색 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 연차 (전체 너비) */
|
||||||
|
.fc-daygrid-event.full-day {
|
||||||
|
width: 100% !important;
|
||||||
|
left: 0 !important;
|
||||||
|
background-color: #ff85a2 !important; /* 연한 빨강 */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -25,12 +25,12 @@
|
|||||||
|
|
||||||
<!-- 카테고리 -->
|
<!-- 카테고리 -->
|
||||||
<div v-if="cateList.length" class="mt-5">
|
<div v-if="cateList.length" class="mt-5">
|
||||||
<CategoryBtn :lists="cateList" />
|
<CategoryBtn :lists="cateList" @update:data="handleSelectedCategoryChange"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 작성 -->
|
<!-- 작성 -->
|
||||||
<div v-if="isWriteVisible" class="mt-5">
|
<div v-if="isWriteVisible" class="mt-5">
|
||||||
<DictWrite @close="isWriteVisible = false" />
|
<DictWrite @close="isWriteVisible = false" :dataList="cateList"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -50,16 +50,9 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- <ul v-if="wordList.length > 0" class="px-0 list-unstyled">
|
|
||||||
<DictCard
|
|
||||||
v-for="item in wordList"
|
|
||||||
:key="item.WRDDICSEQ"
|
|
||||||
:item="item"
|
|
||||||
/>
|
|
||||||
</ul> -->
|
|
||||||
|
|
||||||
<!-- 데이터가 없을 때 -->
|
<!-- 데이터가 없을 때 -->
|
||||||
<div v-else-if="!loading && !error">용어집의 용어가 없습니다.</div>
|
<div v-else-if="!loading && !error" class="card p-5 text-center">용어집의 용어가 없습니다.</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -84,12 +77,17 @@
|
|||||||
|
|
||||||
// 용어집
|
// 용어집
|
||||||
const wordList = ref([]);
|
const wordList = ref([]);
|
||||||
|
|
||||||
//용어집 총개수
|
//용어집 총개수
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
|
||||||
// 카테고리
|
// 카테고리
|
||||||
const cateList = ref([]);
|
const cateList = ref([]);
|
||||||
|
const selectedCategory = ref('');
|
||||||
|
|
||||||
//선택된 알파벳
|
//선택된 알파벳
|
||||||
const selectedAlphabet = ref('');
|
const selectedAlphabet = ref('');
|
||||||
|
|
||||||
// 검색
|
// 검색
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
|
|
||||||
@ -98,77 +96,61 @@
|
|||||||
|
|
||||||
// 데이터 로드
|
// 데이터 로드
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getwordList(); //용어목록조회
|
getwordList();
|
||||||
getwordCategory(); //카테고리목록조회
|
getwordCategory();
|
||||||
});
|
});
|
||||||
|
|
||||||
//용어 목록
|
//용어 목록
|
||||||
const getwordList = (searchKeyword='',indexKeyword='') => {
|
const getwordList = (searchKeyword='', indexKeyword='', category='') => {
|
||||||
axios.get('worddict/getWordList',{
|
axios.get('worddict/getWordList',{
|
||||||
//목록조회시 파라미터 전달
|
//목록조회시 파라미터 전달
|
||||||
params: { searchKeyword: searchKeyword
|
params: {
|
||||||
,indexKeyword:indexKeyword
|
searchKeyword : searchKeyword,
|
||||||
|
indexKeyword :indexKeyword,
|
||||||
|
category : category
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
wordList.value = res.data.data.data; // 용어 목록 저장
|
// 용어 목록 저장
|
||||||
total.value = res.data.data.total; // 총 개수 저장
|
wordList.value = res.data.data.data;
|
||||||
|
// 총 개수 저장
|
||||||
|
total.value = res.data.data.total;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('데이터 로드 오류:', err);
|
console.error('데이터 로드 오류:', err);
|
||||||
error.value = '데이터를 가져오는 중 문제가 발생했습니다.';
|
error.value = '데이터를 가져오는 중 문제가 발생했습니다.';
|
||||||
loading.value = false; // 로딩 종료
|
loading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 카테고리 목록
|
// 카테고리 목록
|
||||||
const getwordCategory = () => {
|
const getwordCategory = () => {
|
||||||
axios.get('worddict/getWordCategory')
|
axios.get('worddict/getWordCategory')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
cateList.value = res.data.data; // 카테고리 목록 저장
|
cateList.value = res.data.data;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('카테고리 로드 오류:', err);
|
console.error('카테고리 로드 오류:', err);
|
||||||
error.value = '카테고리 데이터를 가져오는 중 문제가 발생했습니다.';
|
error.value = '카테고리 데이터를 가져오는 중 문제가 발생했습니다.';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handleSelectedAlphabetChange = (newAlphabet) => {
|
|
||||||
selectedAlphabet.value = newAlphabet;
|
|
||||||
getwordList(searchText.value,selectedAlphabet.value);
|
|
||||||
};
|
|
||||||
// 검색
|
// 검색
|
||||||
const search = (e) => {
|
const search = (e) => {
|
||||||
searchText.value = e.trim();
|
searchText.value = e.trim();
|
||||||
getwordList(searchText.value,selectedAlphabet.value);
|
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 알파벳 선택
|
||||||
|
const handleSelectedAlphabetChange = (newAlphabet) => {
|
||||||
|
selectedAlphabet.value = newAlphabet;
|
||||||
|
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.value);
|
||||||
|
};
|
||||||
|
|
||||||
// 용어집 및 카테고리 목록 통합 API 호출
|
// 카테고리 선택
|
||||||
// const fetchAllData = async () => {
|
const handleSelectedCategoryChange = (category) => {
|
||||||
// loading.value = true;
|
selectedCategory.value = category;
|
||||||
// error.value = '';
|
getwordList(searchText.value, selectedAlphabet.value, selectedCategory.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 filteredList = computed(() =>
|
|
||||||
// wordList.value.filter(item =>
|
|
||||||
// item.WRDDICTTL.toLowerCase().includes(searchText.value.toLowerCase())
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
|
|
||||||
// 작성 toggle
|
// 작성 toggle
|
||||||
const toggleWriteForm = () => {
|
const toggleWriteForm = () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user