838 lines
20 KiB
Plaintext
838 lines
20 KiB
Plaintext
<template>
|
|
<view class="admin-dashboard">
|
|
<!-- 顶部统计卡片 -->
|
|
<view class="stats-cards">
|
|
<view class="stat-card">
|
|
<view class="stat-icon users">
|
|
<image src="/static/icons/users.png" />
|
|
</view>
|
|
<view class="stat-info">
|
|
<text class="stat-value">{{ statsData.totalUsers }}</text>
|
|
<text class="stat-label">总用户数</text>
|
|
<text class="stat-change positive">+{{ statsData.userGrowth }}%</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="stat-card">
|
|
<view class="stat-icon transactions">
|
|
<image src="/static/icons/transaction.png" />
|
|
</view>
|
|
<view class="stat-info">
|
|
<text class="stat-value">{{ statsData.todayTransactions }}</text>
|
|
<text class="stat-label">今日交易</text>
|
|
<text class="stat-change" :class="{ 'positive': statsData.transactionGrowth > 0 }">
|
|
{{ statsData.transactionGrowth > 0 ? '+' : '' }}{{ statsData.transactionGrowth }}%
|
|
</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="stat-card">
|
|
<view class="stat-icon revenue">
|
|
<image src="/static/icons/revenue.png" />
|
|
</view>
|
|
<view class="stat-info">
|
|
<text class="stat-value">¥{{ statsData.todayRevenue }}</text>
|
|
<text class="stat-label">今日营收</text>
|
|
<text class="stat-change positive">+{{ statsData.revenueGrowth }}%</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="stat-card">
|
|
<view class="stat-icon devices">
|
|
<image src="/static/icons/devices.png" />
|
|
</view>
|
|
<view class="stat-info">
|
|
<text class="stat-value">{{ statsData.onlineDevices }}/{{ statsData.totalDevices }}</text>
|
|
<text class="stat-label">在线设备</text>
|
|
<text class="stat-change" :class="{ 'negative': deviceOfflineRate > 10 }">
|
|
{{ (100 - deviceOfflineRate).toFixed(1) }}%
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 系统状态监控 -->
|
|
<view class="system-status">
|
|
<view class="section-header">
|
|
<text class="section-title">系统状态</text>
|
|
<text class="last-update">更新时间: {{ formatTime(lastUpdateTime) }}</text>
|
|
</view>
|
|
|
|
<view class="status-grid">
|
|
<view class="status-item" v-for="service in systemServices" :key="service.name">
|
|
<view class="service-info">
|
|
<view class="service-status" :class="service.status">
|
|
<view class="status-dot"></view>
|
|
</view>
|
|
<text class="service-name">{{ service.name }}</text>
|
|
</view>
|
|
<view class="service-metrics">
|
|
<text class="metric-item">CPU: {{ service.cpu }}%</text>
|
|
<text class="metric-item">内存: {{ service.memory }}%</text>
|
|
<text class="metric-item">响应: {{ service.responseTime }}ms</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 实时告警 -->
|
|
<view class="alerts-section" v-if="alerts.length > 0">
|
|
<view class="section-header">
|
|
<text class="section-title">实时告警</text>
|
|
<text class="alert-count">{{ alerts.length }}条未处理</text>
|
|
</view>
|
|
|
|
<view class="alerts-list">
|
|
<view class="alert-item"
|
|
v-for="alert in alerts"
|
|
:key="alert.id"
|
|
:class="alert.level"
|
|
@click="handleAlert(alert)">
|
|
<view class="alert-icon">
|
|
<image :src="getAlertIcon(alert.level)" />
|
|
</view>
|
|
<view class="alert-content">
|
|
<text class="alert-title">{{ alert.title }}</text>
|
|
<text class="alert-desc">{{ alert.description }}</text>
|
|
<text class="alert-time">{{ formatTime(alert.time) }}</text>
|
|
</view>
|
|
<view class="alert-actions">
|
|
<button class="action-btn" @click.stop="resolveAlert(alert.id)">处理</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 快速操作 -->
|
|
<view class="quick-operations">
|
|
<view class="section-title">快速操作</view>
|
|
<view class="operations-grid">
|
|
<view class="operation-item" @click="goToUserManagement">
|
|
<image class="operation-icon" src="/static/icons/user-manage.png" />
|
|
<text class="operation-text">用户管理</text>
|
|
</view>
|
|
<view class="operation-item" @click="goToCardManagement">
|
|
<image class="operation-icon" src="/static/icons/card-manage.png" />
|
|
<text class="operation-text">卡片管理</text>
|
|
</view>
|
|
<view class="operation-item" @click="goToFinancialManagement">
|
|
<image class="operation-icon" src="/static/icons/finance.png" />
|
|
<text class="operation-text">财务管理</text>
|
|
</view>
|
|
<view class="operation-item" @click="goToDeviceManagement">
|
|
<image class="operation-icon" src="/static/icons/device-manage.png" />
|
|
<text class="operation-text">设备管理</text>
|
|
</view>
|
|
<view class="operation-item" @click="goToReports">
|
|
<image class="operation-icon" src="/static/icons/reports.png" />
|
|
<text class="operation-text">数据报表</text>
|
|
</view>
|
|
<view class="operation-item" @click="goToSystemConfig">
|
|
<image class="operation-icon" src="/static/icons/settings.png" />
|
|
<text class="operation-text">系统配置</text>
|
|
</view>
|
|
<view class="operation-item" @click="goToSecurityMonitor">
|
|
<image class="operation-icon" src="/static/icons/security.png" />
|
|
<text class="operation-text">安全监控</text>
|
|
</view>
|
|
<view class="operation-item emergency" @click="goToEmergencyControl">
|
|
<image class="operation-icon" src="/static/icons/emergency.png" />
|
|
<text class="operation-text">应急控制</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 数据概览图表 -->
|
|
<view class="charts-section">
|
|
<view class="section-title">数据概览</view>
|
|
|
|
<view class="chart-container">
|
|
<view class="chart-header">
|
|
<text class="chart-title">交易趋势</text>
|
|
<view class="chart-filters">
|
|
<text class="filter-item"
|
|
:class="{ 'active': chartPeriod === 'day' }"
|
|
@click="changeChartPeriod('day')">今日</text>
|
|
<text class="filter-item"
|
|
:class="{ 'active': chartPeriod === 'week' }"
|
|
@click="changeChartPeriod('week')">本周</text>
|
|
<text class="filter-item"
|
|
:class="{ 'active': chartPeriod === 'month' }"
|
|
@click="changeChartPeriod('month')">本月</text>
|
|
</view>
|
|
</view>
|
|
<canvas canvas-id="transactionChart" class="chart-canvas"></canvas>
|
|
</view>
|
|
|
|
<view class="mini-charts">
|
|
<view class="mini-chart">
|
|
<text class="mini-chart-title">消费分布</text>
|
|
<canvas canvas-id="consumptionChart" class="mini-chart-canvas"></canvas>
|
|
</view>
|
|
<view class="mini-chart">
|
|
<text class="mini-chart-title">设备状态</text>
|
|
<canvas canvas-id="deviceChart" class="mini-chart-canvas"></canvas>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 系统信息 -->
|
|
<view class="system-info">
|
|
<view class="section-title">系统信息</view>
|
|
<view class="info-grid">
|
|
<view class="info-item">
|
|
<text class="info-label">系统版本</text>
|
|
<text class="info-value">v2.1.0</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="info-label">数据库</text>
|
|
<text class="info-value">PostgreSQL 14.2</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="info-label">在线时长</text>
|
|
<text class="info-value">{{ systemUptime }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="info-label">存储使用</text>
|
|
<text class="info-value">{{ storageUsage }}%</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
data() {
|
|
return {
|
|
statsData: {
|
|
totalUsers: 1247,
|
|
userGrowth: 12.5,
|
|
todayTransactions: 3652,
|
|
transactionGrowth: 8.3,
|
|
todayRevenue: '45,680',
|
|
revenueGrowth: 15.2,
|
|
onlineDevices: 285,
|
|
totalDevices: 310
|
|
},
|
|
systemServices: [
|
|
{
|
|
name: '核心服务',
|
|
status: 'healthy',
|
|
cpu: 35,
|
|
memory: 68,
|
|
responseTime: 120
|
|
},
|
|
{
|
|
name: 'NFC服务',
|
|
status: 'healthy',
|
|
cpu: 28,
|
|
memory: 45,
|
|
responseTime: 95
|
|
},
|
|
{
|
|
name: '支付服务',
|
|
status: 'warning',
|
|
cpu: 78,
|
|
memory: 82,
|
|
responseTime: 350
|
|
},
|
|
{
|
|
name: '门禁服务',
|
|
status: 'healthy',
|
|
cpu: 22,
|
|
memory: 38,
|
|
responseTime: 85
|
|
}
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 1,
|
|
level: 'warning',
|
|
title: '支付服务响应缓慢',
|
|
description: '支付服务平均响应时间超过300ms',
|
|
time: new Date(Date.now() - 300000)
|
|
},
|
|
{
|
|
id: 2,
|
|
level: 'error',
|
|
title: '设备离线',
|
|
description: '饭堂POS机#003失去连接',
|
|
time: new Date(Date.now() - 600000)
|
|
}
|
|
],
|
|
lastUpdateTime: new Date(),
|
|
chartPeriod: 'day',
|
|
systemUptime: '15天8小时',
|
|
storageUsage: 67
|
|
}
|
|
},
|
|
computed: {
|
|
deviceOfflineRate() {
|
|
return ((this.statsData.totalDevices - this.statsData.onlineDevices) / this.statsData.totalDevices * 100)
|
|
}
|
|
},
|
|
onLoad() {
|
|
this.initDashboard()
|
|
},
|
|
onPullDownRefresh() {
|
|
this.refreshDashboard()
|
|
},
|
|
methods: {
|
|
initDashboard() {
|
|
this.loadStatsData()
|
|
this.loadSystemStatus()
|
|
this.loadAlerts()
|
|
this.initCharts()
|
|
this.startRealTimeUpdate()
|
|
},
|
|
loadStatsData() {
|
|
uni.request({
|
|
url: '/api/v1/admin/dashboard-stats',
|
|
success: (res) => {
|
|
this.statsData = res.data
|
|
}
|
|
})
|
|
},
|
|
loadSystemStatus() {
|
|
uni.request({
|
|
url: '/api/v1/admin/system-status',
|
|
success: (res) => {
|
|
this.systemServices = res.data
|
|
}
|
|
})
|
|
},
|
|
loadAlerts() {
|
|
uni.request({
|
|
url: '/api/v1/admin/alerts',
|
|
success: (res) => {
|
|
this.alerts = res.data
|
|
}
|
|
})
|
|
},
|
|
refreshDashboard() {
|
|
Promise.all([
|
|
this.loadStatsData(),
|
|
this.loadSystemStatus(),
|
|
this.loadAlerts()
|
|
]).then(() => {
|
|
this.lastUpdateTime = new Date()
|
|
uni.stopPullDownRefresh()
|
|
})
|
|
},
|
|
startRealTimeUpdate() {
|
|
// 每30秒更新一次数据
|
|
setInterval(() => {
|
|
this.loadStatsData()
|
|
this.loadSystemStatus()
|
|
this.lastUpdateTime = new Date()
|
|
}, 30000)
|
|
},
|
|
initCharts() {
|
|
// 初始化图表
|
|
this.drawTransactionChart()
|
|
this.drawConsumptionChart()
|
|
this.drawDeviceChart()
|
|
},
|
|
drawTransactionChart() {
|
|
// 绘制交易趋势图
|
|
const ctx = uni.createCanvasContext('transactionChart', this)
|
|
// 这里实现具体的图表绘制逻辑
|
|
ctx.draw()
|
|
},
|
|
drawConsumptionChart() {
|
|
// 绘制消费分布图
|
|
const ctx = uni.createCanvasContext('consumptionChart', this)
|
|
// 实现饼图或柱状图
|
|
ctx.draw()
|
|
},
|
|
drawDeviceChart() {
|
|
// 绘制设备状态图
|
|
const ctx = uni.createCanvasContext('deviceChart', this)
|
|
// 实现状态分布图
|
|
ctx.draw()
|
|
},
|
|
changeChartPeriod(period) {
|
|
this.chartPeriod = period
|
|
this.drawTransactionChart()
|
|
},
|
|
handleAlert(alert) {
|
|
uni.navigateTo({
|
|
url: `/pages/mall/nfc/admin/alert-detail?id=${alert.id}`
|
|
})
|
|
},
|
|
resolveAlert(alertId) {
|
|
uni.request({
|
|
url: '/api/v1/admin/resolve-alert',
|
|
method: 'POST',
|
|
data: { alertId },
|
|
success: () => {
|
|
this.alerts = this.alerts.filter(alert => alert.id !== alertId)
|
|
uni.showToast({
|
|
title: '告警已处理',
|
|
icon: 'success'
|
|
})
|
|
}
|
|
})
|
|
},
|
|
getAlertIcon(level) {
|
|
const icons = {
|
|
info: '/static/icons/info.png',
|
|
warning: '/static/icons/warning.png',
|
|
error: '/static/icons/error.png',
|
|
critical: '/static/icons/critical.png'
|
|
}
|
|
return icons[level] || icons.info
|
|
},
|
|
// 导航方法
|
|
goToUserManagement() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/user-management' })
|
|
},
|
|
goToCardManagement() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/card-management' })
|
|
},
|
|
goToFinancialManagement() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/financial-management' })
|
|
},
|
|
goToDeviceManagement() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/device-management' })
|
|
},
|
|
goToReports() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/data-reports' })
|
|
},
|
|
goToSystemConfig() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/system-config' })
|
|
},
|
|
goToSecurityMonitor() {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/security-monitor' })
|
|
},
|
|
goToEmergencyControl() {
|
|
uni.showModal({
|
|
title: '应急控制',
|
|
content: '即将进入应急控制模式,请确认您有相应权限',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
uni.navigateTo({ url: '/pages/mall/nfc/admin/emergency-control' })
|
|
}
|
|
}
|
|
})
|
|
},
|
|
formatTime(time) {
|
|
const now = new Date()
|
|
const diff = now - time
|
|
const minutes = Math.floor(diff / 60000)
|
|
|
|
if (minutes < 1) return '刚刚'
|
|
if (minutes < 60) return `${minutes}分钟前`
|
|
|
|
const hours = Math.floor(minutes / 60)
|
|
if (hours < 24) return `${hours}小时前`
|
|
|
|
return time.toLocaleDateString()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.admin-dashboard {
|
|
padding: 20rpx;
|
|
background-color: #f8f9fa;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.stats-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 20rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 80rpx;
|
|
height: 80rpx;
|
|
border-radius: 16rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 24rpx;
|
|
}
|
|
|
|
.stat-icon.users {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
|
|
.stat-icon.transactions {
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
}
|
|
|
|
.stat-icon.revenue {
|
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
}
|
|
|
|
.stat-icon.devices {
|
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
}
|
|
|
|
.stat-icon image {
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
}
|
|
|
|
.stat-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.stat-change {
|
|
font-size: 20rpx;
|
|
padding: 4rpx 8rpx;
|
|
border-radius: 8rpx;
|
|
align-self: flex-start;
|
|
}
|
|
|
|
.stat-change.positive {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.stat-change.negative {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
|
|
.system-status, .alerts-section, .quick-operations,
|
|
.charts-section, .system-info {
|
|
background: white;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.last-update {
|
|
font-size: 20rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.alert-count {
|
|
font-size: 24rpx;
|
|
color: #dc3545;
|
|
background: #f8d7da;
|
|
padding: 8rpx 16rpx;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
.status-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.status-item {
|
|
padding: 20rpx;
|
|
border: 1rpx solid #eee;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
.service-info {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.service-status {
|
|
width: 16rpx;
|
|
height: 16rpx;
|
|
border-radius: 50%;
|
|
margin-right: 12rpx;
|
|
position: relative;
|
|
}
|
|
|
|
.service-status.healthy {
|
|
background: #28a745;
|
|
}
|
|
|
|
.service-status.warning {
|
|
background: #ffc107;
|
|
}
|
|
|
|
.service-status.error {
|
|
background: #dc3545;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 50%;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
transform: scale(1.2);
|
|
opacity: 0.7;
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.service-name {
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.service-metrics {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8rpx;
|
|
}
|
|
|
|
.metric-item {
|
|
font-size: 20rpx;
|
|
color: #666;
|
|
}
|
|
|
|
.alerts-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16rpx;
|
|
}
|
|
|
|
.alert-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx;
|
|
border-radius: 12rpx;
|
|
border-left: 6rpx solid;
|
|
}
|
|
|
|
.alert-item.warning {
|
|
background: #fff3cd;
|
|
border-left-color: #ffc107;
|
|
}
|
|
|
|
.alert-item.error {
|
|
background: #f8d7da;
|
|
border-left-color: #dc3545;
|
|
}
|
|
|
|
.alert-icon {
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
margin-right: 20rpx;
|
|
}
|
|
|
|
.alert-icon image {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.alert-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.alert-title {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: bold;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.alert-desc {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.alert-time {
|
|
font-size: 20rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.alert-actions {
|
|
margin-left: 20rpx;
|
|
}
|
|
|
|
.action-btn {
|
|
background: #007AFF;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8rpx;
|
|
padding: 12rpx 20rpx;
|
|
font-size: 24rpx;
|
|
}
|
|
|
|
.operations-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.operation-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 30rpx 20rpx;
|
|
border: 1rpx solid #eee;
|
|
border-radius: 12rpx;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.operation-item:hover {
|
|
border-color: #007AFF;
|
|
background: #f8f9ff;
|
|
}
|
|
|
|
.operation-item.emergency {
|
|
border-color: #dc3545;
|
|
background: #fff5f5;
|
|
}
|
|
|
|
.operation-icon {
|
|
width: 48rpx;
|
|
height: 48rpx;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.operation-text {
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
text-align: center;
|
|
}
|
|
|
|
.chart-container {
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.chart-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.chart-filters {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.filter-item {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
padding: 8rpx 16rpx;
|
|
border-radius: 8rpx;
|
|
border: 1rpx solid #ddd;
|
|
}
|
|
|
|
.filter-item.active {
|
|
background: #007AFF;
|
|
color: white;
|
|
border-color: #007AFF;
|
|
}
|
|
|
|
.chart-canvas {
|
|
width: 100%;
|
|
height: 400rpx;
|
|
}
|
|
|
|
.mini-charts {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.mini-chart {
|
|
text-align: center;
|
|
}
|
|
|
|
.mini-chart-title {
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
margin-bottom: 16rpx;
|
|
display: block;
|
|
}
|
|
|
|
.mini-chart-canvas {
|
|
width: 100%;
|
|
height: 200rpx;
|
|
}
|
|
|
|
.info-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 20rpx;
|
|
border: 1rpx solid #eee;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|