deotalandAi/apps/FrontendDesigner/src/views/admin/AdminDashboard.vue

653 lines
14 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="admin-dashboard">
<!-- 页面头部 -->
<div class="dashboard-header">
<div class="header-left">
<h2>{{ t('admin.dashboard.title') }}</h2>
<p class="subtitle">{{ t('admin.dashboard.subtitle') }}</p>
</div>
<div class="header-actions">
<el-button type="primary" @click="refreshData">
<el-icon><Refresh /></el-icon>
{{ t('admin.dashboard.refresh') }}
</el-button>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-grid">
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<div class="stat-icon users">
<el-icon><UserFilled /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.totalUsers }}</div>
<div class="stat-label">{{ t('admin.dashboard.stats.totalUsers') }}</div>
</div>
</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<div class="stat-icon orders">
<el-icon><ShoppingCart /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.totalOrders }}</div>
<div class="stat-label">{{ t('admin.dashboard.stats.totalOrders') }}</div>
</div>
</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<div class="stat-icon reviews">
<el-icon><Document /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.pendingReviews }}</div>
<div class="stat-label">{{ t('admin.dashboard.stats.pendingReviews') }}</div>
</div>
</div>
</el-card>
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<div class="stat-icon revenue">
<el-icon><Money /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">${{ formatNumber(stats.revenue) }}</div>
<div class="stat-label">{{ t('admin.dashboard.stats.revenue') }}</div>
</div>
</div>
</el-card>
</div>
<!-- 图表区域 -->
<div class="charts-section">
<el-row :gutter="24">
<!-- 销售趋势图表 -->
<el-col :xs="24" :lg="12">
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="chart-header">
<span>{{ t('admin.dashboard.charts.salesTrend') }}</span>
<el-select v-model="chartPeriod" size="small" style="width: 120px">
<el-option label="最近7天" value="7d" />
<el-option label="最近30天" value="30d" />
<el-option label="最近90天" value="90d" />
</el-select>
</div>
</template>
<div class="chart-container">
<div class="simple-chart">
<div
v-for="(point, index) in salesData"
:key="index"
class="chart-bar"
:style="{
height: `${(point.value / maxSalesValue) * 200}px`,
background: `linear-gradient(to top, #6b46c1, #a78bfa)`
}"
:title="`${point.date}: $${formatNumber(point.value)}`"
></div>
</div>
<div class="chart-labels">
<span
v-for="(point, index) in salesData"
:key="index"
class="chart-label"
>
{{ point.date }}
</span>
</div>
</div>
</el-card>
</el-col>
<!-- 订单状态分布 -->
<el-col :xs="24" :lg="12">
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="chart-header">
<span>{{ t('admin.dashboard.charts.orderStatus') }}</span>
</div>
</template>
<div class="chart-container">
<div class="pie-chart">
<div class="pie-visual"></div>
<div class="pie-center"></div>
</div>
<div class="pie-legend">
<div class="legend-item">
<div class="legend-color completed"></div>
<span>{{ t('admin.orders.statusOptions.completed') }} ({{ orderStats.completed }}%)</span>
</div>
<div class="legend-item">
<div class="legend-color pending"></div>
<span>{{ t('admin.orders.statusOptions.pending') }} ({{ orderStats.pending }}%)</span>
</div>
<div class="legend-item">
<div class="legend-color cancelled"></div>
<span>{{ t('admin.orders.statusOptions.cancelled') }} ({{ orderStats.cancelled }}%)</span>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 最近活动 -->
<div class="recent-activity">
<el-card class="activity-card" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ t('admin.dashboard.charts.recentActivity') }}</span>
<el-button text type="primary" size="small">{{ t('common.viewAll') }}</el-button>
</div>
</template>
<div class="activity-list">
<div
v-for="activity in recentActivities"
:key="activity.id"
class="activity-item"
>
<div class="activity-icon" :class="activity.type">
<el-icon><component :is="getActivityIcon(activity.type)" /></el-icon>
</div>
<div class="activity-content">
<div class="activity-title">{{ activity.title }}</div>
<div class="activity-time">{{ formatTime(activity.time) }}</div>
</div>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import {
UserFilled,
ShoppingCart,
Document,
Money,
User,
Bell,
Setting
} from '@element-plus/icons-vue'
const { t } = useI18n()
// 响应式数据
const chartPeriod = ref('7d')
const stats = ref({
totalUsers: 12568,
totalOrders: 3421,
pendingReviews: 89,
revenue: 125678
})
const salesData = ref([
{ date: '11/12', value: 12000 },
{ date: '11/13', value: 18000 },
{ date: '11/14', value: 15000 },
{ date: '11/15', value: 22000 },
{ date: '11/16', value: 19000 },
{ date: '11/17', value: 25000 },
{ date: '11/18', value: 28000 }
])
const orderStats = ref({
completed: 75,
pending: 20,
cancelled: 5
})
const recentActivities = ref([
{
id: 1,
type: 'user',
title: '新用户注册zhangsan@example.com',
time: new Date(Date.now() - 1000 * 60 * 5)
},
{
id: 2,
type: 'order',
title: '新订单:#ORD-2025-001',
time: new Date(Date.now() - 1000 * 60 * 15)
},
{
id: 3,
type: 'review',
title: '内容审核:用户反馈',
time: new Date(Date.now() - 1000 * 60 * 30)
},
{
id: 4,
type: 'system',
title: '系统更新完成',
time: new Date(Date.now() - 1000 * 60 * 60)
}
])
// 计算属性
const maxSalesValue = computed(() => {
return Math.max(...salesData.value.map(item => item.value))
})
// 方法
const formatNumber = (num) => {
return new Intl.NumberFormat('zh-CN').format(num)
}
const formatTime = (time) => {
const now = new Date()
const diff = Math.floor((now - time) / 1000)
if (diff < 60) return '刚刚'
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`
return `${Math.floor(diff / 86400)}天前`
}
const getActivityIcon = (type) => {
const icons = {
user: User,
order: ShoppingCart,
review: Document,
system: Setting
}
return icons[type] || Bell
}
// 刷新数据方法
const refreshData = () => {
// 模拟数据刷新
stats.value.totalUsers += Math.floor(Math.random() * 10)
stats.value.totalOrders += Math.floor(Math.random() * 5)
stats.value.pendingReviews = Math.floor(Math.random() * 100)
stats.value.revenue += Math.floor(Math.random() * 1000)
// 刷新销售数据
salesData.value = salesData.value.map(item => ({
...item,
value: item.value + Math.floor(Math.random() * 2000) - 1000
}))
}
onMounted(() => {
// 模拟数据刷新
setInterval(() => {
stats.value.totalUsers += Math.floor(Math.random() * 3)
stats.value.totalOrders += Math.floor(Math.random() * 2)
stats.value.pendingReviews = Math.floor(Math.random() * 100)
}, 30000)
})
</script>
<style scoped>
.admin-dashboard {
margin: 0 auto;
}
/* 页面头部样式 */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24px;
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.header-left h2 {
font-size: 28px;
font-weight: 600;
color: #1f2937;
margin: 0 0 8px 0;
}
.subtitle {
color: #6b7280;
font-size: 16px;
margin: 0;
}
.header-actions {
display: flex;
gap: 12px;
}
/* 统计卡片 */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.stat-card {
border: none;
border-radius: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.stat-content {
display: flex;
align-items: center;
gap: 16px;
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
}
.stat-icon.users {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
.stat-icon.orders {
background: linear-gradient(135deg, #10b981, #059669);
}
.stat-icon.reviews {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.stat-icon.revenue {
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
}
.stat-number {
font-size: 24px;
font-weight: 700;
color: #1f2937;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
color: #6b7280;
margin-top: 4px;
}
/* 图表区域 */
.charts-section {
margin-bottom: 32px;
}
.chart-card {
border: none;
border-radius: 12px;
margin-bottom: 24px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #374151;
}
.chart-container {
padding: 16px 0;
}
/* 销售趋势图表 */
.simple-chart {
display: flex;
align-items: end;
justify-content: space-between;
height: 200px;
padding: 0 16px;
margin-bottom: 16px;
}
.chart-bar {
width: 20px;
border-radius: 4px 4px 0 0;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0.8;
}
.chart-bar:hover {
opacity: 1;
transform: scaleY(1.05);
}
.chart-labels {
display: flex;
justify-content: space-between;
padding: 0 16px;
}
.chart-label {
font-size: 12px;
color: #6b7280;
}
/* 饼图 */
.pie-chart {
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
position: relative;
}
.pie-visual {
width: 120px;
height: 120px;
border-radius: 50%;
background: conic-gradient(
#10b981 0deg 270deg,
#f59e0b 270deg 342deg,
#ef4444 342deg 360deg
);
position: relative;
}
.pie-center {
position: absolute;
width: 60px;
height: 60px;
background: white;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.pie-legend {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #374151;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
.legend-color.completed {
background: #10b981;
}
.legend-color.pending {
background: #f59e0b;
}
.legend-color.cancelled {
background: #ef4444;
}
/* 最近活动 */
.activity-card {
border: none;
border-radius: 12px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #374151;
}
.activity-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.activity-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-radius: 8px;
transition: background-color 0.2s ease;
}
.activity-item:hover {
background: #f9fafb;
}
.activity-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
}
.activity-icon.user {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
.activity-icon.order {
background: linear-gradient(135deg, #10b981, #059669);
}
.activity-icon.review {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.activity-icon.system {
background: linear-gradient(135deg, #6b7280, #4b5563);
}
.activity-content {
flex: 1;
}
.activity-title {
font-size: 14px;
font-weight: 500;
color: #374151;
margin-bottom: 2px;
}
.activity-time {
font-size: 12px;
color: #9ca3af;
}
/* 响应式设计 */
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.page-title {
font-size: 24px;
}
.stat-content {
gap: 12px;
}
.stat-icon {
width: 40px;
height: 40px;
font-size: 16px;
}
.stat-number {
font-size: 20px;
}
.simple-chart {
height: 150px;
}
.chart-bar {
width: 16px;
}
}
@media (max-width: 480px) {
.admin-dashboard {
padding: 0;
}
.page-title {
font-size: 20px;
}
.stat-content {
gap: 8px;
}
.stat-icon {
width: 36px;
height: 36px;
font-size: 14px;
}
.stat-number {
font-size: 18px;
}
.simple-chart {
height: 120px;
}
}
</style>