- 일별 익명 방문자 해시·사이트/게시물 통계(030 마이그레이션) - POST /api/analytics/pageview, 관리자 analytics API, 클라이언트 트래커 - 관리자 대시보드 통계 카드·인기 게시물 Top 5 - 스플래시: SITE_BRAND_LOGO_TEXT localStorage 제거
131 lines
4.8 KiB
Vue
131 lines
4.8 KiB
Vue
<script setup>
|
|
definePageMeta({
|
|
layout: 'admin'
|
|
})
|
|
|
|
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>
|
|
|
|
<template>
|
|
<section class="admin-dashboard">
|
|
<div class="admin-dashboard__header border-b border-line bg-paper p-6">
|
|
<p class="admin-dashboard__eyebrow text-xs font-semibold uppercase text-muted">
|
|
Admin
|
|
</p>
|
|
<h1 class="admin-dashboard__title mt-2 text-3xl font-semibold">
|
|
대시보드
|
|
</h1>
|
|
</div>
|
|
<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__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>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
</template>
|