Files
sb-front/src/views/board/MyBoardView.vue
T
ByungCheol 29ff09a8d6
CI / build (push) Failing after 12m7s
feat: 내 글·댓글 모아보기 화면 + 사이드바 메뉴
- MyBoardView(탭: 내 글/내 댓글, 페이징, 글로 이동), /my-board 라우트
- 사이드바 게시판 영역에 내 글·댓글 링크, 헤더 타이틀

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:26:16 +09:00

210 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { boardApi } from '@/api/boardApi'
import { formatRelative } from '@/utils/datetime'
import { boardLabel } from '@/constants/boards'
const router = useRouter()
const tab = ref('posts') // posts | comments
const items = ref([])
const page = ref(1)
const totalPages = ref(0)
const loading = ref(false)
const error = ref(null)
async function load() {
loading.value = true
error.value = null
try {
const res = tab.value === 'posts'
? await boardApi.myPosts({ page: page.value, size: 10 })
: await boardApi.myComments({ page: page.value, size: 10 })
items.value = res.content
totalPages.value = res.totalPages
} catch (e) {
error.value = e.response?.data?.message || '불러오지 못했습니다.'
} finally {
loading.value = false
}
}
function switchTab(t) {
if (tab.value === t) return
tab.value = t
page.value = 1
}
function goPage(p) {
if (p < 1 || p > totalPages.value || p === page.value) return
page.value = p
}
function openPost(category, postId) {
router.push(`/board/${category}/${postId}`)
}
watch([tab, page], load)
onMounted(load)
</script>
<template>
<section class="myboard">
<div class="tabs">
<button type="button" class="tab" :class="{ active: tab === 'posts' }" @click="switchTab('posts')"> </button>
<button type="button" class="tab" :class="{ active: tab === 'comments' }" @click="switchTab('comments')"> 댓글</button>
</div>
<p v-if="error" class="msg error">{{ error }}</p>
<p v-if="loading" class="msg">불러오는 중...</p>
<template v-else>
<ul v-if="items.length" class="list">
<!-- -->
<template v-if="tab === 'posts'">
<li v-for="p in items" :key="p.id" class="item" @click="openPost(p.category, p.id)">
<div class="line1">
<span class="badge">{{ boardLabel(p.category) }}</span>
<span class="title">{{ p.title }}</span>
</div>
<div class="line2">
<span>{{ formatRelative(p.createdAt) }}</span>
<span>· 조회 {{ p.viewCount }}</span>
<span>· 추천 {{ p.upCount || 0 }}</span>
<span v-if="p.commentCount">· 댓글 {{ p.commentCount }}</span>
</div>
</li>
</template>
<!-- 댓글 -->
<template v-else>
<li v-for="c in items" :key="c.id" class="item" @click="openPost(c.category, c.postId)">
<div class="line1">
<span class="badge">{{ boardLabel(c.category) }}</span>
<span class="title">{{ c.postTitle }}</span>
</div>
<div class="comment-text">{{ c.content }}</div>
<div class="line2"><span>{{ formatRelative(c.createdAt) }}</span></div>
</li>
</template>
</ul>
<p v-else class="msg">{{ tab === 'posts' ? '작성한 글이 없습니다.' : '작성한 댓글이 없습니다.' }}</p>
<nav v-if="totalPages > 1" class="pagination">
<button type="button" :disabled="page <= 1" @click="goPage(page - 1)"></button>
<span class="page-info">{{ page }} / {{ totalPages }}</span>
<button type="button" :disabled="page >= totalPages" @click="goPage(page + 1)"></button>
</nav>
</template>
</section>
</template>
<style scoped>
.myboard {
max-width: 800px;
margin: 0 auto;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.tab {
flex: 1;
padding: 0.6rem 0;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-background-soft);
color: var(--color-text);
cursor: pointer;
font-weight: 600;
}
.tab.active {
border-color: hsla(160, 100%, 37%, 1);
color: hsla(160, 100%, 37%, 1);
background: hsla(160, 100%, 37%, 0.08);
}
.list {
list-style: none;
padding: 0;
margin: 0;
}
.item {
padding: 0.85rem 0.5rem;
border-bottom: 1px solid var(--color-border);
cursor: pointer;
}
.item:hover {
background: var(--color-background-soft);
}
.line1 {
display: flex;
align-items: center;
gap: 0.45rem;
}
.badge {
flex: none;
font-size: 0.7rem;
padding: 0.08rem 0.4rem;
border-radius: 4px;
background: var(--color-background-mute);
color: var(--color-text);
opacity: 0.8;
}
.title {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.comment-text {
margin-top: 0.3rem;
font-size: 0.9rem;
opacity: 0.85;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line2 {
margin-top: 0.3rem;
font-size: 0.8rem;
opacity: 0.6;
display: flex;
gap: 0.35rem;
flex-wrap: wrap;
}
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-top: 1.25rem;
}
.pagination button {
width: 36px;
height: 36px;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-background-soft);
color: var(--color-text);
cursor: pointer;
font-size: 1.1rem;
}
.pagination button:disabled {
opacity: 0.4;
cursor: default;
}
.page-info {
font-size: 0.9rem;
}
.msg {
margin: 1.5rem 0;
text-align: center;
opacity: 0.7;
}
.msg.error {
color: #c0392b;
}
</style>