@@ -141,9 +141,6 @@ const recommenders = computed(() => post.value?.recommenders || [])
|
|||||||
const previewRecommenders = computed(() => recommenders.value.slice(0, 10))
|
const previewRecommenders = computed(() => recommenders.value.slice(0, 10))
|
||||||
const extraRecommenders = computed(() => Math.max(0, (post.value?.upCount || 0) - 10))
|
const extraRecommenders = computed(() => Math.max(0, (post.value?.upCount || 0) - 10))
|
||||||
const showRecommenders = ref(false)
|
const showRecommenders = ref(false)
|
||||||
function openRecommenders() {
|
|
||||||
if (post.value?.upCount) showRecommenders.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
</script>
|
</script>
|
||||||
@@ -161,11 +158,12 @@ onMounted(load)
|
|||||||
:name="post.authorName"
|
:name="post.authorName"
|
||||||
:google-picture="post.authorGooglePicture"
|
:google-picture="post.authorGooglePicture"
|
||||||
:profile-image="post.authorProfileImage"
|
:profile-image="post.authorProfileImage"
|
||||||
:size="26"
|
:size="16"
|
||||||
/>
|
/>
|
||||||
<span class="meta-author">{{ post.authorName }}</span>
|
<span class="meta-author">{{ post.authorName }}</span>
|
||||||
<span>· {{ formatRelative(post.createdAt) }}</span>
|
<span>· {{ formatRelative(post.createdAt) }}</span>
|
||||||
<span>· 조회 {{ post.viewCount }}</span>
|
<span>· 조회 {{ post.viewCount }}</span>
|
||||||
|
<span>· 추천 {{ post.upCount || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -194,23 +192,37 @@ onMounted(load)
|
|||||||
>👎 비추천 <b>{{ post.downCount || 0 }}</b></button>
|
>👎 비추천 <b>{{ post.downCount || 0 }}</b></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 추천자 (아바타 최대 10개 + 초과 안내, 클릭 시 전체 목록) -->
|
<!-- 추천자 (아바타 최대 10개 + 초과 안내, 클릭 시 하단에 전체 목록 펼침) -->
|
||||||
<div v-if="!restricted && post.upCount" class="recommenders" @click="openRecommenders">
|
<div v-if="!restricted && post.upCount" class="recommenders-wrap">
|
||||||
<div class="rec-avatars">
|
<button type="button" class="recommenders" @click="showRecommenders = !showRecommenders">
|
||||||
<UserAvatar
|
<span class="rec-avatars">
|
||||||
v-for="r in previewRecommenders"
|
<UserAvatar
|
||||||
:key="r.memberId"
|
v-for="r in previewRecommenders"
|
||||||
:name="r.name"
|
:key="r.memberId"
|
||||||
:google-picture="r.googlePicture"
|
:name="r.name"
|
||||||
:profile-image="r.profileImage"
|
:google-picture="r.googlePicture"
|
||||||
:size="28"
|
:profile-image="r.profileImage"
|
||||||
class="rec-avatar"
|
:size="16"
|
||||||
/>
|
class="rec-avatar"
|
||||||
</div>
|
/>
|
||||||
<span class="rec-label">
|
</span>
|
||||||
<template v-if="extraRecommenders > 0">+{{ extraRecommenders }}명이 추천했습니다</template>
|
<span class="rec-label">
|
||||||
<template v-else>{{ post.upCount }}명이 추천했습니다</template>
|
<template v-if="extraRecommenders > 0">+{{ extraRecommenders }}명이 추천했습니다</template>
|
||||||
</span>
|
<template v-else>{{ post.upCount }}명이 추천했습니다</template>
|
||||||
|
</span>
|
||||||
|
<span class="rec-toggle">{{ showRecommenders ? '▲' : '▼' }}</span>
|
||||||
|
</button>
|
||||||
|
<ul v-if="showRecommenders" class="rec-inline-list">
|
||||||
|
<li v-for="r in recommenders" :key="r.memberId">
|
||||||
|
<UserAvatar
|
||||||
|
:name="r.name"
|
||||||
|
:google-picture="r.googlePicture"
|
||||||
|
:profile-image="r.profileImage"
|
||||||
|
:size="16"
|
||||||
|
/>
|
||||||
|
<span class="rec-name">{{ r.name }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 태그 (본문 아래) -->
|
<!-- 태그 (본문 아래) -->
|
||||||
@@ -243,7 +255,7 @@ onMounted(load)
|
|||||||
:name="c.authorName"
|
:name="c.authorName"
|
||||||
:google-picture="c.authorGooglePicture"
|
:google-picture="c.authorGooglePicture"
|
||||||
:profile-image="c.authorProfileImage"
|
:profile-image="c.authorProfileImage"
|
||||||
:size="24"
|
:size="16"
|
||||||
/>
|
/>
|
||||||
<strong>{{ c.authorName }}</strong>
|
<strong>{{ c.authorName }}</strong>
|
||||||
<span class="comment-date">{{ formatRelative(c.createdAt) }}</span>
|
<span class="comment-date">{{ formatRelative(c.createdAt) }}</span>
|
||||||
@@ -279,29 +291,6 @@ onMounted(load)
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- 추천자 전체 목록 (아바타 + 이름) -->
|
|
||||||
<Teleport to="body">
|
|
||||||
<div v-if="showRecommenders" class="rec-modal-backdrop" @click.self="showRecommenders = false">
|
|
||||||
<div class="rec-modal" role="dialog" aria-modal="true" aria-label="추천한 사람">
|
|
||||||
<div class="rec-modal-head">
|
|
||||||
<strong>추천한 사람 {{ post?.upCount || 0 }}명</strong>
|
|
||||||
<IconBtn icon="close" title="닫기" size="sm" @click="showRecommenders = false" />
|
|
||||||
</div>
|
|
||||||
<ul class="rec-modal-list">
|
|
||||||
<li v-for="r in recommenders" :key="r.memberId">
|
|
||||||
<UserAvatar
|
|
||||||
:name="r.name"
|
|
||||||
:google-picture="r.googlePicture"
|
|
||||||
:profile-image="r.profileImage"
|
|
||||||
:size="32"
|
|
||||||
/>
|
|
||||||
<span class="rec-name">{{ r.name }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Teleport>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -362,26 +351,30 @@ h1 {
|
|||||||
background: rgba(230, 126, 34, 0.08);
|
background: rgba(230, 126, 34, 0.08);
|
||||||
}
|
}
|
||||||
/* 추천자 */
|
/* 추천자 */
|
||||||
|
.recommenders-wrap {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
.recommenders {
|
.recommenders {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
padding: 0.6rem 0.8rem;
|
padding: 0.6rem 0.8rem;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--color-background-soft);
|
background: var(--color-background-soft);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: 0.5rem;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.recommenders:hover {
|
.recommenders:hover {
|
||||||
background: var(--color-background-mute);
|
background: var(--color-background-mute);
|
||||||
}
|
}
|
||||||
.rec-avatars {
|
.rec-avatars {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
.rec-avatar {
|
.rec-avatar {
|
||||||
margin-left: -6px;
|
margin-left: -4px;
|
||||||
box-shadow: 0 0 0 2px var(--color-background-soft);
|
box-shadow: 0 0 0 2px var(--color-background-soft);
|
||||||
}
|
}
|
||||||
.rec-avatar:first-child {
|
.rec-avatar:first-child {
|
||||||
@@ -392,6 +385,30 @@ h1 {
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
.rec-toggle {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.rec-inline-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0.4rem 0 0;
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
.rec-inline-list li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.35rem 0.4rem;
|
||||||
|
}
|
||||||
|
.rec-name {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
@@ -520,50 +537,6 @@ button.notice-btn {
|
|||||||
border-color: #e67e22;
|
border-color: #e67e22;
|
||||||
color: #e67e22;
|
color: #e67e22;
|
||||||
}
|
}
|
||||||
/* 추천자 모달 */
|
|
||||||
.rec-modal-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 2000;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.rec-modal {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 360px;
|
|
||||||
max-height: 70vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: var(--color-background);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.rec-modal-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.85rem 1rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
.rec-modal-list {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.rec-modal-list li {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.6rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
}
|
|
||||||
.rec-name {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
.comment-form {
|
.comment-form {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ onMounted(loadTags)
|
|||||||
<th>제목</th>
|
<th>제목</th>
|
||||||
<th class="col-author">작성자</th>
|
<th class="col-author">작성자</th>
|
||||||
<th class="col-num">조회</th>
|
<th class="col-num">조회</th>
|
||||||
|
<th class="col-recommend">추천</th>
|
||||||
<th class="col-date">작성일</th>
|
<th class="col-date">작성일</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -171,7 +172,6 @@ onMounted(loadTags)
|
|||||||
<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="p.commentCount" class="comment-count">[{{ p.commentCount }}]</span>
|
<span v-if="p.commentCount" class="comment-count">[{{ p.commentCount }}]</span>
|
||||||
<span v-if="p.upCount" class="recommend-count">👍 {{ p.upCount }}</span>
|
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-author">
|
<td class="col-author">
|
||||||
@@ -180,12 +180,13 @@ onMounted(loadTags)
|
|||||||
:name="p.authorName"
|
:name="p.authorName"
|
||||||
:google-picture="p.authorGooglePicture"
|
:google-picture="p.authorGooglePicture"
|
||||||
:profile-image="p.authorProfileImage"
|
:profile-image="p.authorProfileImage"
|
||||||
:size="22"
|
:size="16"
|
||||||
/>
|
/>
|
||||||
<span class="author-name">{{ p.authorName }}</span>
|
<span class="author-name">{{ p.authorName }}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-num">{{ p.viewCount }}</td>
|
<td class="col-num">{{ p.viewCount }}</td>
|
||||||
|
<td class="col-recommend">{{ p.upCount || 0 }}</td>
|
||||||
<td class="col-date">{{ formatRelative(p.createdAt) }}</td>
|
<td class="col-date">{{ formatRelative(p.createdAt) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -318,6 +319,10 @@ button:disabled {
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.col-recommend {
|
||||||
|
width: 56px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.col-author {
|
.col-author {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
@@ -341,12 +346,6 @@ button:disabled {
|
|||||||
color: hsla(160, 100%, 37%, 1);
|
color: hsla(160, 100%, 37%, 1);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.recommend-count {
|
|
||||||
margin-left: 0.35rem;
|
|
||||||
color: var(--color-text);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 0.82rem;
|
|
||||||
}
|
|
||||||
.blocked-msg {
|
.blocked-msg {
|
||||||
color: #c0392b;
|
color: #c0392b;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -517,9 +516,15 @@ button:disabled {
|
|||||||
.post-table .col-num::before {
|
.post-table .col-num::before {
|
||||||
content: '· 조회 ';
|
content: '· 조회 ';
|
||||||
}
|
}
|
||||||
.post-table .col-date {
|
.post-table .col-recommend {
|
||||||
order: 4;
|
order: 4;
|
||||||
}
|
}
|
||||||
|
.post-table .col-recommend::before {
|
||||||
|
content: '· 추천 ';
|
||||||
|
}
|
||||||
|
.post-table .col-date {
|
||||||
|
order: 5;
|
||||||
|
}
|
||||||
.post-table .col-date::before {
|
.post-table .col-date::before {
|
||||||
content: '· ';
|
content: '· ';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user