Files
urldb/web/pages/admin/index.vue
2025-08-08 01:28:25 +08:00

391 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="space-y-8">
<!-- 欢迎区域 -->
<n-card class="bg-gradient-to-r from-blue-500 to-purple-600 text-white border-0 relative">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold mb-2 text-white">
欢迎回来管理员
</h1>
<p class="text-blue-100 text-lg">
这里是管理后台您可以管理所有系统资源和配置
</p>
</div>
<div class="hidden md:block">
<div class="w-16 h-16 bg-white/20 rounded-full flex items-center justify-center">
<i class="fas fa-shield-alt text-2xl text-white"></i>
</div>
</div>
</div>
</n-card>
<!-- 统计卡片 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- 今日资源/总资源数 -->
<n-card>
<div class="flex items-center">
<div class="p-3 bg-blue-100 dark:bg-blue-900 rounded-lg">
<i class="fas fa-cloud text-blue-600 dark:text-blue-400 text-xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">今日资源/总资源数</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.today_resources || 0 }}/{{ stats.total_resources || 0 }}</p>
</div>
</div>
<template #footer>
<n-button text type="primary" @click="navigateTo('/admin/resources')">
查看详情
<template #icon>
<i class="fas fa-arrow-right"></i>
</template>
</n-button>
</template>
</n-card>
<!-- 今日浏览量 -->
<n-card>
<div class="flex items-center">
<div class="p-3 bg-orange-100 dark:bg-orange-900 rounded-lg">
<i class="fas fa-eye text-orange-600 dark:text-orange-400 text-xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">今日浏览量</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.today_views || 0 }}</p>
</div>
</div>
<template #footer>
<n-button text type="warning" @click="navigateTo('/admin/search-stats')">
查看详情
<template #icon>
<i class="fas fa-arrow-right"></i>
</template>
</n-button>
</template>
</n-card>
<!-- 今日搜索量 -->
<n-card>
<div class="flex items-center">
<div class="p-3 bg-green-100 dark:bg-green-900 rounded-lg">
<i class="fas fa-search text-green-600 dark:text-green-400 text-xl"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">今日搜索量</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.today_searches || 0 }}</p>
</div>
</div>
<template #footer>
<n-button text type="success" @click="navigateTo('/admin/search-stats')">
查看详情
<template #icon>
<i class="fas fa-arrow-right"></i>
</template>
</n-button>
</template>
</n-card>
</div>
<!-- 平台管理 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="flex items-center mb-4">
<div class="p-3 bg-green-100 rounded-lg">
<i class="fas fa-server text-green-600 text-xl"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">平台列表</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">系统支持的网盘平台</p>
</div>
</div>
<div class="space-y-2">
<div class="flex flex-wrap gap-1 w-full text-left rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer">
<div v-for="pan in pans" :key="pan.id" class="h-6 px-1 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
<span v-html="pan.icon"></span>&nbsp;{{ pan.name }}
</div>
</div>
</div>
</div>
<!-- 统计趋势图表区域 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 访问量趋势图 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">访问量趋势</h3>
<div class="p-2 bg-orange-100 dark:bg-orange-900 rounded-full">
<i class="fas fa-chart-line text-orange-600 dark:text-orange-400 text-sm"></i>
</div>
</div>
<div class="h-40">
<canvas ref="viewsChart"></canvas>
</div>
</div>
<!-- 搜索量趋势图 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">搜索量趋势</h3>
<div class="p-2 bg-green-100 dark:bg-green-900 rounded-full">
<i class="fas fa-chart-line text-green-600 dark:text-green-400 text-sm"></i>
</div>
</div>
<div class="h-40">
<canvas ref="searchesChart"></canvas>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// 设置页面布局
definePageMeta({
layout: 'admin'
})
import { useStatsApi, usePanApi } from '~/composables/useApi'
import { useApiFetch } from '~/composables/useApiFetch'
import { parseApiResponse } from '~/composables/useApi'
import Chart from 'chart.js/auto'
// API
const statsApi = useStatsApi()
const panApi = usePanApi()
// 获取统计数据
const { data: statsData } = await useAsyncData('adminStats', () => statsApi.getStats())
// 获取平台数据
const { data: pansData } = await useAsyncData('adminPans', () => panApi.getPans())
// 统计数据
const stats = computed(() => {
console.log('原始统计数据:', statsData.value)
const result = (statsData.value as any) || {
total_resources: 0,
today_resources: 0,
today_views: 0,
today_searches: 0
}
console.log('处理后的统计数据:', result)
return result
})
// 平台数据
const pans = computed(() => (pansData.value as any) || [])
// 趋势图数据
const weeklyViews = ref<any[]>([])
const weeklySearches = ref<any[]>([])
// 获取趋势数据
const fetchTrendData = async () => {
console.log('开始获取趋势数据...')
try {
// 获取访问量趋势数据
const viewsResponse = await useApiFetch('/stats/views-trend').then(parseApiResponse)
if (viewsResponse && Array.isArray(viewsResponse)) {
weeklyViews.value = viewsResponse.map((item: any) => ({
label: item.date ? new Date(item.date).toLocaleDateString('zh-CN', { weekday: 'short' }) : '',
value: item.views || 0
}))
} else {
// 如果没有数据,使用模拟数据
weeklyViews.value = [
{ label: '周一', value: 0 },
{ label: '周二', value: 0 },
{ label: '周三', value: 0 },
{ label: '周四', value: 0 },
{ label: '周五', value: 0 },
{ label: '周六', value: 0 },
{ label: '周日', value: 0 }
]
}
// 获取搜索量趋势数据
const searchesResponse = await useApiFetch('/stats/searches-trend').then(parseApiResponse)
console.log('搜索量趋势API响应:', searchesResponse)
if (searchesResponse && Array.isArray(searchesResponse)) {
weeklySearches.value = searchesResponse.map((item: any) => ({
label: item.date ? new Date(item.date).toLocaleDateString('zh-CN', { weekday: 'short' }) : '',
value: item.searches || 0
}))
console.log('处理后的搜索量数据:', weeklySearches.value)
} else {
// 如果没有数据,使用模拟数据
weeklySearches.value = [
{ label: '周一', value: 45 },
{ label: '周二', value: 52 },
{ label: '周三', value: 67 },
{ label: '周四', value: 58 },
{ label: '周五', value: 73 },
{ label: '周六', value: 89 },
{ label: '周日', value: 81 }
]
console.log('使用模拟搜索量数据:', weeklySearches.value)
}
} catch (error) {
console.error('获取趋势数据失败:', error)
// 使用默认模拟数据
weeklyViews.value = [
{ label: '周一', value: 0 },
{ label: '周二', value: 0 },
{ label: '周三', value: 0 },
{ label: '周四', value: 0 },
{ label: '周五', value: 0 },
{ label: '周六', value: 0 },
{ label: '周日', value: 0 }
]
weeklySearches.value = [
{ label: '周一', value: 0 },
{ label: '周二', value: 0 },
{ label: '周三', value: 0 },
{ label: '周四', value: 0 },
{ label: '周五', value: 0 },
{ label: '周六', value: 0 },
{ label: '周日', value: 0 }
]
}
}
// 图表引用
const viewsChart = ref<HTMLCanvasElement | null>(null)
const searchesChart = ref<HTMLCanvasElement | null>(null)
// 图表实例
let viewsChartInstance: any = null
let searchesChartInstance: any = null
// 初始化图表
const initCharts = () => {
console.log('开始初始化图表...')
console.log('访问量数据:', weeklyViews.value)
console.log('搜索量数据:', weeklySearches.value)
// 访问量趋势图
if (viewsChart.value) {
console.log('访问量图表容器存在:', viewsChart.value)
if (viewsChartInstance) {
viewsChartInstance.destroy()
}
const ctx = viewsChart.value.getContext('2d')
if (ctx) {
console.log('访问量图表上下文获取成功')
viewsChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: weeklyViews.value.map(day => day.label),
datasets: [{
label: '访问量',
data: weeklyViews.value.map(day => day.value),
borderColor: 'rgb(249, 115, 22)',
backgroundColor: 'rgba(249, 115, 22, 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)'
}
}
}
}
})
}
}
// 搜索量趋势图
if (searchesChart.value) {
console.log('搜索量图表容器存在:', searchesChart.value)
if (searchesChartInstance) {
searchesChartInstance.destroy()
}
const ctx = searchesChart.value.getContext('2d')
if (ctx) {
console.log('搜索量图表上下文获取成功')
console.log('初始化搜索量图表,数据:', weeklySearches.value)
searchesChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: weeklySearches.value.map(day => day.label),
datasets: [{
label: '搜索量',
data: weeklySearches.value.map(day => day.value),
borderColor: 'rgb(34, 197, 94)',
backgroundColor: 'rgba(34, 197, 94, 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)'
}
}
}
}
})
}
}
}
// 监听数据变化,更新图表
watch([weeklyViews, weeklySearches], () => {
nextTick(() => {
initCharts()
})
})
// 组件挂载后初始化图表和数据
onMounted(async () => {
console.log('组件挂载,开始初始化...')
await fetchTrendData()
console.log('数据获取完成,准备初始化图表...')
nextTick(() => {
console.log('nextTick执行开始初始化图表...')
initCharts()
})
})
</script>
<style scoped>
/* 确保Font Awesome图标正确显示 */
.fas {
font-family: 'Font Awesome 6 Free';
font-weight: 900;
}
</style>