Merge remote-tracking branch 'origin/main' into wordDict
This commit is contained in:
commit
ff74a7477b
15
package-lock.json
generated
15
package-lock.json
generated
@ -18,6 +18,7 @@
|
|||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"dompurify": "^3.2.3",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
"front": "file:",
|
"front": "file:",
|
||||||
"heic2any": "^0.0.4",
|
"heic2any": "^0.0.4",
|
||||||
@ -1483,6 +1484,12 @@
|
|||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
|
||||||
@ -2258,6 +2265,14 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"dompurify": "^3.2.3",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
"front": "file:",
|
"front": "file:",
|
||||||
"heic2any": "^0.0.4",
|
"heic2any": "^0.0.4",
|
||||||
|
|||||||
@ -1,56 +1,77 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="list-unstyled">
|
<div>
|
||||||
<li>
|
<BoardProfile :profileName="comment.author" :showDetail="false" :author="true" :isChild="isChild" />
|
||||||
<BoardProfile profileName=곤데리 :showDetail="false" :author="true" />
|
<div class="mt-6">
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<p class="m-0">{{ comment.content }}</p>
|
||||||
<PlusButton @click="toggleComment"/>
|
</div>
|
||||||
<BoardComentArea v-if="comment" />
|
<PlusButton v-if="isPlusButton" @click="toggleComment" class="mt-6"/>
|
||||||
<ul class="list-unstyled twoDepth">
|
<BoardComentArea v-if="isComment" @submit="submitComment"/>
|
||||||
<li>
|
|
||||||
<BoardProfile profileName=곤데리2 :showDetail="false" />
|
<!-- 대댓글 -->
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<ul v-if="comment.children && comment.children.length" class="list-unstyled">
|
||||||
<BoardComentArea v-if="comment" />
|
<li
|
||||||
</li>
|
v-for="child in comment.children"
|
||||||
</ul>
|
:key="child.id"
|
||||||
</li>
|
class="pt-8 ps-10"
|
||||||
<li>
|
>
|
||||||
<BoardProfile profileName=곤데리 :showDetail="false" />
|
<BoardComment :comment="child" :isPlusButton="false" :isChild="true" @submitComment="addChildComment" />
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
</li>
|
||||||
<PlusButton @click="toggleComment"/>
|
</ul>
|
||||||
<BoardComentArea v-if="comment" />
|
<!-- <ul class="list-unstyled twoDepth">
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<BoardProfile profileName=곤데리2 :showDetail="false" />
|
||||||
<BoardProfile profileName=곤데리 :showDetail="false" />
|
<div class="mt-2">저도 궁금합니다.</div>
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<BoardComentArea v-if="comment" />
|
||||||
<PlusButton @click="toggleComment"/>
|
</li>
|
||||||
<BoardComentArea v-if="comment" />
|
</ul> -->
|
||||||
</li>
|
<!-- <BoardProfile profileName=곤데리 :showDetail="false" />
|
||||||
</ul>
|
<div class="mt-2">저도 궁금합니다.</div>
|
||||||
<Pagination/>
|
<PlusButton @click="toggleComment"/>
|
||||||
|
<BoardComentArea v-if="comment" /> -->
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import BoardProfile from './BoardProfile.vue';
|
import BoardProfile from './BoardProfile.vue';
|
||||||
import BoardComentArea from './BoardComentArea.vue';
|
import BoardComentArea from './BoardComentArea.vue';
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import Pagination from '../pagination/Pagination.vue';
|
|
||||||
import PlusButton from '../button/PlusBtn.vue';
|
import PlusButton from '../button/PlusBtn.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
import { defineEmits } from 'vue';
|
import { defineEmits } from 'vue';
|
||||||
|
|
||||||
const comment = ref(false);
|
const props = defineProps({
|
||||||
|
comment: {
|
||||||
const toggleComment = () => {
|
type: Object,
|
||||||
comment.value = !comment.value
|
required: true,
|
||||||
};
|
},
|
||||||
|
isPlusButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
isChild: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// emits 정의
|
// emits 정의
|
||||||
const emit = defineEmits(['submitComment']);
|
const emit = defineEmits(['submitComment']);
|
||||||
|
|
||||||
|
// 댓글 입력 창 토글
|
||||||
|
const isComment = ref(false);
|
||||||
|
const toggleComment = () => {
|
||||||
|
isComment.value = !isComment.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 부모 컴포넌트에 대댓글 추가 요청
|
||||||
|
const addChildComment = (parentId, newComment) => {
|
||||||
|
emit('submitComment', parentId, newComment);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.twoDepth {
|
/* .twoDepth {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
}
|
}
|
||||||
@ -66,5 +87,5 @@ const emit = defineEmits(['submitComment']);
|
|||||||
.btn-text-primary:active,
|
.btn-text-primary:active,
|
||||||
.btn-text-primary:focus {
|
.btn-text-primary:focus {
|
||||||
background-color: transparent
|
background-color: transparent
|
||||||
}
|
} */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
48
src/components/board/BoardCommentList.vue
Normal file
48
src/components/board/BoardCommentList.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="list-unstyled mt-10">
|
||||||
|
<li
|
||||||
|
v-for="comment in comments"
|
||||||
|
:key="comment.id"
|
||||||
|
class="mt-8"
|
||||||
|
>
|
||||||
|
<BoardComment :comment="comment" @submitComment="addComment" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import BoardComment from './BoardComment.vue'
|
||||||
|
|
||||||
|
const comments = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
author: '홍길동',
|
||||||
|
content: '저도 궁금합니다.',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
author: '사용자1',
|
||||||
|
content: '저도요!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
author: '사용자2',
|
||||||
|
content: '저도..',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
author: '사용자4',
|
||||||
|
content: '흥미로운 주제네요.',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
author: '사용자5',
|
||||||
|
content: '우오아아아아아앙',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
@ -3,7 +3,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-6 gap-2">
|
<div class="d-flex justify-content-between align-items-center flex-wrap mb-6 gap-2">
|
||||||
<!-- 제목 섹션 -->
|
<!-- 제목 섹션 -->
|
||||||
<div class="me-1">
|
<div class="me-1">
|
||||||
<h5 class="mb-0">{{ boardTitle }}</h5>
|
<h5 class="mb-0">{{ boardTitle }}adada</h5>
|
||||||
</div>
|
</div>
|
||||||
<!-- 첨부파일 섹션 -->
|
<!-- 첨부파일 섹션 -->
|
||||||
<div v-if="dropdownItems.length > 0" class="btn-group">
|
<div v-if="dropdownItems.length > 0" class="btn-group">
|
||||||
|
|||||||
@ -10,13 +10,13 @@
|
|||||||
<span>2024.12.10 10:46</span>
|
<span>2024.12.10 10:46</span>
|
||||||
<template v-if="showDetail">
|
<template v-if="showDetail">
|
||||||
<span>
|
<span>
|
||||||
<i class="fa-regular fa-eye"></i> 1
|
<i class="fa-regular fa-eye"></i> {{ views }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<i class="fa-regular fa-thumbs-up"></i> 1
|
<i class="fa-regular fa-thumbs-up"></i> {{ likes }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<i class="fa-regular fa-thumbs-down"></i> 1
|
<i class="fa-regular fa-thumbs-down"></i> {{ dislikes }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -29,14 +29,16 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="author">
|
<template v-if="author">
|
||||||
<button class="btn author btn-label-primary btn-icon" @click="handleEdit">
|
<EditButton />
|
||||||
|
<DeleteButton />
|
||||||
|
<!-- <button class="btn author btn-label-primary btn-icon" @click="handleEdit">
|
||||||
<i class='bx bx-edit-alt'></i>
|
<i class='bx bx-edit-alt'></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn author btn-label-primary btn-icon" @click="handleDelete">
|
<button class="btn author btn-label-primary btn-icon" @click="handleDelete">
|
||||||
<i class='bx bx-trash'></i>
|
<i class='bx bx-trash'></i>
|
||||||
</button>
|
</button> -->
|
||||||
</template>
|
</template>
|
||||||
<BoardRecommendBtn :likeClicked="true" :dislikeClicked="false" />
|
<BoardRecommendBtn v-if="!isChild" :isRecommend="false" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,13 +50,12 @@ import axios from '@api';
|
|||||||
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';
|
||||||
import { onMounted } from 'vue';
|
|
||||||
|
|
||||||
// Vue Router 인스턴스
|
// Vue Router 인스턴스
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Props 정의
|
// Props 정의
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
profileName: {
|
profileName: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '익명',
|
default: '익명',
|
||||||
@ -71,6 +72,22 @@ defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
views: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
likes: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
dislikes: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
isChild: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const boardId = 100; //수정필요!!
|
const boardId = 100; //수정필요!!
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
<template>
|
<template v-if="isRecommend">
|
||||||
<button class="btn btn-label-primary btn-icon" :class="likeClicked ? 'clicked' : '', bigBtn ? 'big' : '' ">
|
<button class="btn btn-label-primary btn-icon" :class="{'clicked': likeClicked, 'big': bigBtn}" @click="handleLike">
|
||||||
<i class="fa-regular fa-thumbs-up"></i> <span class="num">1</span>
|
<i class="fa-regular fa-thumbs-up"></i> <span class="num">{{ likeCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-label-danger btn-icon" :class="dislikeClicked ? 'clicked' : '', bigBtn ? 'big' : '' ">
|
<button class="btn btn-label-danger btn-icon" :class="{'clicked': dislikeClicked, 'big': bigBtn}" @click="handleDislike">
|
||||||
<i class="fa-regular fa-thumbs-down"></i> <span class="num">1</span>
|
<i class="fa-regular fa-thumbs-down"></i> <span class="num">{{ dislikeCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
likeClicked : {
|
likeClicked : {
|
||||||
type : Boolean,
|
type : Boolean,
|
||||||
default : false,
|
default : false,
|
||||||
@ -20,8 +22,69 @@ defineProps({
|
|||||||
bigBtn : {
|
bigBtn : {
|
||||||
type :Boolean,
|
type :Boolean,
|
||||||
default : false,
|
default : false,
|
||||||
}
|
},
|
||||||
|
isRecommend: {
|
||||||
|
type:Boolean,
|
||||||
|
default:true,
|
||||||
|
},
|
||||||
|
boardId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
commentId: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
likeCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
dislikeCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['updateReaction']);
|
||||||
|
|
||||||
|
const likeClicked = ref(props.likeClicked);
|
||||||
|
const dislikeClicked = ref(props.dislikeClicked);
|
||||||
|
const likeCount = ref(props.likeCount);
|
||||||
|
const dislikeCount = ref(props.dislikeCount);
|
||||||
|
|
||||||
|
// 부모에서 likeCount 또는 dislikeCount가 변경되면 로컬 상태를 업데이트
|
||||||
|
watch(() => props.likeCount, (newVal) => {
|
||||||
|
likeCount.value = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.dislikeCount, (newVal) => {
|
||||||
|
dislikeCount.value = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLike = () => {
|
||||||
|
likeClicked.value = !likeClicked.value;
|
||||||
|
likeCount.value += likeClicked.value ? 1 : -1;
|
||||||
|
emit('updateReaction', { type: 'like', boardId: props.boardId, commentId: props.commentId });
|
||||||
|
if(likeClicked.value === true){
|
||||||
|
if(dislikeClicked.value === true) {
|
||||||
|
dislikeClicked.value = false;
|
||||||
|
dislikeCount.value += -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDislike = () => {
|
||||||
|
dislikeClicked.value = !dislikeClicked.value;
|
||||||
|
dislikeCount.value += dislikeClicked.value ? 1 : -1;
|
||||||
|
emit('updateReaction', { type: 'dislike', boardId: props.boardId, commentId: props.commentId });
|
||||||
|
if(dislikeClicked.value === true){
|
||||||
|
if(likeClicked.value === true) {
|
||||||
|
likeClicked.value = false;
|
||||||
|
likeCount.value += -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -10,14 +10,15 @@
|
|||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
<div v-if="showError" class="text-danger mt-1">
|
<div v-if="showError" class="text-danger mt-1">
|
||||||
{{ errorMsg }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref ,computed} from 'vue';
|
||||||
|
import { fileMsg } from '@/common/msgEnum';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const prop = defineProps({
|
const prop = defineProps({
|
||||||
@ -38,27 +39,43 @@ const prop = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emits = defineEmits(['update:data', 'update:isValid']);
|
const emits = defineEmits(['update:data', 'update:isValid']);
|
||||||
|
|
||||||
// Constants
|
|
||||||
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB
|
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
const errorMsg = ref('첨부파일의 총 용량이 5MB를 초과합니다.');
|
const MAX_FILE_COUNT = 5; // 최대 파일 개수
|
||||||
const showError = ref(false);
|
const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'application/pdf']; // 허용된 파일 유형
|
||||||
|
|
||||||
|
const showError = ref(false);
|
||||||
|
const fileMsgKey = ref(''); // 에러 메시지 키
|
||||||
|
|
||||||
// Change Handler
|
|
||||||
const changeHandler = (event) => {
|
const changeHandler = (event) => {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||||
|
const invalidFiles = files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type));
|
||||||
|
|
||||||
|
// 파일 검증 로직
|
||||||
if (totalSize > MAX_TOTAL_SIZE) {
|
if (totalSize > MAX_TOTAL_SIZE) {
|
||||||
showError.value = true; // 에러 메시지 표시
|
showError.value = true;
|
||||||
emits('update:data', []); // 부모 컴포넌트로 빈 배열 전달
|
fileMsgKey.value = 'FileMaxSizeMsg';
|
||||||
emits('update:isValid', false); // 유효하지 않은 상태 전달
|
emits('update:data', []);
|
||||||
|
emits('update:isValid', false);
|
||||||
|
} else if (files.length > MAX_FILE_COUNT) {
|
||||||
|
showError.value = true;
|
||||||
|
fileMsgKey.value = 'FileMaxLengthMsg';
|
||||||
|
emits('update:data', []);
|
||||||
|
emits('update:isValid', false);
|
||||||
|
} else if (invalidFiles.length > 0) {
|
||||||
|
showError.value = true;
|
||||||
|
fileMsgKey.value = 'FileNotTypeMsg';
|
||||||
|
emits('update:data', []);
|
||||||
|
emits('update:isValid', false);
|
||||||
} else {
|
} else {
|
||||||
showError.value = false; // 에러 메시지 숨기기
|
showError.value = false;
|
||||||
emits('update:data', files); // 부모 컴포넌트로 파일 전달
|
fileMsgKey.value = '';
|
||||||
emits('update:isValid', true); // 유효한 상태 전달
|
emits('update:data', files);
|
||||||
|
emits('update:isValid', true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const errorMessage = computed(() => (fileMsg[fileMsgKey.value] || ''));
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,141 +1,124 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card mb-3 shadow-sm">
|
<div class="card mb-3 shadow-sm fixed-card" style="width: 18rem; height: 17rem">
|
||||||
<div class="row g-0">
|
<!-- 이미지가 있을 경우 card-img-top으로 표시 -->
|
||||||
<!-- 이미지 섹션 -->
|
<img
|
||||||
<div v-if="img" class="col-md-2">
|
v-if="img"
|
||||||
<img
|
:src="img"
|
||||||
:src="img"
|
class="card-img-top"
|
||||||
alt="이미지"
|
alt="이미지"
|
||||||
class="img-fluid rounded-start"
|
style="object-fit: cover; height: 100px;"
|
||||||
style="object-fit: cover; height: 100%; width: 100%;"
|
/>
|
||||||
/>
|
<div class="card-body">
|
||||||
</div>
|
<!-- 카테고리 태그 (존재할 경우) -->
|
||||||
<!-- 게시물 내용 섹션 -->
|
<h6 v-if="category" class="badge rounded-pill bg-primary text-white mb-2">
|
||||||
<div :class="contentColClass">
|
{{ category }}
|
||||||
<div class="card-body">
|
</h6>
|
||||||
<!-- 태그 -->
|
<!-- 제목과 첨부파일 아이콘 -->
|
||||||
<h6 class="badge rounded-pill bg-primary text-white mb-2">
|
<h5 class="card-title">
|
||||||
{{ category }}
|
{{ title }}
|
||||||
</h6>
|
<span class="text-muted" v-if="attachment">
|
||||||
<!-- 제목 -->
|
<i class="fa-solid fa-paperclip"></i>
|
||||||
<h5 class="card-title">
|
</span>
|
||||||
{{ title }}
|
</h5>
|
||||||
<span class="text-muted me-3" v-if="attachment">
|
<!-- 본문 -->
|
||||||
<i class="fa-solid fa-paperclip"></i>
|
<p class="card-text limit-two-lines">{{ content }}</p>
|
||||||
</span>
|
<!-- 날짜 및 조회수, 좋아요, 댓글 -->
|
||||||
</h5>
|
<p class="card-text">
|
||||||
<!-- 본문 -->
|
<small class="text-muted">{{ formattedDate }}</small>
|
||||||
<div class="card-text str_wrap" v-html="$common.contentToHtml(content)"></div>
|
<div class="d-flex mt-2 mt-sm-0">
|
||||||
<!-- 날짜 -->
|
<span class="text-muted me-3">
|
||||||
<div class="d-flex justify-content-between">
|
<i class="fa-regular fa-eye"></i> {{ views || 0 }}
|
||||||
<small class="text-muted">{{ formattedDate }}</small>
|
</span>
|
||||||
<!-- 조회수, 좋아요, 댓글 -->
|
<span class="text-muted me-3" v-if="comments !== null">
|
||||||
<div>
|
<i class="bx bx-comment"></i> {{ comments }}
|
||||||
<span class="text-muted me-3">
|
</span>
|
||||||
<i class="fa-regular fa-eye"></i> {{ views || 0 }}
|
<span class="text-muted me-3" v-if="likes != null">
|
||||||
</span>
|
<i class="bx bx-like"></i> {{ likes }}
|
||||||
<span class="text-muted me-3" v-if="likes != null">
|
</span>
|
||||||
<i class="bx bx-like"></i> {{ likes }}
|
<span class="text-muted me-3" v-if="dislikes != null">
|
||||||
</span>
|
<i class="bx bx-dislike"></i> {{ dislikes }}
|
||||||
<span class="text-muted" v-if="comments !== null">
|
</span>
|
||||||
<i class="bx bx-comment"></i> {{ comments }}
|
</div>
|
||||||
</span>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
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,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
date: {
|
date: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
likes: {
|
likes: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null,
|
default: null,
|
||||||
|
},
|
||||||
|
dislikes: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
comments: {
|
comments: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
attachment: {
|
attachment: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// computed 속성
|
const formattedDate = computed(() => {
|
||||||
const contentColClass = computed(() => {
|
const dateObj = new Date(props.date);
|
||||||
return props.img ? 'col-md-10 col-12' : 'col-md-12';
|
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')}`;
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
// formattedDate을 computed로 정의
|
<style scoped>
|
||||||
const formattedDate = computed(() => {
|
.card {
|
||||||
const date = new Date(props.date);
|
|
||||||
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>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* 카드 스타일 */
|
|
||||||
.card {
|
|
||||||
border: 1px solid #e6e6e6;
|
border: 1px solid #e6e6e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
.card:hover {
|
||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 텍스트 줄임 표시 */
|
/* card-img-top의 모서리 둥글게 처리 */
|
||||||
.str_wrap {
|
.card-img-top {
|
||||||
overflow: hidden;
|
border-top-left-radius: 8px;
|
||||||
text-overflow: ellipsis;
|
border-top-right-radius: 8px;
|
||||||
display: -webkit-box;
|
}
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 이미지 스타일 */
|
/* 내용 텍스트를 두 줄로 제한 */
|
||||||
.img-fluid {
|
.limit-two-lines {
|
||||||
border-radius: 8px 0 0 8px;
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
/* 태그 배지 스타일 */
|
|
||||||
.badge {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 5px 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mt-4">
|
<div class="container my-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 v-for="post in posts" :key="post.id" @click="handleClick(post.id)">
|
<div class="col-md-3 mb-4 d-flex justify-content-center" 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 || ''"
|
||||||
@ -11,11 +12,14 @@
|
|||||||
: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>
|
||||||
|
|
||||||
|
|||||||
@ -1,36 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-xxl 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="col">
|
<div class="container col-11 px-10 ">
|
||||||
<search-bar @update:data="search" />
|
<search-bar @update:data="search" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<!-- 새 글쓰기 -->
|
<!-- 새 글쓰기 -->
|
||||||
<div class="mb-4">
|
<div class="container col-1 px-12 py-2">
|
||||||
<router-link to="/board/write">
|
<router-link to="/board/write">
|
||||||
<WriteButton />
|
<WriteButton />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 공지사항 리스트 -->
|
<div class="row g-3">
|
||||||
<div v-if="pagination.currentPage === 1" class="mb-8">
|
|
||||||
<div class="row g-3">
|
|
||||||
<h3>공지사항</h3>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<BoardCardList :posts="noticeList" @click="goDetail" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 일반 리스트 -->
|
|
||||||
<div>
|
|
||||||
<div class="row g-3">
|
|
||||||
<h3 class="col">일반게시판</h3>
|
|
||||||
|
|
||||||
<!-- 셀렉트 박스 -->
|
<!-- 셀렉트 박스 -->
|
||||||
<div class="col-12 col-md-auto">
|
<div class="col-12 col-md-auto">
|
||||||
<select class="form-select" v-model="selectedOrder" @change="handleSortChange">
|
<select class="form-select" v-model="selectedOrder" @change="handleSortChange">
|
||||||
@ -39,6 +22,16 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 공지사항 리스트 -->
|
||||||
|
<div v-if="pagination.currentPage === 1" class="mb-8">
|
||||||
|
<div class="row">
|
||||||
|
<BoardCardList :posts="noticeList" @click="goDetail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 일반 리스트 -->
|
||||||
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<BoardCardList :posts="generalList" @click="goDetail" />
|
<BoardCardList :posts="generalList" @click="goDetail" />
|
||||||
</div>
|
</div>
|
||||||
@ -131,11 +124,13 @@ const fetchGeneralPosts = async (page = 1) => {
|
|||||||
generalList.value = data.list.map((post, index) => ({
|
generalList.value = data.list.map((post, index) => ({
|
||||||
...post,
|
...post,
|
||||||
id: post.id || index,
|
id: post.id || index,
|
||||||
img: post.img || null,
|
img: post.firstImageUrl || null,
|
||||||
views: post.cnt || 0,
|
views: post.cnt || 0,
|
||||||
likes: post.likeCount != null ? post.likeCount : null,
|
likes: post.likeCount != null ? post.dislikeCount : null,
|
||||||
|
dislikes: post.dislikeCount != null ? post.likeCount : null,
|
||||||
comments: post.commentCount != null ? post.commentCount : null,
|
comments: post.commentCount != null ? post.commentCount : null,
|
||||||
attachment: post.hasAttachment || false,
|
attachment: post.hasAttachment || false,
|
||||||
|
content: post.plainContent || '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 페이지네이션 정보 업데이트
|
// 페이지네이션 정보 업데이트
|
||||||
@ -170,9 +165,10 @@ const fetchNoticePosts = async () => {
|
|||||||
noticeList.value = response.data.data.map((post, index) => ({
|
noticeList.value = response.data.data.map((post, index) => ({
|
||||||
...post,
|
...post,
|
||||||
id: post.id || index,
|
id: post.id || index,
|
||||||
img: post.img || null,
|
img: post.firstImageUrl || null,
|
||||||
views: post.cnt || 0,
|
views: post.cnt || 0,
|
||||||
attachment: post.hasAttachment || false,
|
attachment: post.hasAttachment || false,
|
||||||
|
content: post.plainContent || '',
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
console.error("데이터 오류:", response.data);
|
console.error("데이터 오류:", response.data);
|
||||||
|
|||||||
@ -5,29 +5,75 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<!-- 프로필 헤더 -->
|
<!-- 프로필 헤더 -->
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<BoardProfile :boardId="currentBoardId" :profileName="profileName" />
|
<BoardProfile
|
||||||
|
:boardId="currentBoardId"
|
||||||
|
:profileName="profileName"
|
||||||
|
:views="views"
|
||||||
|
:likes="likes"
|
||||||
|
:dislikes="dislikes"
|
||||||
|
class="pb-6 border-bottom"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 게시글 내용 -->
|
<!-- 게시글 내용 -->
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="mb-4">{{ boardTitle }}</h5>
|
|
||||||
<!-- HTML 콘텐츠 렌더링 -->
|
|
||||||
<div class="board-content text-body" style="line-height: 1.6;" v-html="$common.contentToHtml(boardContent)">
|
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center flex-wrap mb-6 gap-2">
|
||||||
|
<!-- 제목 섹션 -->
|
||||||
|
<div class="me-1">
|
||||||
|
<h5 class="mb-4">{{ boardTitle }}</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 첨부파일 섹션 -->
|
||||||
|
<div v-if="attachments" class="btn-group">
|
||||||
|
<button type="button" class="btn btn-label-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fa-solid fa-download me-2"></i>
|
||||||
|
첨부파일
|
||||||
|
<!-- (<span class="attachment-num">{{ dropdownItems.length }}</span>) -->
|
||||||
|
</button>
|
||||||
|
<!-- <ul class="dropdown-menu">
|
||||||
|
<li v-for="(item, index) in dropdownItems" :key="index">
|
||||||
|
<a class="dropdown-item" href="javascript:void(0);">
|
||||||
|
{{ item.label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- HTML 콘텐츠 렌더링 -->
|
||||||
|
<div class="board-content text-body" style="line-height: 1.6;" v-html="$common.contentToHtml(boardContent)"></div>
|
||||||
|
|
||||||
|
<!-- 좋아요 버튼 -->
|
||||||
|
<div class="row justify-content-center my-10">
|
||||||
|
<BoardRecommendBtn
|
||||||
|
:bigBtn="true"
|
||||||
|
:boardId="currentBoardId"
|
||||||
|
:commentId="null"
|
||||||
|
:likeCount="currentLikeCount"
|
||||||
|
:dislikeCount="currentDislikeCount"
|
||||||
|
@updateReaction="handleUpdateReaction"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 첨부파일 목록 -->
|
<!-- 첨부파일 목록 -->
|
||||||
<ul v-if="attachments.length" class="attachments mt-4 list-unstyled">
|
<!-- <ul v-if="attachments.length" class="attachments mt-4 list-unstyled">
|
||||||
<li v-for="(attachment, index) in attachments" :key="index" class="mb-2">
|
<li v-for="(attachment, index) in attachments" :key="index" class="mb-2">
|
||||||
<a :href="attachment.url" target="_blank" class="text-decoration-none">{{ attachment.name }}</a>
|
<a :href="attachment.url" target="_blank" class="text-decoration-none">{{ attachment.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul> -->
|
||||||
|
|
||||||
<!-- 댓글 영역 -->
|
<!-- 댓글 영역 -->
|
||||||
<BoardComentArea :comments="comments" />
|
<BoardComentArea :comments="comments" />
|
||||||
</div>
|
|
||||||
<!-- 수정 버튼 -->
|
<!-- 수정 버튼 -->
|
||||||
<div class="card-footer d-flex justify-content-end">
|
<!-- <button class="btn btn-primary" @click="goToEditPage">
|
||||||
<button class="btn btn-primary" @click="goToEditPage">
|
|
||||||
글 수정
|
글 수정
|
||||||
</button>
|
</button> -->
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<BoardCommentList/>
|
||||||
|
|
||||||
|
<Pagination/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -38,18 +84,23 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import BoardComentArea from '@c/board/BoardComentArea.vue';
|
import BoardComentArea from '@c/board/BoardComentArea.vue';
|
||||||
import BoardProfile from '@c/board/BoardProfile.vue';
|
import BoardProfile from '@c/board/BoardProfile.vue';
|
||||||
|
import BoardCommentList from '@/components/board/BoardCommentList.vue';
|
||||||
|
import BoardRecommendBtn from '@/components/button/BoardRecommendBtn.vue';
|
||||||
|
import Pagination from '@/components/pagination/Pagination.vue';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
import Quill from 'quill';
|
|
||||||
|
|
||||||
// 게시물 데이터 상태
|
// 게시물 데이터 상태
|
||||||
const profileName = ref('익명 사용자');
|
const profileName = ref('익명 사용자');
|
||||||
const boardTitle = ref('제목 없음');
|
const boardTitle = ref('제목 없음');
|
||||||
const boardContent = ref('');
|
const boardContent = ref('');
|
||||||
const convertedContent = ref('내용 없음');
|
|
||||||
const comments = ref([]);
|
const comments = ref([]);
|
||||||
const attachments = ref([]);
|
const attachments = ref([]);
|
||||||
|
const views = ref(0);
|
||||||
|
const likes = ref(0);
|
||||||
|
const dislikes = ref(0);
|
||||||
|
const attachment = ref(false);
|
||||||
|
|
||||||
// 라우트에서 ID 가져오기
|
// 라우트에서 ID 가져오기
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -69,19 +120,37 @@ const fetchBoardDetails = async () => {
|
|||||||
|
|
||||||
// API 응답 데이터 반영
|
// API 응답 데이터 반영
|
||||||
const boardDetail = data.boardDetail || {};
|
const boardDetail = data.boardDetail || {};
|
||||||
profileName.value = boardDetail.author || '익명 사용자';
|
// console.log('boardDetail:', boardDetail);
|
||||||
boardTitle.value = boardDetail.title || '제목 없음';
|
|
||||||
boardContent.value = boardDetail.content || '';
|
|
||||||
|
|
||||||
|
profileName.value = data.author || '익명 사용자';
|
||||||
|
boardTitle.value = data.title || '제목 없음';
|
||||||
|
boardContent.value = data.content || '';
|
||||||
|
views.value = data.cnt || 0;
|
||||||
|
likes.value = data.likeCount || 0;
|
||||||
|
dislikes.value = data.dislikeCount || 0;
|
||||||
|
attachment.value = data.hasAttachment || null;
|
||||||
|
|
||||||
attachments.value = data.attachments || [];
|
attachments.value = data.attachments || [];
|
||||||
comments.value = data.comments || [];
|
comments.value = data.comments || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('게시물 가져오기 오류:', error);
|
|
||||||
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const currentLikeCount = ref(10);
|
||||||
|
const currentDislikeCount = ref(2);
|
||||||
|
|
||||||
|
// 좋아요, 싫어요
|
||||||
|
const handleUpdateReaction = async ({ type, boardId, commentId }) => {
|
||||||
|
try {
|
||||||
|
const cmtId = commentId !== null ? commentId : 0;
|
||||||
|
|
||||||
|
const response = await axios.post(`/board/${boardId}/${cmtId}/reaction`, { type });
|
||||||
|
} catch (error) {
|
||||||
|
alert('반응을 업데이트하는 중 오류 발생');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 데이터 로드
|
// 컴포넌트 마운트 시 데이터 로드
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchBoardDetails();
|
fetchBoardDetails();
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
<label class="col-md-2 col-form-label">카테고리 <span class="text-danger">*</span></label>
|
<label class="col-md-2 col-form-label">카테고리 <span class="text-danger">*</span></label>
|
||||||
<div class="d-flex flex-wrap align-items-center mt-3 ms-1">
|
<div class="d-flex flex-wrap align-items-center mt-3 ms-1">
|
||||||
<div
|
<div
|
||||||
v-for="(categoryName, index) in categoryList"
|
v-for="(category, index) in categoryList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="form-check me-3"
|
class="form-check me-3"
|
||||||
>
|
>
|
||||||
@ -30,18 +30,19 @@
|
|||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="radio"
|
type="radio"
|
||||||
:id="`category-${index}`"
|
:id="`category-${index}`"
|
||||||
:value="index"
|
:value="category.CMNCODVAL"
|
||||||
v-model="category"
|
v-model="categoryValue"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" :for="`category-${index}`">
|
<label class="form-check-label" :for="`category-${index}`">
|
||||||
{{ categoryName }}
|
{{ category.CMNCODNAM }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="invalid-feedback" :class="categoryAlert ? 'display-block' : ''">카테고리를 선택해주세요.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 비밀번호 필드 -->
|
<!-- 비밀번호 필드 -->
|
||||||
<div v-if="category === 1" class="mb-4">
|
<div v-if="categoryValue === 300102" class="mb-4">
|
||||||
<FormInput
|
<FormInput
|
||||||
title="비밀번호"
|
title="비밀번호"
|
||||||
name="pw"
|
name="pw"
|
||||||
@ -94,25 +95,39 @@
|
|||||||
import QEditor from '@c/editor/QEditor.vue';
|
import QEditor from '@c/editor/QEditor.vue';
|
||||||
import FormInput from '@c/input/FormInput.vue';
|
import FormInput from '@c/input/FormInput.vue';
|
||||||
import FormFile from '@c/input/FormFile.vue';
|
import FormFile from '@c/input/FormFile.vue';
|
||||||
import { getCurrentInstance, ref } from 'vue';
|
import { getCurrentInstance, ref, onMounted } from 'vue';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import axios from '@api';
|
import axios from '@api';
|
||||||
|
|
||||||
const categoryList = ['자유', '익명', '공지사항']; // 카테고리 이름
|
const categoryList = ref([]);
|
||||||
const title = ref('');
|
const title = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const category = ref(0); // 기본값 0
|
const categoryValue = ref(null);
|
||||||
const content = ref('');
|
const content = ref('');
|
||||||
const attachFiles = ref(null);
|
const attachFiles = ref(null);
|
||||||
const isFileValid = ref(true); // 파일 유효성 상태 추가
|
const isFileValid = ref(true);
|
||||||
|
|
||||||
const titleAlert = ref(false);
|
const titleAlert = ref(false);
|
||||||
const passwordAlert = ref(false);
|
const passwordAlert = ref(false);
|
||||||
const contentAlert = ref(false);
|
const contentAlert = ref(false);
|
||||||
|
const categoryAlert = ref(false);
|
||||||
const attachFilesAlert = ref(false);
|
const attachFilesAlert = ref(false);
|
||||||
|
|
||||||
const { appContext } = getCurrentInstance();
|
const { appContext } = getCurrentInstance();
|
||||||
const $common = appContext.config.globalProperties.$common; // $common에 접근
|
const $common = appContext.config.globalProperties.$common;
|
||||||
|
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('board/categories');
|
||||||
|
categoryList.value = response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('카테고리 불러오기 오류:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchCategories();
|
||||||
|
});
|
||||||
|
|
||||||
const goList = () => {
|
const goList = () => {
|
||||||
router.push('/board');
|
router.push('/board');
|
||||||
@ -120,10 +135,11 @@ const goList = () => {
|
|||||||
|
|
||||||
const write = async () => {
|
const write = async () => {
|
||||||
titleAlert.value = !title.value;
|
titleAlert.value = !title.value;
|
||||||
passwordAlert.value = category.value === 1 && !password.value;
|
passwordAlert.value = categoryValue.value === 300102 && !password.value;
|
||||||
contentAlert.value = !content.value;
|
contentAlert.value = !content.value;
|
||||||
|
categoryAlert.value = !categoryValue.value;
|
||||||
|
|
||||||
if (titleAlert.value || passwordAlert.value || contentAlert.value || !isFileValid.value) {
|
if (titleAlert.value || passwordAlert.value || contentAlert.value || categoryAlert.value || !isFileValid.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +147,8 @@ const write = async () => {
|
|||||||
const boardData = {
|
const boardData = {
|
||||||
LOCBRDTTL: title.value,
|
LOCBRDTTL: title.value,
|
||||||
LOCBRDCON: $common.deltaAsJson(content.value),
|
LOCBRDCON: $common.deltaAsJson(content.value),
|
||||||
LOCBRDPWD: category.value === 1 ? password.value : null,
|
LOCBRDPWD: categoryValue.value === 300102 ? password.value : null,
|
||||||
LOCBRDTYP: category.value === 1 ? 'S' : 'F',
|
LOCBRDTYP: categoryValue.value
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: boardResponse } = await axios.post('board', boardData);
|
const { data: boardResponse } = await axios.post('board', boardData);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user