Files
urldb/web/pages/admin/search-stats.vue
2025-07-21 23:38:28 +08:00

218 lines
7.5 KiB
Vue

<template>
<div class="min-h-screen bg-gray-50">
<div class="container mx-auto px-4 py-8">
<!-- 页面标题 -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">搜索统计</h1>
<p class="text-gray-600 mt-2">查看搜索量统计和热门关键词分析</p>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600">今日搜索</p>
<p class="text-2xl font-bold text-gray-900">{{ stats.todaySearches }}</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600">本周搜索</p>
<p class="text-2xl font-bold text-gray-900">{{ stats.weekSearches }}</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-100 text-purple-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600">本月搜索</p>
<p class="text-2xl font-bold text-gray-900">{{ stats.monthSearches }}</p>
</div>
</div>
</div>
</div>
<!-- 搜索趋势图表 -->
<div class="bg-white rounded-lg shadow p-6 mb-8">
<h2 class="text-xl font-semibold text-gray-900 mb-4">搜索趋势</h2>
<div class="h-64">
<canvas ref="trendChart"></canvas>
</div>
</div>
<!-- 热门关键词 -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold text-gray-900 mb-4">热门关键词</h2>
<div class="space-y-4">
<div v-for="keyword in stats.hotKeywords" :key="keyword.keyword"
class="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
<div class="flex items-center">
<span class="inline-flex items-center justify-center w-8 h-8 bg-blue-100 text-blue-600 rounded-full text-sm font-medium mr-3">
{{ keyword.rank }}
</span>
<span class="text-gray-900 font-medium">{{ keyword.keyword }}</span>
</div>
<div class="flex items-center">
<span class="text-gray-600 mr-2">{{ keyword.count }}</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="bg-blue-600 h-2 rounded-full"
:style="{ width: getPercentage(keyword.count) + '%' }"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 搜索记录 -->
<div class="bg-white rounded-lg shadow p-6 mt-8">
<h2 class="text-xl font-semibold text-gray-900 mb-4">搜索记录</h2>
<table class="w-full table-auto text-sm">
<thead>
<tr>
<th class="px-2 py-2 text-left">关键词</th>
<th class="px-2 py-2 text-left">次数</th>
<th class="px-2 py-2 text-left">日期</th>
</tr>
</thead>
<tbody>
<tr v-for="item in searchList" :key="item.id">
<td class="px-2 py-2">{{ item.keyword }}</td>
<td class="px-2 py-2">{{ item.count }}</td>
<td class="px-2 py-2">{{ item.date ? (new Date(item.date)).toLocaleDateString() : '' }}</td>
</tr>
</tbody>
</table>
<div v-if="searchList.length === 0" class="text-gray-400 text-center py-8">暂无搜索记录</div>
</div>
</div>
</div>
</template>
<script setup>
// 设置页面布局
definePageMeta({
layout: 'admin'
})
import { ref, onMounted, computed } from 'vue'
import Chart from 'chart.js/auto'
import { useApiFetch } from '~/composables/useApiFetch'
import { parseApiResponse } from '~/composables/useApi'
const stats = ref({
todaySearches: 0,
weekSearches: 0,
monthSearches: 0,
hotKeywords: [],
searchTrend: {
days: [],
values: []
}
})
const searchList = ref([])
const trendChart = ref(null)
let chart = null
// 获取百分比
const getPercentage = (count) => {
if (stats.value.hotKeywords.length === 0) return 0
const maxCount = Math.max(...stats.value.hotKeywords.map(k => k.count))
return Math.round((count / maxCount) * 100)
}
// 加载搜索统计
const loadSearchStats = async () => {
try {
// 1. 汇总卡片
const summary = await useApiFetch('/search-stats/summary').then(parseApiResponse)
stats.value.todaySearches = summary.today || 0
stats.value.weekSearches = summary.week || 0
stats.value.monthSearches = summary.month || 0
// 2. 热门关键词
const hotKeywords = await useApiFetch('/search-stats/hot-keywords').then(parseApiResponse)
stats.value.hotKeywords = hotKeywords || []
// 3. 趋势
const trend = await useApiFetch('/search-stats/trend').then(parseApiResponse)
stats.value.searchTrend.days = (trend || []).map(item => item.date ? (new Date(item.date)).toLocaleDateString() : '')
stats.value.searchTrend.values = (trend || []).map(item => item.total_searches)
// 4. 搜索记录
const data = await useApiFetch('/search-stats').then(parseApiResponse)
searchList.value = data || []
// 5. 更新图表
setTimeout(updateChart, 100)
} catch (error) {
console.error('加载搜索统计失败:', error)
}
}
// 更新图表
const updateChart = () => {
if (chart) {
chart.destroy()
}
const ctx = trendChart.value.getContext('2d')
chart = new Chart(ctx, {
type: 'line',
data: {
labels: stats.value.searchTrend.days,
datasets: [{
label: '搜索量',
data: stats.value.searchTrend.values,
borderColor: 'rgb(59, 130, 246)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
},
x: {
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
})
}
onMounted(() => {
loadSearchStats()
})
</script>