diff --git a/src/api/boardApi.js b/src/api/boardApi.js index bc68a89..0b97b62 100644 --- a/src/api/boardApi.js +++ b/src/api/boardApi.js @@ -53,4 +53,11 @@ export const boardApi = { unblock(id) { return http.post(`/board/posts/${id}/unblock`) }, + // 공지 설정/해제 (관리자) — 목록 최상단 고정 + setNotice(id) { + return http.post(`/board/posts/${id}/notice`) + }, + unsetNotice(id) { + return http.post(`/board/posts/${id}/unnotice`) + }, } diff --git a/src/views/board/BoardDetailView.vue b/src/views/board/BoardDetailView.vue index 34b3dcf..e3cb173 100644 --- a/src/views/board/BoardDetailView.vue +++ b/src/views/board/BoardDetailView.vue @@ -80,6 +80,15 @@ async function unblockPost() { } } +async function setNoticePost(on) { + try { + await (on ? boardApi.setNotice(postId) : boardApi.unsetNotice(postId)) + await load() + } catch (e) { + alert(e.response?.data?.message || '처리에 실패했습니다.') + } +} + async function addComment() { const content = commentText.value.trim() if (!content) return @@ -115,7 +124,7 @@ onMounted(load)
-

{{ post.title }}

+

공지{{ post.title }}

{{ post.authorName }} · {{ formatRelative(post.createdAt) }} @@ -144,6 +153,9 @@ onMounted(load)
+ + + @@ -268,6 +280,20 @@ button.warn { border-color: #e67e22; color: #e67e22; } +button.notice-btn { + border-color: hsla(160, 100%, 37%, 1); + color: hsla(160, 100%, 37%, 1); +} +.notice-badge { + margin-right: 0.4rem; + padding: 0.1rem 0.45rem; + border-radius: 4px; + background: hsla(160, 100%, 37%, 1); + color: #fff; + font-size: 0.8rem; + font-weight: 700; + vertical-align: middle; +} .actions { display: flex; justify-content: space-between; diff --git a/src/views/board/BoardListView.vue b/src/views/board/BoardListView.vue index d09a562..3078767 100644 --- a/src/views/board/BoardListView.vue +++ b/src/views/board/BoardListView.vue @@ -163,8 +163,9 @@ onMounted(loadTags) - + + 공지 @@ -318,6 +319,22 @@ button:disabled { color: #c0392b; font-size: 0.72rem; } +.notice-badge { + margin-right: 0.4rem; + padding: 0.05rem 0.4rem; + border-radius: 3px; + background: hsla(160, 100%, 37%, 1); + color: #fff; + font-size: 0.72rem; + font-weight: 700; + vertical-align: middle; +} +.notice-row { + background: hsla(160, 100%, 37%, 0.06); +} +.notice-row .title { + font-weight: 600; +} /* 하단 영역: 페이징(가운데) + 돋보기(오른쪽) 같은 행 */ .list-footer { diff --git a/src/views/board/BoardWriteView.vue b/src/views/board/BoardWriteView.vue index 277a6d6..9c3e058 100644 --- a/src/views/board/BoardWriteView.vue +++ b/src/views/board/BoardWriteView.vue @@ -2,19 +2,25 @@ import { onMounted, ref, computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { boardApi } from '@/api/boardApi' +import { useAuthStore } from '@/stores/auth' import MarkdownEditor from '@/components/editor/MarkdownEditor.vue' import IconBtn from '@/components/ui/IconBtn.vue' import { DEFAULT_BOARD } from '@/constants/boards' const route = useRoute() const router = useRouter() +const auth = useAuthStore() const category = route.params.category || DEFAULT_BOARD const editId = route.params.id || null const isEdit = computed(() => !!editId) +// 공지: 관리자가 커뮤니티 게시판에 새 글 작성할 때만 설정 가능 +const isAdmin = computed(() => auth.user?.role === 'ADMIN') +const canNotice = computed(() => isAdmin.value && category === 'community' && !isEdit.value) const title = ref('') const content = ref('') +const notice = ref(false) const tagGroups = ref([]) const selectedTagIds = ref([]) const loading = ref(false) @@ -65,6 +71,7 @@ async function submit() { title: title.value.trim(), content: content.value, category, + notice: canNotice.value ? notice.value : false, tagIds: selectedTagIds.value, } try { @@ -97,6 +104,12 @@ onMounted(async () => {
+ + +
@@ -178,6 +191,17 @@ h1 { font-size: 0.85rem; opacity: 0.65; } +.notice-check { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.9rem; + cursor: pointer; +} +.notice-check input { + width: auto; + cursor: pointer; +} .buttons { display: flex; justify-content: flex-end;