게시판 수정 업데이트

This commit is contained in:
dyhj625 2025-01-14 16:08:33 +09:00
parent a093a1b394
commit d87a87450a
7 changed files with 187 additions and 176 deletions

View File

@ -48,6 +48,7 @@ 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();
@ -71,8 +72,8 @@ defineProps({
default: false, default: false,
}, },
}); });
const boardId = 80; //test
const boardId = 100; //!!
// //
const handleEdit = () => { const handleEdit = () => {
router.push({ name: 'BoardEdit', params: { id: boardId } }); router.push({ name: 'BoardEdit', params: { id: boardId } });
@ -91,6 +92,7 @@ const handleDelete = async () => {
} }
} }
}; };
</script> </script>
<style scoped> <style scoped>

View File

@ -5,22 +5,26 @@
<span :class="isEssential ? 'text-red' : 'none'">*</span> <span :class="isEssential ? 'text-red' : 'none'">*</span>
</label> </label>
<div class="col-md-10"> <div class="col-md-10">
<input :id="name" <input
:id="name"
class="form-control" class="form-control"
:type="type" :type="type"
@input="updateInput" v-model="inputValue"
:value="value"
:maxLength="maxlength" :maxLength="maxlength"
:placeholder="title" /> :placeholder="title"
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">{{ title }} 확인해주세요.</div> />
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">
{{ title }} 확인해주세요.
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref, watch } from 'vue';
const prop = defineProps({ // Props
const props = defineProps({
title: { title: {
type: String, type: String,
default: '라벨', default: '라벨',
@ -34,39 +38,42 @@ const prop = defineProps({
isEssential: { isEssential: {
type: Boolean, type: Boolean,
default: false, default: false,
required: false,
}, },
type: { type: {
type: String, type: String,
default: 'text', default: 'text',
required: false,
}, },
value: { modelValue: {
type: String, type: String,
default: '', default: '',
require: false,
}, },
maxlength: { maxlength: {
type: Number, type: Number,
default: 30, default: 30,
required: false,
}, },
isAlert: { isAlert: {
type: Boolean, type: Boolean,
default: false, default: false,
required: false, },
}
}); });
const emits = defineEmits(['update:data']) // Emits
const emits = defineEmits(['update:modelValue']);
const updateInput = function (event) { // `inputValue`
//Type Number maxlength const inputValue = ref(props.modelValue);
if (event.target.value.length > prop.maxlength) {
event.target.value = event.target.value.slice(0, prop.maxlength); //
watch(inputValue, (newValue) => {
emits('update:modelValue', newValue);
});
//
watch(() => props.modelValue, (newValue) => {
if (inputValue.value !== newValue) {
inputValue.value = newValue;
} }
emits('update:data', event.target.value); });
};
</script> </script>
<style> <style>

View File

@ -1,14 +1,10 @@
<template> <template>
<div class="container mt-4"> <div class="card mb-3 shadow-sm">
<div v-if="posts.length === 0" class="text-center">
게시물이 없습니다.
</div>
<div v-for="post in posts" :key="post.id" class="card mb-3 shadow-sm">
<div class="row g-0"> <div class="row g-0">
<!-- 이미지 섹션 --> <!-- 이미지 섹션 -->
<div v-if="post.img" class="col-md-2"> <div v-if="img" class="col-md-2">
<img <img
:src="post.img" :src="img"
alt="이미지" alt="이미지"
class="img-fluid rounded-start" class="img-fluid rounded-start"
style="object-fit: cover; height: 100%; width: 100%;" style="object-fit: cover; height: 100%; width: 100%;"
@ -19,22 +15,22 @@
<div class="card-body"> <div class="card-body">
<!-- 태그 --> <!-- 태그 -->
<h6 class="badge rounded-pill bg-primary text-white mb-2"> <h6 class="badge rounded-pill bg-primary text-white mb-2">
{{ post.category }} {{ category }}
</h6> </h6>
<!-- 제목 --> <!-- 제목 -->
<h5 class="card-title">{{ post.title }}</h5> <h5 class="card-title">{{ title }}</h5>
<!-- 본문 --> <!-- 본문 -->
<p class="card-text str_wrap">{{ post.content }}</p> <p class="card-text str_wrap">{{ content }}</p>
<!-- 날짜 --> <!-- 날짜 -->
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<small class="text-muted">{{ formatDate(post.date) }}</small> <small class="text-muted">{{ formatDate(date) }}</small>
<!-- 좋아요와 댓글 --> <!-- 좋아요와 댓글 -->
<div> <div>
<span class="text-muted me-3"> <span class="text-muted me-3">
<i class="bx bx-like"></i> {{ post.likes || 0 }} <i class="bx bx-like"></i> {{ likes || 0 }}
</span> </span>
<span class="text-muted"> <span class="text-muted">
<i class="bx bx-comment"></i> {{ post.comments || 0 }} <i class="bx bx-comment"></i> {{ comments || 0 }}
</span> </span>
</div> </div>
</div> </div>
@ -42,35 +38,41 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import axios from "@api";
export default { export default {
data() { props: {
return { img: {
posts: [], // type: String,
}; default: null,
},
category: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
date: {
type: String,
required: true,
},
likes: {
type: Number,
default: 0,
},
comments: {
type: Number,
default: 0,
},
}, },
methods: { methods: {
async fetchPosts() {
try {
const response = await axios.get("board/general");
if (response.data && Array.isArray(response.data.data)) {
console.log("API Response:",response.data);
this.posts = response.data.data.map((post) => ({
...post,
img: post.img || null, //
likes: post.likes || 0, //
comments: post.comments || 0, //
}));
}
} catch (error) {
console.error("Failed to fetch posts:", error);
}
},
formatDate(dateString) { formatDate(dateString) {
const date = new Date(dateString); const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String( return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
@ -80,9 +82,6 @@ export default {
).padStart(2, "0")}`; ).padStart(2, "0")}`;
}, },
}, },
mounted() {
this.fetchPosts();
},
}; };
</script> </script>

View File

@ -0,0 +1,37 @@
<template>
<div class="container mt-4">
<div v-if="posts.length === 0" class="text-center">
게시물이 없습니다.
</div>
<div v-for="post in posts" :key="post.id">
<BoardCard
:img="post.img"
:category="post.category"
:title="post.title"
:content="post.content"
:date="post.date"
:likes="post.likes"
:comments="post.comments"
/>
</div>
</div>
</template>
<script>
import BoardCard from './BoardCard.vue';
export default {
components: {
BoardCard,
},
props: {
posts: {
type: Array,
required: true,
},
},
};
</script>
<style>
</style>

View File

@ -50,7 +50,6 @@
<script setup> <script setup>
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 { 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';
@ -58,31 +57,33 @@ import axios from '@api';
// //
const title = ref(''); const title = ref('');
const content = ref(''); const content = ref('');
const attachFiles = ref([]);
// //
const titleAlert = ref(false); const titleAlert = ref(false);
const contentAlert = ref(false); const contentAlert = ref(false);
const attachFilesAlert = ref(false);
// //
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const currentBoardId = ref(Number(route.params.id)); const currentBoardId = ref(route.params.id); // ID
console.log(currentBoardId.value)
// //
const fetchBoardDetails = async () => { const fetchBoardDetails = async () => {
try { try {
const response = await axios.get(`board/${currentBoardId.value}`); const response = await axios.get(`board/${currentBoardId.value}`);
const data = response.data.data.boardDetail; const data = response.data.data.boardDetail;
// if (!data) {
title.value = data.title || ''; console.error('API에서 게시물 데이터를 반환하지 않았습니다.');
content.value = data.content || ''; return;
console.log('게시물 데이터 로드 완료:', { title: title.value, content: content.value }); }
//
title.value = data.title || '제목 없음';
content.value = data.content || '내용 없음';
} catch (error) { } catch (error) {
console.error('게시물 가져오기 오류:', error.response || error.message); console.error('게시물 가져오기 오류:', error.response || error.message);
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
} }
}; };
@ -115,19 +116,6 @@ const updateBoard = async () => {
await axios.put(`board/${currentBoardId.value}`, boardData); await axios.put(`board/${currentBoardId.value}`, boardData);
//
if (attachFiles.value && attachFiles.value.length > 0) {
for (const file of attachFiles.value) {
const formData = new FormData();
formData.append('file', file);
await axios.post(`board/${currentBoardId.value}/attachments`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
}
alert('게시물이 수정되었습니다.'); alert('게시물이 수정되었습니다.');
goList(); goList();
} catch (error) { } catch (error) {
@ -151,4 +139,8 @@ onMounted(() => {
color: red; color: red;
text-align: center; text-align: center;
} }
.display-block {
display: block;
}
</style> </style>

View File

@ -8,22 +8,7 @@
</router-link> </router-link>
</div> </div>
<!-- 게시판 리스트 --> <board-card :posts="filteredList" @click="goDetail" />
<template v-for="(item, index) in filteredList" :key="item.id">
<board-card
:title="item.title"
:content="item.content"
:img="item.img"
:date="item.date"
@click="goDetail(item.id)"
>
<template #badgeType>
<span v-if="item.type === 1" class="badge rounded-pill bg-label-danger">공지</span>
<span v-else-if="item.type === 2" class="badge rounded-pill bg-label-primary">자유</span>
<span v-else-if="item.type === 3" class="badge rounded-pill bg-label-success">익명</span>
</template>
</board-card>
</template>
<div class="mt-8"> <div class="mt-8">
<pagination /> <pagination />
@ -33,16 +18,16 @@
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, onMounted } from 'vue';
import BoardCard from '@c/list/BoardCard.vue'; import BoardCard 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 dummy from '@a/boardDummy.json';
import WriteButton from '@c/button/WriteBtn.vue'; import WriteButton from '@c/button/WriteBtn.vue';
import axios from '@api';
// //
const list = ref(dummy); const list = ref([]);
const searchText = ref(''); const searchText = ref('');
// //
@ -61,6 +46,29 @@ const filteredList = computed(() =>
item.title.toLowerCase().includes(searchText.value.toLowerCase()) item.title.toLowerCase().includes(searchText.value.toLowerCase())
) )
); );
//
const fetchPosts = async () => {
try {
const response = await axios.get("board/general");
if (response.data && Array.isArray(response.data.data)) {
list.value = response.data.data.map((post) => ({
...post,
img: post.img || null,
likes: post.likes || 0,
comments: post.comments || 0,
}));
}
} catch (error) {
console.error("Failed to fetch posts:", error);
}
};
//
onMounted(() => {
fetchPosts();
});
</script> </script>
<style> <style>

View File

@ -21,9 +21,14 @@
<!-- 댓글 영역 --> <!-- 댓글 영역 -->
<BoardComentArea :comments="comments" /> <BoardComentArea :comments="comments" />
</div> </div>
<!-- 댓글 입력 --> <!-- 수정 버튼 -->
<div class="card-footer"> <div class="card-footer d-flex justify-content-end">
<BoardComment @submitComment="addComment" /> <button
class="btn btn-primary"
@click="goToEditPage"
>
수정
</button>
</div> </div>
</div> </div>
</div> </div>
@ -33,10 +38,9 @@
<script setup> <script setup>
import BoardComentArea from '@c/board/BoardComentArea.vue'; import BoardComentArea from '@c/board/BoardComentArea.vue';
import BoardComment from '@c/board/BoardComment.vue';
import BoardProfile from '@c/board/BoardProfile.vue'; import BoardProfile from '@c/board/BoardProfile.vue';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import axios from '@api'; import axios from '@api';
// //
@ -48,22 +52,13 @@ const attachments = ref([]);
// ID // ID
const route = useRoute(); const route = useRoute();
const router = useRouter();
const currentBoardId = ref(Number(route.params.id)); const currentBoardId = ref(Number(route.params.id));
console.log(currentBoardId.value) console.log(currentBoardId.value)
// //
const addComment = async (newComment) => { const goToEditPage = () => {
try { router.push({ name: 'BoardEdit', params: { id: currentBoardId.value } });
const requestData = {
content: newComment,
};
const response = await axios.post(`board/${currentBoardId.value}/comment`, requestData);
comments.value.push(response.data);
alert('댓글이 추가되었습니다.');
} catch (error) {
console.error('댓글 추가 오류:', error);
alert('댓글 추가 중 오류가 발생했습니다.');
}
}; };
// //
@ -91,32 +86,3 @@ onMounted(() => {
fetchBoardDetails(); fetchBoardDetails();
}); });
</script> </script>
<style>
/* 카드 스타일 */
.card {
margin-bottom: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 게시글 내용 스타일 */
.board-content {
white-space: pre-wrap; /* 줄바꿈 및 공백 유지 */
font-size: 16px;
line-height: 1.6;
}
/* 첨부파일 목록 스타일 */
.attachments {
padding-left: 20px;
list-style: disc;
margin-top: 1rem;
}
/* 댓글 섹션 스타일 */
.card-footer {
border-top: 1px solid #e6e6e6;
background-color: #f9f9f9;
}
</style>