290 lines
9.1 KiB
Vue
290 lines
9.1 KiB
Vue
<template>
|
||
<div class="space-y-6">
|
||
<!-- 统计卡片 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">总用户数</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.totalUsers }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">今日订单</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.todayOrders }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">今日收入</dt>
|
||
<dd class="text-lg font-medium text-gray-900">¥{{ stats.todayRevenue.toLocaleString() }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0">
|
||
<div class="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
|
||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="ml-5 w-0 flex-1">
|
||
<dl>
|
||
<dt class="text-sm font-medium text-gray-500 truncate">活跃译员</dt>
|
||
<dd class="text-lg font-medium text-gray-900">{{ stats.activeTranslators }}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 最近活动 -->
|
||
<div class="bg-white shadow rounded-lg">
|
||
<div class="px-6 py-4 border-b border-gray-200">
|
||
<h3 class="text-lg font-medium text-gray-900">最近活动</h3>
|
||
</div>
|
||
<div class="px-6 py-4">
|
||
<div class="flow-root">
|
||
<ul class="-my-5 divide-y divide-gray-200">
|
||
<li v-for="activity in recentActivities" :key="activity.id" class="py-4">
|
||
<div class="flex items-center space-x-4">
|
||
<div class="flex-shrink-0">
|
||
<div :class="activity.iconColor" class="w-8 h-8 rounded-full flex items-center justify-center">
|
||
<span class="text-sm font-medium text-white">{{ activity.icon }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<p class="text-sm font-medium text-gray-900 truncate">{{ activity.title }}</p>
|
||
<p class="text-sm text-gray-500">{{ activity.description }}</p>
|
||
</div>
|
||
<div class="flex-shrink-0 text-sm text-gray-500">{{ activity.time }}</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
// 确保使用默认布局(包含Sidebar)和管理员认证
|
||
definePageMeta({
|
||
middleware: 'admin-auth', // 使用管理员认证中间件
|
||
layout: 'default' // 明确指定使用默认布局
|
||
})
|
||
|
||
// 页面标题
|
||
useHead({
|
||
title: '仪表板 - 翻译管理系统'
|
||
})
|
||
|
||
// 临时注释掉Supabase导入
|
||
// const { getStats, getCalls } = useSupabaseData()
|
||
|
||
// 当前用户信息
|
||
const currentUser = ref({
|
||
name: '系统管理员',
|
||
role: '管理员'
|
||
})
|
||
|
||
// 统计数据
|
||
const stats = ref({
|
||
totalUsers: 0,
|
||
todayOrders: 0,
|
||
todayRevenue: 0,
|
||
activeTranslators: 0
|
||
})
|
||
|
||
// 加载状态
|
||
const loading = ref(true)
|
||
const error = ref(null)
|
||
|
||
// 最近活动
|
||
const recentActivities = ref([])
|
||
|
||
// 获取用户信息
|
||
onMounted(() => {
|
||
loadDashboardData()
|
||
|
||
// 客户端专用操作
|
||
if (process.client) {
|
||
const adminUser = localStorage.getItem('adminUser')
|
||
if (adminUser) {
|
||
try {
|
||
const user = JSON.parse(adminUser)
|
||
console.log('当前用户:', user)
|
||
} catch (error) {
|
||
console.error('解析用户信息失败:', error)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
// 加载仪表板数据
|
||
const loadDashboardData = async () => {
|
||
try {
|
||
loading.value = true
|
||
error.value = null
|
||
|
||
console.log('开始加载仪表板数据...')
|
||
|
||
// 临时注释掉Supabase调用,使用模拟数据
|
||
// const statsData = await getStats()
|
||
// console.log('统计数据加载成功:', statsData)
|
||
|
||
// 使用模拟数据
|
||
const statsData = {
|
||
totalUsers: 156,
|
||
totalCalls: 23,
|
||
totalRevenue: 12580,
|
||
activeInterpreters: 8
|
||
}
|
||
|
||
stats.value = {
|
||
totalUsers: statsData.totalUsers || 0,
|
||
todayOrders: statsData.totalCalls || 0,
|
||
todayRevenue: statsData.totalRevenue || 0,
|
||
activeTranslators: statsData.activeInterpreters || 0
|
||
}
|
||
|
||
// 临时注释掉Supabase调用,使用模拟数据
|
||
// const recentCalls = await getCalls()
|
||
// console.log('通话记录加载成功:', recentCalls)
|
||
|
||
// 使用模拟活动数据
|
||
recentActivities.value = [
|
||
{
|
||
id: 1,
|
||
title: '新订单创建',
|
||
description: '张先生 - 李译员',
|
||
time: '2分钟前',
|
||
icon: 'O',
|
||
iconColor: 'bg-yellow-400'
|
||
},
|
||
{
|
||
id: 2,
|
||
title: '翻译完成',
|
||
description: '王女士 - 陈译员',
|
||
time: '15分钟前',
|
||
icon: '✓',
|
||
iconColor: 'bg-green-400'
|
||
},
|
||
{
|
||
id: 3,
|
||
title: '翻译进行中',
|
||
description: '李总 - 刘译员',
|
||
time: '30分钟前',
|
||
icon: 'T',
|
||
iconColor: 'bg-blue-400'
|
||
}
|
||
]
|
||
|
||
console.log('仪表板数据加载成功:', { stats: stats.value, activities: recentActivities.value })
|
||
|
||
} catch (err) {
|
||
console.error('加载仪表板数据失败:', err)
|
||
error.value = '加载数据失败,请刷新页面重试'
|
||
// 显示默认数据
|
||
stats.value = {
|
||
totalUsers: 0,
|
||
todayOrders: 0,
|
||
todayRevenue: 0,
|
||
activeTranslators: 0
|
||
}
|
||
recentActivities.value = []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 根据状态获取活动标题
|
||
const getActivityTitle = (status) => {
|
||
const statusMap = {
|
||
'pending': '新订单创建',
|
||
'in_progress': '翻译进行中',
|
||
'completed': '翻译完成',
|
||
'cancelled': '订单取消'
|
||
}
|
||
return statusMap[status] || '订单更新'
|
||
}
|
||
|
||
// 根据状态获取活动图标
|
||
const getActivityIcon = (status) => {
|
||
const iconMap = {
|
||
'pending': 'O',
|
||
'in_progress': 'T',
|
||
'completed': '✓',
|
||
'cancelled': 'X'
|
||
}
|
||
return iconMap[status] || 'U'
|
||
}
|
||
|
||
// 根据状态获取活动颜色
|
||
const getActivityColor = (status) => {
|
||
const colorMap = {
|
||
'pending': 'bg-yellow-400',
|
||
'in_progress': 'bg-blue-400',
|
||
'completed': 'bg-green-400',
|
||
'cancelled': 'bg-red-400'
|
||
}
|
||
return colorMap[status] || 'bg-gray-400'
|
||
}
|
||
|
||
// 格式化时间
|
||
const formatTime = (timestamp) => {
|
||
if (!timestamp) return '未知时间'
|
||
|
||
const now = new Date()
|
||
const time = new Date(timestamp)
|
||
const diff = now - time
|
||
|
||
const minutes = Math.floor(diff / (1000 * 60))
|
||
const hours = Math.floor(diff / (1000 * 60 * 60))
|
||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||
|
||
if (minutes < 1) return '刚刚'
|
||
if (minutes < 60) return `${minutes}分钟前`
|
||
if (hours < 24) return `${hours}小时前`
|
||
return `${days}天前`
|
||
}
|
||
</script>
|
||
|