445 lines
20 KiB
Vue
445 lines
20 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<!-- 页面头部 -->
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-semibold text-gray-900">数据报表</h1>
|
|
<p class="mt-1 text-sm text-gray-500">查看平台运营数据和业务报表</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<button
|
|
@click="exportReport"
|
|
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
|
|
>
|
|
导出报表
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 时间范围选择 -->
|
|
<div class="bg-white shadow rounded-lg">
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<label for="start-date" class="block text-sm font-medium text-gray-700 mb-1">开始日期</label>
|
|
<input
|
|
id="start-date"
|
|
v-model="startDate"
|
|
type="date"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label for="end-date" class="block text-sm font-medium text-gray-700 mb-1">结束日期</label>
|
|
<input
|
|
id="end-date"
|
|
v-model="endDate"
|
|
type="date"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label for="report-type" class="block text-sm font-medium text-gray-700 mb-1">报表类型</label>
|
|
<select
|
|
id="report-type"
|
|
v-model="reportType"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<option value="overview">综合概览</option>
|
|
<option value="orders">订单报表</option>
|
|
<option value="users">用户报表</option>
|
|
<option value="finance">财务报表</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex items-end">
|
|
<button
|
|
@click="generateReport"
|
|
class="w-full px-4 py-2 text-sm font-medium text-white bg-green-600 border border-transparent rounded-md hover:bg-green-700"
|
|
>
|
|
生成报表
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 数据概览卡片 -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<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-4 h-4 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"/>
|
|
</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">{{ reportData.totalOrders.toLocaleString() }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<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-4 h-4 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"/>
|
|
</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">¥{{ reportData.totalRevenue.toLocaleString() }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<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-4 h-4 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"/>
|
|
</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">{{ reportData.activeUsers.toLocaleString() }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<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-4 h-4 text-white" 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"/>
|
|
</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">{{ reportData.completionRate }}%</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 图表区域 -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<!-- 订单趋势图 -->
|
|
<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="p-6">
|
|
<div class="h-64 bg-gray-50 rounded-lg flex items-center justify-center">
|
|
<div class="text-center">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" 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"/>
|
|
</svg>
|
|
<p class="mt-2 text-sm text-gray-500">订单趋势图表</p>
|
|
<p class="text-xs text-gray-400">可集成 Chart.js 或其他图表库</p>
|
|
</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="p-6">
|
|
<div class="h-64 bg-gray-50 rounded-lg flex items-center justify-center">
|
|
<div class="text-center">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z"/>
|
|
</svg>
|
|
<p class="mt-2 text-sm text-gray-500">收入分析图表</p>
|
|
<p class="text-xs text-gray-400">可集成 Chart.js 或其他图表库</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 详细报表数据 -->
|
|
<div class="bg-white shadow rounded-lg overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">详细数据</h3>
|
|
</div>
|
|
|
|
<!-- 订单报表 -->
|
|
<div v-if="reportType === 'orders'" class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">日期</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">新增订单</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">完成订单</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">取消订单</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">完成率</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<tr v-for="row in orderReportData" :key="row.date" class="hover:bg-gray-50">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ row.date }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ row.newOrders }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-green-600">{{ row.completedOrders }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-red-600">{{ row.cancelledOrders }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ row.completionRate }}%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 用户报表 -->
|
|
<div v-else-if="reportType === 'users'" class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">日期</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">新增用户</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">活跃用户</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">留存率</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<tr v-for="row in userReportData" :key="row.date" class="hover:bg-gray-50">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ row.date }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-600">{{ row.newUsers }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-green-600">{{ row.activeUsers }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ row.retentionRate }}%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 财务报表 -->
|
|
<div v-else-if="reportType === 'finance'" class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">日期</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">收入</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">支出</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">净利润</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<tr v-for="row in financeReportData" :key="row.date" class="hover:bg-gray-50">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ row.date }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-green-600">¥{{ row.revenue.toLocaleString() }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-red-600">¥{{ row.expenses.toLocaleString() }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium" :class="row.profit >= 0 ? 'text-green-600' : 'text-red-600'">
|
|
¥{{ row.profit.toLocaleString() }}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 综合概览 -->
|
|
<div v-else class="p-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-900 mb-2">订单统计</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">总订单数:</span>
|
|
<span class="font-medium">{{ reportData.totalOrders }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">已完成:</span>
|
|
<span class="font-medium text-green-600">{{ reportData.completedOrders }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">进行中:</span>
|
|
<span class="font-medium text-blue-600">{{ reportData.pendingOrders }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-900 mb-2">用户统计</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">总用户数:</span>
|
|
<span class="font-medium">{{ reportData.totalUsers }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">活跃用户:</span>
|
|
<span class="font-medium text-green-600">{{ reportData.activeUsers }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">新增用户:</span>
|
|
<span class="font-medium text-blue-600">{{ reportData.newUsers }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-900 mb-2">财务统计</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">总收入:</span>
|
|
<span class="font-medium text-green-600">¥{{ reportData.totalRevenue.toLocaleString() }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">总支出:</span>
|
|
<span class="font-medium text-red-600">¥{{ reportData.totalExpenses.toLocaleString() }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600">净利润:</span>
|
|
<span class="font-medium" :class="reportData.netProfit >= 0 ? 'text-green-600' : 'text-red-600'">
|
|
¥{{ reportData.netProfit.toLocaleString() }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 空状态 -->
|
|
<div v-if="!reportData.totalOrders" class="text-center py-12">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" 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"/>
|
|
</svg>
|
|
<h3 class="mt-2 text-sm font-medium text-gray-900">暂无报表数据</h3>
|
|
<p class="mt-1 text-sm text-gray-500">请选择日期范围并生成报表</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
// 页面元数据 - 确保使用默认布局
|
|
definePageMeta({
|
|
middleware: 'auth',
|
|
layout: 'default' // 明确指定使用默认布局
|
|
})
|
|
|
|
// 页面标题
|
|
useHead({
|
|
title: '数据报表 - 翻译管理系统'
|
|
})
|
|
|
|
// 日期范围
|
|
const startDate = ref('')
|
|
const endDate = ref('')
|
|
const reportType = ref('overview')
|
|
|
|
// 报表数据
|
|
const reportData = ref({
|
|
totalOrders: 0,
|
|
totalRevenue: 0,
|
|
activeUsers: 0,
|
|
completionRate: 0,
|
|
completedOrders: 0,
|
|
pendingOrders: 0,
|
|
totalUsers: 0,
|
|
newUsers: 0,
|
|
totalExpenses: 0,
|
|
netProfit: 0
|
|
})
|
|
|
|
// 订单报表数据
|
|
const orderReportData = ref([])
|
|
|
|
// 用户报表数据
|
|
const userReportData = ref([])
|
|
|
|
// 财务报表数据
|
|
const financeReportData = ref([])
|
|
|
|
// 生成报表
|
|
const generateReport = async () => {
|
|
try {
|
|
// 模拟API调用
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
|
|
// 模拟报表数据
|
|
reportData.value = {
|
|
totalOrders: 1250,
|
|
totalRevenue: 850000,
|
|
activeUsers: 320,
|
|
completionRate: 92.5,
|
|
completedOrders: 1156,
|
|
pendingOrders: 94,
|
|
totalUsers: 450,
|
|
newUsers: 28,
|
|
totalExpenses: 320000,
|
|
netProfit: 530000
|
|
}
|
|
|
|
// 根据报表类型生成相应数据
|
|
if (reportType.value === 'orders') {
|
|
orderReportData.value = [
|
|
{ date: '2024-01-15', newOrders: 45, completedOrders: 42, cancelledOrders: 2, completionRate: 93.3 },
|
|
{ date: '2024-01-14', newOrders: 38, completedOrders: 35, cancelledOrders: 1, completionRate: 92.1 },
|
|
{ date: '2024-01-13', newOrders: 52, completedOrders: 48, cancelledOrders: 3, completionRate: 92.3 },
|
|
{ date: '2024-01-12', newOrders: 41, completedOrders: 39, cancelledOrders: 1, completionRate: 95.1 },
|
|
{ date: '2024-01-11', newOrders: 47, completedOrders: 44, cancelledOrders: 2, completionRate: 93.6 }
|
|
]
|
|
} else if (reportType.value === 'users') {
|
|
userReportData.value = [
|
|
{ date: '2024-01-15', newUsers: 12, activeUsers: 89, retentionRate: 85.2 },
|
|
{ date: '2024-01-14', newUsers: 8, activeUsers: 92, retentionRate: 87.1 },
|
|
{ date: '2024-01-13', newUsers: 15, activeUsers: 95, retentionRate: 84.6 },
|
|
{ date: '2024-01-12', newUsers: 10, activeUsers: 88, retentionRate: 86.3 },
|
|
{ date: '2024-01-11', newUsers: 14, activeUsers: 91, retentionRate: 85.8 }
|
|
]
|
|
} else if (reportType.value === 'finance') {
|
|
financeReportData.value = [
|
|
{ date: '2024-01-15', revenue: 45000, expenses: 18000, profit: 27000 },
|
|
{ date: '2024-01-14', revenue: 38000, expenses: 15000, profit: 23000 },
|
|
{ date: '2024-01-13', revenue: 52000, expenses: 21000, profit: 31000 },
|
|
{ date: '2024-01-12', revenue: 41000, expenses: 16000, profit: 25000 },
|
|
{ date: '2024-01-11', revenue: 47000, expenses: 19000, profit: 28000 }
|
|
]
|
|
}
|
|
|
|
alert('报表生成成功!')
|
|
} catch (error) {
|
|
alert('生成报表失败,请重试')
|
|
}
|
|
}
|
|
|
|
// 导出报表
|
|
const exportReport = () => {
|
|
// 这里可以实现导出功能
|
|
alert('导出功能开发中...')
|
|
}
|
|
|
|
// 初始化日期
|
|
onMounted(() => {
|
|
const today = new Date()
|
|
const lastWeek = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
|
|
|
|
endDate.value = today.toISOString().split('T')[0]
|
|
startDate.value = lastWeek.toISOString().split('T')[0]
|
|
})
|
|
</script> |