관리자 목록과 이미지 설정 개선

This commit is contained in:
2026-07-20 12:37:53 +09:00
parent cf7d6ca0de
commit ad38644171
19 changed files with 788 additions and 92 deletions

View File

@@ -36,6 +36,9 @@ const { data: analyticsSummary, refresh: refreshSummary } = await useFetch('/adm
query: analyticsQuery,
default: () => ({
todayVisitors: 0,
totalVisitors: 0,
totalPageViews: 0,
totalPosts: 0,
visitorsLast7Days: 0,
pageViewsLast30Days: 0,
onlineNow: 0,
@@ -106,6 +109,29 @@ const {
const selectedTrendDetailLabel = computed(() => selectedTrendRange.value.label || '오늘')
const trafficSources = computed(() => selectedTrendDetail.value?.sources || [])
const trafficDevices = computed(() => selectedTrendDetail.value?.devices || [])
const totalDeviceVisitors = computed(() => {
return trafficDevices.value.reduce((sum, row) => sum + Number(row.visitors || 0), 0)
})
const groupedTrafficDevices = computed(() => {
const groups = new Map()
for (const row of trafficDevices.value) {
const key = row.deviceType || '기타'
const existing = groups.get(key) || {
deviceType: key,
pageViews: 0,
visitors: 0,
rows: []
}
existing.pageViews += Number(row.pageViews || 0)
existing.visitors += Number(row.visitors || 0)
existing.rows.push(row)
groups.set(key, existing)
}
return [...groups.values()].sort((a, b) => b.pageViews - a.pageViews || b.visitors - a.visitors)
})
const topPosts = computed(() => selectedTrendDetail.value?.topPosts || [])
const topPages = computed(() => selectedTrendDetail.value?.topPages || [])
const trendDetailError = computed(() => selectedTrendDetailError.value ? '선택한 기간 통계 상세를 불러오지 못했습니다.' : '')
@@ -240,6 +266,21 @@ const formatTrafficPercent = (value) => {
return `${Math.round((Number(value || 0) / total) * 100)}%`
}
/**
* 디바이스 방문자 비율을 표시한다.
* @param {number} value - 방문자 수
* @returns {string} 비율
*/
const formatDeviceVisitorPercent = (value) => {
const total = totalDeviceVisitors.value
if (!total) {
return '0%'
}
return `${Math.round((Number(value || 0) / total) * 100)}%`
}
const chartMetrics = computed(() => [
{
key: 'visitors',
@@ -487,9 +528,13 @@ watch(trendRows, () => {
<strong class="ml-2 text-lg text-ink">{{ realtime.summary.onlineNow }}</strong>
</p>
<p class="admin-dashboard__summary-item text-muted">
오늘 접속자
오늘 방문자
<strong class="ml-2 text-lg text-ink">{{ analyticsSummary.todayVisitors }}</strong>
</p>
<p class="admin-dashboard__summary-item text-muted">
누적 방문자
<strong class="ml-2 text-lg text-ink">{{ analyticsSummary.totalVisitors }}</strong>
</p>
<p class="admin-dashboard__summary-item text-muted">
게시물 수
<strong class="ml-2 text-lg text-ink">{{ posts.length }}</strong>
@@ -832,27 +877,39 @@ watch(trendRows, () => {
디바이스
</h3>
<ul
v-if="trafficDevices.length"
v-if="groupedTrafficDevices.length"
class="mt-3 space-y-2"
>
<li
v-for="row in trafficDevices"
:key="`${row.deviceType}-${row.osName}`"
class="flex items-center justify-between gap-3 border-b border-line pb-2 text-xs last:border-b-0 last:pb-0"
v-for="group in groupedTrafficDevices"
:key="group.deviceType"
class="border-b border-line pb-2 text-xs last:border-b-0 last:pb-0"
>
<div>
<p class="font-medium text-ink">
{{ row.deviceType }}
</p>
<p class="mt-0.5 text-[11px] text-muted">
{{ row.osName }}
<div class="flex items-start justify-between gap-3">
<div>
<p class="font-medium text-ink">
{{ group.deviceType }}
</p>
<p class="mt-0.5 text-[11px] text-muted">
{{ group.rows.map((row) => row.osName).filter(Boolean).join(' / ') || '-' }}
</p>
</div>
<p class="text-right text-muted">
<span class="font-semibold text-ink">{{ group.pageViews }}</span>회
<br>
{{ group.visitors }}명 · {{ formatDeviceVisitorPercent(group.visitors) }}
</p>
</div>
<p class="text-right text-muted">
<span class="font-semibold text-ink">{{ row.pageViews }}</span>회
<br>
{{ row.visitors }}명
</p>
<ul class="mt-2 space-y-1 border-t border-line pt-2 text-[11px] text-muted">
<li
v-for="row in group.rows"
:key="`${group.deviceType}-${row.osName}`"
class="flex items-center justify-between gap-2"
>
<span>{{ row.osName || '-' }}</span>
<span>{{ row.visitors }}명 · {{ formatDeviceVisitorPercent(row.visitors) }}</span>
</li>
</ul>
</li>
</ul>
<p
@@ -926,9 +983,9 @@ watch(trendRows, () => {
</p>
<p class="mt-1 text-[11px] leading-snug text-muted">
IP {{ session.ipAddress || '-' }}
<span v-if="Number(session.pageCount || 0) > 1">
· 열린 페이지 {{ session.pageCount }}개
</span>
</p>
<p v-if="Number(session.pageCount || 0) > 1" class="mt-0.5 text-[11px] leading-snug text-muted">
열린 페이지 {{ session.pageCount }}개
</p>
</div>
</li>