게시판 수정 업데이트
This commit is contained in:
parent
a093a1b394
commit
d87a87450a
@ -48,6 +48,7 @@ import axios from '@api';
|
||||
import DeleteButton from '../button/DeleteBtn.vue';
|
||||
import EditButton from '../button/EditBtn.vue';
|
||||
import BoardRecommendBtn from '../button/BoardRecommendBtn.vue';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
// Vue Router 인스턴스
|
||||
const router = useRouter();
|
||||
@ -71,8 +72,8 @@ defineProps({
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const boardId = 80; //test용
|
||||
|
||||
const boardId = 100; //수정필요!!
|
||||
// 수정 버튼 핸들러
|
||||
const handleEdit = () => {
|
||||
router.push({ name: 'BoardEdit', params: { id: boardId } });
|
||||
@ -91,6 +92,7 @@ const handleDelete = async () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -5,22 +5,26 @@
|
||||
<span :class="isEssential ? 'text-red' : 'none'">*</span>
|
||||
</label>
|
||||
<div class="col-md-10">
|
||||
<input :id="name"
|
||||
<input
|
||||
:id="name"
|
||||
class="form-control"
|
||||
:type="type"
|
||||
@input="updateInput"
|
||||
:value="value"
|
||||
v-model="inputValue"
|
||||
:maxLength="maxlength"
|
||||
:placeholder="title" />
|
||||
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">{{ title }}을 확인해주세요.</div>
|
||||
:placeholder="title"
|
||||
/>
|
||||
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">
|
||||
{{ title }}을 확인해주세요.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const prop = defineProps({
|
||||
// Props 정의
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '라벨',
|
||||
@ -34,39 +38,42 @@ const prop = defineProps({
|
||||
isEssential: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
require: false,
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
required: false,
|
||||
},
|
||||
isAlert : {
|
||||
isAlert: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:data'])
|
||||
// Emits 정의
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const updateInput = function (event) {
|
||||
//Type Number 일때 maxlength 적용 안됨 방지
|
||||
if (event.target.value.length > prop.maxlength) {
|
||||
event.target.value = event.target.value.slice(0, prop.maxlength);
|
||||
// 로컬 상태로 사용하기 위한 `inputValue`
|
||||
const inputValue = ref(props.modelValue);
|
||||
|
||||
// 부모로 데이터 업데이트
|
||||
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>
|
||||
|
||||
<style>
|
||||
|
||||
@ -1,42 +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" class="card mb-3 shadow-sm">
|
||||
<div class="row g-0">
|
||||
<!-- 이미지 섹션 -->
|
||||
<div v-if="post.img" class="col-md-2">
|
||||
<img
|
||||
:src="post.img"
|
||||
alt="이미지"
|
||||
class="img-fluid rounded-start"
|
||||
style="object-fit: cover; height: 100%; width: 100%;"
|
||||
/>
|
||||
</div>
|
||||
<!-- 게시물 내용 섹션 -->
|
||||
<div class="col-md-10">
|
||||
<div class="card-body">
|
||||
<!-- 태그 -->
|
||||
<h6 class="badge rounded-pill bg-primary text-white mb-2">
|
||||
{{ post.category }}
|
||||
</h6>
|
||||
<!-- 제목 -->
|
||||
<h5 class="card-title">{{ post.title }}</h5>
|
||||
<!-- 본문 -->
|
||||
<p class="card-text str_wrap">{{ post.content }}</p>
|
||||
<!-- 날짜 -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<small class="text-muted">{{ formatDate(post.date) }}</small>
|
||||
<!-- 좋아요와 댓글 -->
|
||||
<div>
|
||||
<span class="text-muted me-3">
|
||||
<i class="bx bx-like"></i> {{ post.likes || 0 }}
|
||||
</span>
|
||||
<span class="text-muted">
|
||||
<i class="bx bx-comment"></i> {{ post.comments || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="row g-0">
|
||||
<!-- 이미지 섹션 -->
|
||||
<div v-if="img" class="col-md-2">
|
||||
<img
|
||||
:src="img"
|
||||
alt="이미지"
|
||||
class="img-fluid rounded-start"
|
||||
style="object-fit: cover; height: 100%; width: 100%;"
|
||||
/>
|
||||
</div>
|
||||
<!-- 게시물 내용 섹션 -->
|
||||
<div class="col-md-10">
|
||||
<div class="card-body">
|
||||
<!-- 태그 -->
|
||||
<h6 class="badge rounded-pill bg-primary text-white mb-2">
|
||||
{{ category }}
|
||||
</h6>
|
||||
<!-- 제목 -->
|
||||
<h5 class="card-title">{{ title }}</h5>
|
||||
<!-- 본문 -->
|
||||
<p class="card-text str_wrap">{{ content }}</p>
|
||||
<!-- 날짜 -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<small class="text-muted">{{ formatDate(date) }}</small>
|
||||
<!-- 좋아요와 댓글 -->
|
||||
<div>
|
||||
<span class="text-muted me-3">
|
||||
<i class="bx bx-like"></i> {{ likes || 0 }}
|
||||
</span>
|
||||
<span class="text-muted">
|
||||
<i class="bx bx-comment"></i> {{ comments || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -46,31 +41,38 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "@api";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
posts: [], // 게시물 데이터 초기화
|
||||
};
|
||||
props: {
|
||||
img: {
|
||||
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: {
|
||||
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) {
|
||||
const date = new Date(dateString);
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(
|
||||
@ -80,9 +82,6 @@ export default {
|
||||
).padStart(2, "0")}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchPosts();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
37
src/components/list/BoardCardList.vue
Normal file
37
src/components/list/BoardCardList.vue
Normal 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>
|
||||
@ -50,7 +50,6 @@
|
||||
<script setup>
|
||||
import QEditor from '@c/editor/QEditor.vue';
|
||||
import FormInput from '@c/input/FormInput.vue';
|
||||
import FormFile from '@c/input/FormFile.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import axios from '@api';
|
||||
@ -58,31 +57,33 @@ import axios from '@api';
|
||||
// 상태 변수
|
||||
const title = ref('');
|
||||
const content = ref('');
|
||||
const attachFiles = ref([]);
|
||||
|
||||
// 경고 상태
|
||||
const titleAlert = ref(false);
|
||||
const contentAlert = ref(false);
|
||||
const attachFilesAlert = ref(false);
|
||||
|
||||
// 라우터
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const currentBoardId = ref(Number(route.params.id));
|
||||
|
||||
const currentBoardId = ref(route.params.id); // 라우트에서 ID 가져오기
|
||||
console.log(currentBoardId.value)
|
||||
// 게시물 데이터 로드
|
||||
const fetchBoardDetails = async () => {
|
||||
try {
|
||||
const response = await axios.get(`board/${currentBoardId.value}`);
|
||||
const data = response.data.data.boardDetail;
|
||||
|
||||
// 데이터 초기화
|
||||
title.value = data.title || '';
|
||||
content.value = data.content || '';
|
||||
console.log('게시물 데이터 로드 완료:', { title: title.value, content: content.value });
|
||||
if (!data) {
|
||||
console.error('API에서 게시물 데이터를 반환하지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 데이터 설정
|
||||
title.value = data.title || '제목 없음';
|
||||
content.value = data.content || '내용 없음';
|
||||
|
||||
} catch (error) {
|
||||
console.error('게시물 가져오기 오류:', error.response || error.message);
|
||||
alert('게시물 데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
@ -115,19 +116,6 @@ const updateBoard = async () => {
|
||||
|
||||
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('게시물이 수정되었습니다.');
|
||||
goList();
|
||||
} catch (error) {
|
||||
@ -151,4 +139,8 @@ onMounted(() => {
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.display-block {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -8,22 +8,7 @@
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- 게시판 리스트 -->
|
||||
<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>
|
||||
<board-card :posts="filteredList" @click="goDetail" />
|
||||
|
||||
<div class="mt-8">
|
||||
<pagination />
|
||||
@ -33,16 +18,16 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import BoardCard from '@c/list/BoardCard.vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import BoardCard from '@/components/list/BoardCardList.vue';
|
||||
import Pagination from '@c/pagination/Pagination.vue';
|
||||
import SearchBar from '@c/search/SearchBar.vue';
|
||||
import router from '@/router';
|
||||
import dummy from '@a/boardDummy.json';
|
||||
import WriteButton from '@c/button/WriteBtn.vue';
|
||||
import axios from '@api';
|
||||
|
||||
// 데이터 초기화
|
||||
const list = ref(dummy);
|
||||
const list = ref([]);
|
||||
const searchText = ref('');
|
||||
|
||||
// 상세 페이지 이동
|
||||
@ -61,6 +46,29 @@ const filteredList = computed(() =>
|
||||
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>
|
||||
|
||||
<style>
|
||||
|
||||
@ -21,9 +21,14 @@
|
||||
<!-- 댓글 영역 -->
|
||||
<BoardComentArea :comments="comments" />
|
||||
</div>
|
||||
<!-- 댓글 입력 -->
|
||||
<div class="card-footer">
|
||||
<BoardComment @submitComment="addComment" />
|
||||
<!-- 수정 버튼 -->
|
||||
<div class="card-footer d-flex justify-content-end">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="goToEditPage"
|
||||
>
|
||||
글 수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -33,10 +38,9 @@
|
||||
|
||||
<script setup>
|
||||
import BoardComentArea from '@c/board/BoardComentArea.vue';
|
||||
import BoardComment from '@c/board/BoardComment.vue';
|
||||
import BoardProfile from '@c/board/BoardProfile.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import axios from '@api';
|
||||
|
||||
// 게시물 데이터 상태
|
||||
@ -48,22 +52,13 @@ const attachments = ref([]);
|
||||
|
||||
// 라우트에서 ID 가져오기
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const currentBoardId = ref(Number(route.params.id));
|
||||
console.log(currentBoardId.value)
|
||||
|
||||
// 댓글 추가 함수
|
||||
const addComment = async (newComment) => {
|
||||
try {
|
||||
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('댓글 추가 중 오류가 발생했습니다.');
|
||||
}
|
||||
// 글 수정 페이지로 이동
|
||||
const goToEditPage = () => {
|
||||
router.push({ name: 'BoardEdit', params: { id: currentBoardId.value } });
|
||||
};
|
||||
|
||||
// 게시물 상세 데이터 불러오기
|
||||
@ -91,32 +86,3 @@ onMounted(() => {
|
||||
fetchBoardDetails();
|
||||
});
|
||||
</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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user