v1.3.3: 자체 최소 통계 및 스플래시 localStorage 정리

- 일별 익명 방문자 해시·사이트/게시물 통계(030 마이그레이션)
- POST /api/analytics/pageview, 관리자 analytics API, 클라이언트 트래커
- 관리자 대시보드 통계 카드·인기 게시물 Top 5
- 스플래시: SITE_BRAND_LOGO_TEXT localStorage 제거
This commit is contained in:
2026-05-20 12:15:13 +09:00
parent b6a3228b09
commit 3623305119
18 changed files with 831 additions and 37 deletions

View File

@@ -7,6 +7,19 @@ const { data: posts } = await useFetch('/admin/api/posts', {
default: () => []
})
const { data: analyticsSummary } = await useFetch('/admin/api/analytics/summary', {
default: () => ({
todayVisitors: 0,
visitorsLast7Days: 0,
pageViewsLast30Days: 0
})
})
const { data: topPosts } = await useFetch('/admin/api/analytics/posts', {
query: { days: 30, limit: 5 },
default: () => []
})
const publishedCount = computed(() => posts.value.filter((post) => post.status === 'published').length)
const draftCount = computed(() => posts.value.filter((post) => post.status === 'draft').length)
</script>
@@ -21,30 +34,96 @@ const draftCount = computed(() => posts.value.filter((post) => post.status === '
대시보드
</h1>
</div>
<div class="admin-dashboard__body grid gap-4 bg-paper p-6 text-sm text-muted md:grid-cols-3">
<section class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase">
Posts
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ posts.length }}
</strong>
<div class="admin-dashboard__body space-y-6 bg-paper p-6">
<section class="admin-dashboard__analytics grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<article class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase text-muted">
오늘 방문
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ analyticsSummary.todayVisitors }}
</strong>
</article>
<article class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase text-muted">
7 방문
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ analyticsSummary.visitorsLast7Days }}
</strong>
</article>
<article class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase text-muted">
30 조회
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ analyticsSummary.pageViewsLast30Days }}
</strong>
</article>
<article class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase text-muted">
게시물
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ posts.length }}
</strong>
<p class="admin-dashboard__metric-note mt-2 text-xs text-muted">
발행 {{ publishedCount }} · 초안 {{ draftCount }}
</p>
</article>
</section>
<section class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase">
Published
<section class="admin-dashboard__top-posts border border-line bg-white p-4">
<h2 class="admin-dashboard__section-title text-sm font-semibold text-ink">
인기 게시물 (30)
</h2>
<ul
v-if="topPosts.length"
class="admin-dashboard__top-posts-list mt-4 space-y-3 text-sm"
>
<li
v-for="(item, index) in topPosts"
:key="item.id"
class="admin-dashboard__top-posts-item flex items-start justify-between gap-4 border-b border-line pb-3 last:border-b-0 last:pb-0"
>
<div class="min-w-0 flex-1">
<p class="admin-dashboard__top-posts-rank text-xs font-semibold uppercase text-muted">
#{{ index + 1 }}
</p>
<NuxtLink
:to="`/post/${item.slug}`"
class="admin-dashboard__top-posts-title mt-1 block truncate font-medium text-ink hover:underline"
target="_blank"
>
{{ item.title }}
</NuxtLink>
</div>
<dl class="admin-dashboard__top-posts-stats shrink-0 text-right text-xs text-muted">
<div>
<dt class="inline">
조회
</dt>
<dd class="inline font-semibold text-ink">
{{ item.views }}
</dd>
</div>
<div class="mt-1">
<dt class="inline">
읽음
</dt>
<dd class="inline font-semibold text-ink">
{{ item.reads }}
</dd>
</div>
</dl>
</li>
</ul>
<p
v-else
class="admin-dashboard__top-posts-empty mt-4 text-sm text-muted"
>
아직 집계된 게시물 조회 데이터가 없습니다.
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ publishedCount }}
</strong>
</section>
<section class="admin-dashboard__metric border border-line bg-white p-4">
<p class="admin-dashboard__metric-label text-xs font-semibold uppercase">
Draft
</p>
<strong class="admin-dashboard__metric-value mt-2 block text-3xl text-ink">
{{ draftCount }}
</strong>
</section>
</div>
</section>