- 헤더에 내 아바타(계정정보 링크) + 앱 시작 시 프로필 동기화(재로그인 없이 아바타/포인트) - 목록: 인기 글 상단 배지(🔥), 비추천 20+ 블라인드 표시 - 상세: 본인 글/댓글 추천·비추천 비활성, 비추천 20+ 글/댓글 블라인드(그래도 보기) - 댓글: 인기 댓글 배지, 본문 작성자에 작성자 뱃지, 목록 하단 새로고침 버튼(입력폼 위) - 추천자 목록 가로 줄바꿈(아바타+이름 단위, 이름 안 잘리게) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,8 @@ onMounted(() => {
|
|||||||
window.addEventListener('auth:unauthorized', onUnauthorized)
|
window.addEventListener('auth:unauthorized', onUnauthorized)
|
||||||
window.addEventListener('premium:required', onPremiumRequired)
|
window.addEventListener('premium:required', onPremiumRequired)
|
||||||
ui.loadSignupEnabled() // 회원가입 허용 여부 선로딩(랜딩/로그인 가입 버튼에 반영)
|
ui.loadSignupEnabled() // 회원가입 허용 여부 선로딩(랜딩/로그인 가입 버튼에 반영)
|
||||||
|
// 로그인 상태면 전체 프로필(아바타·포인트) 최신화 — 재로그인 없이 헤더 아바타 반영
|
||||||
|
if (auth.isAuthenticated) auth.refreshProfile()
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('auth:unauthorized', onUnauthorized)
|
window.removeEventListener('auth:unauthorized', onUnauthorized)
|
||||||
|
|||||||
@@ -43,4 +43,8 @@ export const authApi = {
|
|||||||
points() {
|
points() {
|
||||||
return http.get('/auth/points')
|
return http.get('/auth/points')
|
||||||
},
|
},
|
||||||
|
// 현재 사용자 전체 프로필(아바타·포인트 포함) — 재로그인 없이 최신값 동기화
|
||||||
|
profile() {
|
||||||
|
return http.get('/auth/profile')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { RouterLink, useRouter, useRoute } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useUiStore } from '@/stores/ui'
|
import { useUiStore } from '@/stores/ui'
|
||||||
import { boardLabel } from '@/constants/boards'
|
import { boardLabel } from '@/constants/boards'
|
||||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||||
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const ui = useUiStore()
|
const ui = useUiStore()
|
||||||
@@ -55,7 +56,15 @@ async function handleLogout() {
|
|||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<template v-if="auth.isAuthenticated">
|
<template v-if="auth.isAuthenticated">
|
||||||
<span class="username">{{ auth.user?.name || auth.user?.loginId }}님</span>
|
<RouterLink to="/settings/account" class="me" title="계정정보">
|
||||||
|
<UserAvatar
|
||||||
|
:name="auth.user?.name || auth.user?.loginId"
|
||||||
|
:google-picture="auth.user?.googlePicture"
|
||||||
|
:profile-image="auth.user?.profileImage"
|
||||||
|
:size="26"
|
||||||
|
/>
|
||||||
|
<span class="username">{{ auth.user?.name || auth.user?.loginId }}님</span>
|
||||||
|
</RouterLink>
|
||||||
<IconBtn icon="logout" title="로그아웃" @click="handleLogout" />
|
<IconBtn icon="logout" title="로그아웃" @click="handleLogout" />
|
||||||
</template>
|
</template>
|
||||||
<IconBtn v-else icon="login" title="로그인" variant="primary" @click="ui.openLogin()" />
|
<IconBtn v-else icon="login" title="로그인" variant="primary" @click="ui.openLogin()" />
|
||||||
@@ -105,9 +114,24 @@ async function handleLogout() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.me {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.me:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
.username {
|
.username {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
/* 좁은 폰 화면: 이름 숨기고 아바타만 */
|
||||||
|
.username {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.hamburger {
|
.hamburger {
|
||||||
|
|||||||
+12
-1
@@ -105,6 +105,17 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
await persist()
|
await persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 서버에서 전체 프로필(아바타·포인트 포함)을 받아 병합 — 재로그인 없이 최신화
|
||||||
|
async function refreshProfile() {
|
||||||
|
if (!token.value) return
|
||||||
|
try {
|
||||||
|
const me = await authApi.profile()
|
||||||
|
await applyUser(me)
|
||||||
|
} catch {
|
||||||
|
// 실패해도 캐시된 사용자 정보 유지
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function clear() {
|
async function clear() {
|
||||||
exitDemo()
|
exitDemo()
|
||||||
token.value = ''
|
token.value = ''
|
||||||
@@ -112,7 +123,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
await persist()
|
await persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
return { token, user, ready, isAuthenticated, isAdmin, isPremium, restore, login, googleLogin, signup, fetchMe, applyUser, logout, clear }
|
return { token, user, ready, isAuthenticated, isAdmin, isPremium, restore, login, googleLogin, signup, fetchMe, applyUser, refreshProfile, logout, clear }
|
||||||
})
|
})
|
||||||
|
|
||||||
function safeParse(value) {
|
function safeParse(value) {
|
||||||
|
|||||||
@@ -39,6 +39,26 @@ function canModifyComment(c) {
|
|||||||
return auth.user.id === c.authorId || isAdmin.value
|
return auth.user.id === c.authorId || isAdmin.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 본인 글/댓글 여부 (추천/비추천 차단용)
|
||||||
|
const isMyPost = computed(() => !!post.value && auth.user?.id === post.value.authorId)
|
||||||
|
function isMyComment(c) {
|
||||||
|
return auth.user?.id === c.authorId
|
||||||
|
}
|
||||||
|
// 본문 작성자가 단 댓글인지 (작성자 뱃지)
|
||||||
|
function isAuthorComment(c) {
|
||||||
|
return !!post.value && c.authorId === post.value.authorId
|
||||||
|
}
|
||||||
|
// 비추천 20개 이상 블라인드 + 펼치기 상태
|
||||||
|
const BLIND_DOWN = 20
|
||||||
|
function isBlinded(item) {
|
||||||
|
return (item?.downCount || 0) >= BLIND_DOWN
|
||||||
|
}
|
||||||
|
const postRevealed = ref(false)
|
||||||
|
const revealedComments = ref({})
|
||||||
|
function toggleReveal(c) {
|
||||||
|
revealedComments.value[c.id] = !revealedComments.value[c.id]
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
@@ -106,6 +126,21 @@ async function removeComment(c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 댓글 목록만 새로고침 (다른 사람이 단 댓글·추천 반영)
|
||||||
|
const refreshingComments = ref(false)
|
||||||
|
async function refreshComments() {
|
||||||
|
if (refreshingComments.value) return
|
||||||
|
refreshingComments.value = true
|
||||||
|
try {
|
||||||
|
const fresh = await boardApi.get(postId)
|
||||||
|
post.value.comments = fresh.comments
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.response?.data?.message || '댓글을 새로고침하지 못했습니다.')
|
||||||
|
} finally {
|
||||||
|
refreshingComments.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 추천/비추천 =====
|
// ===== 추천/비추천 =====
|
||||||
const voting = ref(false)
|
const voting = ref(false)
|
||||||
async function votePost(type) {
|
async function votePost(type) {
|
||||||
@@ -178,17 +213,27 @@ onMounted(load)
|
|||||||
🚫 관리자에 의해 열람이 제한된 게시글입니다.
|
🚫 관리자에 의해 열람이 제한된 게시글입니다.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 비추천 많은 글 블라인드 (작성자·관리자는 정상 표시) -->
|
||||||
|
<div v-else-if="isBlinded(post) && !isMyPost && !isAdmin && !postRevealed" class="blinded">
|
||||||
|
🙈 비추천이 많아 블라인드 처리된 게시글입니다.
|
||||||
|
<button type="button" class="reveal-btn" @click="postRevealed = true">그래도 보기</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MarkdownViewer v-else :content="post.content" class="content" />
|
<MarkdownViewer v-else :content="post.content" class="content" />
|
||||||
|
|
||||||
<!-- 추천 / 비추천 -->
|
<!-- 추천 / 비추천 (본인 글은 비활성) -->
|
||||||
<div v-if="!restricted" class="vote-bar">
|
<div v-if="!restricted" class="vote-bar">
|
||||||
<button
|
<button
|
||||||
type="button" class="vote-btn up" :class="{ active: post.myVote === 'UP' }"
|
type="button" class="vote-btn up" :class="{ active: post.myVote === 'UP' }"
|
||||||
:disabled="voting" @click="votePost('UP')"
|
:disabled="voting || isMyPost"
|
||||||
|
:title="isMyPost ? '본인 글은 추천할 수 없습니다' : ''"
|
||||||
|
@click="votePost('UP')"
|
||||||
>👍 추천 <b>{{ post.upCount || 0 }}</b></button>
|
>👍 추천 <b>{{ post.upCount || 0 }}</b></button>
|
||||||
<button
|
<button
|
||||||
type="button" class="vote-btn down" :class="{ active: post.myVote === 'DOWN' }"
|
type="button" class="vote-btn down" :class="{ active: post.myVote === 'DOWN' }"
|
||||||
:disabled="voting" @click="votePost('DOWN')"
|
:disabled="voting || isMyPost"
|
||||||
|
:title="isMyPost ? '본인 글은 비추천할 수 없습니다' : ''"
|
||||||
|
@click="votePost('DOWN')"
|
||||||
>👎 비추천 <b>{{ post.downCount || 0 }}</b></button>
|
>👎 비추천 <b>{{ post.downCount || 0 }}</b></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -249,7 +294,7 @@ onMounted(load)
|
|||||||
<h2>댓글 {{ post.comments.length }}</h2>
|
<h2>댓글 {{ post.comments.length }}</h2>
|
||||||
|
|
||||||
<ul class="comment-list">
|
<ul class="comment-list">
|
||||||
<li v-for="c in post.comments" :key="c.id" class="comment">
|
<li v-for="c in post.comments" :key="c.id" class="comment" :class="{ 'hot-comment': c.hot }">
|
||||||
<div class="comment-head">
|
<div class="comment-head">
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
:name="c.authorName"
|
:name="c.authorName"
|
||||||
@@ -258,26 +303,46 @@ onMounted(load)
|
|||||||
:size="16"
|
:size="16"
|
||||||
/>
|
/>
|
||||||
<strong>{{ c.authorName }}</strong>
|
<strong>{{ c.authorName }}</strong>
|
||||||
|
<span v-if="isAuthorComment(c)" class="author-badge">작성자</span>
|
||||||
|
<span v-if="c.hot" class="hot-badge">🔥 인기</span>
|
||||||
<span class="comment-date">{{ formatRelative(c.createdAt) }}</span>
|
<span class="comment-date">{{ formatRelative(c.createdAt) }}</span>
|
||||||
<span v-if="canModifyComment(c)" class="comment-del">
|
<span v-if="canModifyComment(c)" class="comment-del">
|
||||||
<IconBtn icon="trash" title="댓글 삭제" variant="danger" size="sm" @click="removeComment(c)" />
|
<IconBtn icon="trash" title="댓글 삭제" variant="danger" size="sm" @click="removeComment(c)" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<MarkdownViewer :content="c.content" class="comment-body" />
|
<!-- 비추천 많은 댓글 블라인드 (작성자·관리자는 정상) -->
|
||||||
<div class="comment-votes">
|
<div v-if="isBlinded(c) && !isMyComment(c) && !isAdmin && !revealedComments[c.id]" class="blinded comment-blinded">
|
||||||
<button
|
🙈 비추천이 많아 블라인드된 댓글입니다.
|
||||||
type="button" class="cvote up" :class="{ active: c.myVote === 'UP' }"
|
<button type="button" class="reveal-btn" @click="toggleReveal(c)">그래도 보기</button>
|
||||||
@click="voteComment(c, 'UP')"
|
|
||||||
>👍 {{ c.upCount || 0 }}</button>
|
|
||||||
<button
|
|
||||||
type="button" class="cvote down" :class="{ active: c.myVote === 'DOWN' }"
|
|
||||||
@click="voteComment(c, 'DOWN')"
|
|
||||||
>👎 {{ c.downCount || 0 }}</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<MarkdownViewer :content="c.content" class="comment-body" />
|
||||||
|
<div class="comment-votes">
|
||||||
|
<button
|
||||||
|
type="button" class="cvote up" :class="{ active: c.myVote === 'UP' }"
|
||||||
|
:disabled="isMyComment(c)"
|
||||||
|
:title="isMyComment(c) ? '본인 댓글은 추천할 수 없습니다' : ''"
|
||||||
|
@click="voteComment(c, 'UP')"
|
||||||
|
>👍 {{ c.upCount || 0 }}</button>
|
||||||
|
<button
|
||||||
|
type="button" class="cvote down" :class="{ active: c.myVote === 'DOWN' }"
|
||||||
|
:disabled="isMyComment(c)"
|
||||||
|
:title="isMyComment(c) ? '본인 댓글은 비추천할 수 없습니다' : ''"
|
||||||
|
@click="voteComment(c, 'DOWN')"
|
||||||
|
>👎 {{ c.downCount || 0 }}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-if="!post.comments.length" class="msg">첫 댓글을 남겨보세요.</p>
|
<p v-if="!post.comments.length" class="msg">첫 댓글을 남겨보세요.</p>
|
||||||
|
|
||||||
|
<!-- 댓글 목록 최하단(입력폼 위) 새로고침 -->
|
||||||
|
<div class="comment-refresh">
|
||||||
|
<button type="button" class="refresh-btn" :disabled="refreshingComments" @click="refreshComments">
|
||||||
|
🔄 {{ refreshingComments ? '새로고침 중…' : '댓글 새로고침' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form v-if="!post.blocked" class="comment-form" @submit.prevent="addComment">
|
<form v-if="!post.blocked" class="comment-form" @submit.prevent="addComment">
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
v-model="commentText"
|
v-model="commentText"
|
||||||
@@ -393,21 +458,22 @@ h1 {
|
|||||||
.rec-inline-list {
|
.rec-inline-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0.4rem 0 0;
|
margin: 0.4rem 0 0;
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.55rem 0.65rem;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-wrap: wrap;
|
||||||
gap: 0.1rem;
|
gap: 0.45rem 0.8rem;
|
||||||
}
|
}
|
||||||
.rec-inline-list li {
|
.rec-inline-list li {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.35rem;
|
||||||
padding: 0.35rem 0.4rem;
|
white-space: nowrap; /* 이름이 잘리지 않도록 아바타+이름 단위로 줄바꿈 */
|
||||||
|
flex: none;
|
||||||
}
|
}
|
||||||
.rec-name {
|
.rec-name {
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -519,11 +585,67 @@ button.notice-btn {
|
|||||||
.comment-body {
|
.comment-body {
|
||||||
margin-top: 0.3rem;
|
margin-top: 0.3rem;
|
||||||
}
|
}
|
||||||
|
.author-badge {
|
||||||
|
padding: 0.05rem 0.35rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: hsla(160, 100%, 37%, 1);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.hot-comment {
|
||||||
|
background: rgba(231, 76, 60, 0.05);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.hot-badge {
|
||||||
|
padding: 0.05rem 0.35rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #e74c3c;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
.comment-votes {
|
.comment-votes {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
margin-top: 0.4rem;
|
margin-top: 0.4rem;
|
||||||
}
|
}
|
||||||
|
/* 블라인드 */
|
||||||
|
.blinded {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
border: 1px dashed var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--color-text);
|
||||||
|
opacity: 0.85;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
.comment-blinded {
|
||||||
|
padding: 0.7rem 0.8rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.reveal-btn {
|
||||||
|
padding: 0.3rem 0.8rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
/* 댓글 새로고침 */
|
||||||
|
.comment-refresh {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0.5rem 0 0.25rem;
|
||||||
|
}
|
||||||
|
.refresh-btn {
|
||||||
|
padding: 0.35rem 1rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
.cvote {
|
.cvote {
|
||||||
padding: 0.2rem 0.6rem;
|
padding: 0.2rem 0.6rem;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ const router = useRouter()
|
|||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const isAdmin = computed(() => auth.user?.role === 'ADMIN')
|
const isAdmin = computed(() => auth.user?.role === 'ADMIN')
|
||||||
|
|
||||||
|
// 비추천 20개 이상이면 블라인드 (작성자·관리자에겐 정상 표시)
|
||||||
|
const BLIND_DOWN = 20
|
||||||
|
function isBlinded(p) {
|
||||||
|
return (p.downCount || 0) >= BLIND_DOWN
|
||||||
|
}
|
||||||
|
function showBlindMsg(p) {
|
||||||
|
return isBlinded(p) && !isAdmin.value && auth.user?.id !== p.authorId
|
||||||
|
}
|
||||||
|
|
||||||
// 현재 게시판(카테고리)
|
// 현재 게시판(카테고리)
|
||||||
const category = computed(() => route.params.category || DEFAULT_BOARD)
|
const category = computed(() => route.params.category || DEFAULT_BOARD)
|
||||||
|
|
||||||
@@ -165,12 +174,17 @@ onMounted(loadTags)
|
|||||||
<tr v-for="p in posts" :key="p.id" :class="{ 'notice-row': p.notice }" @click="router.push({ path: `/board/${category}/${p.id}`, query: route.query })">
|
<tr v-for="p in posts" :key="p.id" :class="{ 'notice-row': p.notice }" @click="router.push({ path: `/board/${category}/${p.id}`, query: route.query })">
|
||||||
<td>
|
<td>
|
||||||
<span v-if="p.notice" class="notice-badge">공지</span>
|
<span v-if="p.notice" class="notice-badge">공지</span>
|
||||||
|
<span v-if="p.hot" class="hot-badge">🔥 인기</span>
|
||||||
<template v-if="p.blocked && !isAdmin">
|
<template v-if="p.blocked && !isAdmin">
|
||||||
<span class="blocked-msg">🚫 관리자에 의해 제한된 게시글입니다.</span>
|
<span class="blocked-msg">🚫 관리자에 의해 제한된 게시글입니다.</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="showBlindMsg(p)">
|
||||||
|
<span class="blocked-msg">🙈 비추천이 많아 블라인드된 게시글입니다.</span>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="title">{{ p.title }}</span>
|
<span class="title">{{ p.title }}</span>
|
||||||
<span v-if="p.blocked" class="blocked-badge">제한</span>
|
<span v-if="p.blocked" class="blocked-badge">제한</span>
|
||||||
|
<span v-if="isBlinded(p)" class="blind-badge">블라인드</span>
|
||||||
<span v-if="p.commentCount" class="comment-count">[{{ p.commentCount }}]</span>
|
<span v-if="p.commentCount" class="comment-count">[{{ p.commentCount }}]</span>
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
@@ -374,6 +388,25 @@ button:disabled {
|
|||||||
.notice-row .title {
|
.notice-row .title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.hot-badge {
|
||||||
|
margin-right: 0.4rem;
|
||||||
|
padding: 0.05rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #e74c3c;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.blind-badge {
|
||||||
|
margin-left: 0.35rem;
|
||||||
|
padding: 0.05rem 0.35rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--color-text);
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* 하단 영역: 페이징(가운데) + 돋보기(오른쪽) 같은 행 */
|
/* 하단 영역: 페이징(가운데) + 돋보기(오른쪽) 같은 행 */
|
||||||
.list-footer {
|
.list-footer {
|
||||||
|
|||||||
Reference in New Issue
Block a user