Files
abot/admin/dashboard/templates/index.html
2025-04-11 18:00:20 +08:00

585 lines
24 KiB
HTML
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.
{% extends "base.html" %}
{% block title %}首页概览 - 机器人管理后台{% endblock %}
{% block content %}
<!-- 首页概览 -->
<div>
<el-row :gutter="20">
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>{% raw %}{{ currentUser.data.nickname }}{% endraw %}</span>
</div>
<div v-if="currentUser.success" style="display: flex; align-items: center;">
<div v-if="currentUser.data.avatar" style="margin-right: 15px;">
<img :src="'/static/images/' + currentUser.data.avatar" style="width: 60px; height: 60px; border-radius: 50%;" />
</div>
<div>
<div style="font-size: 24px; color: #666;">{% raw %}{{ currentUser.data.wx_id }}{% endraw %}</div>
</div>
</div>
<div v-else style="text-align: center; color: #999;">
<i class="el-icon-warning" style="font-size: 24px;"></i>
<div>{% raw %}{{ currentUser.message || '未获取到用户信息' }}{% endraw %}</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>总调用次数</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ totalCalls }}{% endraw %}
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>成功率</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ successRate.toFixed(2) }}{% endraw %}%
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>活跃用户数</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ activeUsers }}{% endraw %}
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>活跃群组数</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ activeGroups }}{% endraw %}
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>平均响应时间</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ avgResponseTime.toFixed(2) }}{% endraw %} ms
</div>
</el-card>
</el-col>
</el-row>
<!-- 添加热门用户、群组和插件 -->
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="8">
<el-card shadow="hover">
<div slot="header">
<span>热门用户</span>
</div>
<el-table :data="topUsers" style="width: 100%">
<!-- 修改将用户ID改为用户信息使用固定像素宽度 -->
<el-table-column label="用户信息" min-width="180">
<template slot-scope="scope">
{% raw %}{{ scope.row.user_name || scope.row.user_id }} ({{ scope.row.user_id }}){% endraw %}
</template>
</el-table-column>
<el-table-column prop="total_calls" label="调用次数" width="80" align="center"></el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover">
<div slot="header">
<span>热门群组</span>
</div>
<el-table :data="topGroups" style="width: 100%">
<!-- 修改将群组ID改为群组信息使用固定像素宽度 -->
<el-table-column label="群组信息" min-width="180">
<template slot-scope="scope">
{% raw %}{{ scope.row.group_name || scope.row.group_id }} ({{ scope.row.group_id }}){% endraw %}
</template>
</el-table-column>
<el-table-column prop="total_calls" label="调用次数" width="80" align="center"></el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover">
<div slot="header">
<span>热门插件</span>
</div>
<el-table :data="topPlugins" style="width: 100%">
<el-table-column prop="plugin_name" label="插件名称" min-width="180"></el-table-column>
<el-table-column prop="total_calls" label="调用次数" width="80" align="center"></el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="12">
<div class="chart-container">
<h3>插件使用排行</h3>
<canvas id="pluginChart" width="400" height="150"></canvas>
</div>
</el-col>
<el-col :span="12">
<div class="chart-container">
<h3>成功率分析</h3>
<canvas id="successRateChart" width="400" height="150"></canvas>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="24">
<div class="chart-container">
<h3>使用趋势</h3>
<canvas id="trendChart" width="800" height="150"></canvas>
</div>
</el-col>
</el-row>
</div>
<!-- 在适当位置添加系统信息卡片 -->
<div class="col-lg-4">
<div class="card">
<div class="card-body">
<!-- 将原来的系统状态卡片替换为以下内容 -->
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="24">
<el-card shadow="hover">
<div slot="header">
<span>系统状态</span>
</div>
<el-row :gutter="20">
<el-col :span="8">
<div class="system-info-item">
<div class="system-info-row">
<span>操作系统:</span>
<span>{% raw %}{{ systemInfo.os }} {{ systemInfo.os_version }}{% endraw %}</span>
</div>
<div class="system-info-row">
<span>Python版本:</span>
<span>{% raw %}{{ systemInfo.python_version }}{% endraw %}</span>
</div>
<div class="system-info-row">
<span>运行时间:</span>
<span>{% raw %}{{ formattedUptime }}{% endraw %}</span>
</div>
</div>
</el-col>
<el-col :span="16">
<div class="system-info-item">
<div class="system-info-row">
<span>CPU使用率:</span>
<span>{% raw %}{{ systemInfo.cpu_usage }}{% endraw %}%</span>
</div>
<el-progress
:percentage="systemInfo.cpu_usage"
:status="getProgressStatus(systemInfo.cpu_usage)"
:stroke-width="8">
</el-progress>
<div class="system-info-row">
<span>内存使用率:</span>
<span>{% raw %}{{ systemInfo.memory_usage }}{% endraw %}%</span>
</div>
<el-progress
:percentage="systemInfo.memory_usage"
:status="getProgressStatus(systemInfo.memory_usage)"
:stroke-width="8">
</el-progress>
<div class="system-info-row">
<span>磁盘使用率:</span>
<span>{% raw %}{{ systemInfo.disk_usage }}{% endraw %}%</span>
</div>
<el-progress
:percentage="systemInfo.disk_usage"
:status="getProgressStatus(systemInfo.disk_usage)"
:stroke-width="8">
</el-progress>
</div>
</el-col>
</el-row>
<div class="system-update-time">最后更新: {% raw %}{{ systemInfo.timestamp }}{% endraw %}</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
totalCalls: 0,
successRate: 0,
activeUsers: 0,
activeGroups: 0,
avgResponseTime: 0, // 确保这里初始化为数字
topUsers: [],
topGroups: [],
topPlugins: [],
pluginStats: [],
charts: {}, // 添加charts对象
// 添加当前用户信息对象
currentUser: {
success: false,
message: '加载中...',
data: {}
},
// 添加系统信息相关数据
systemInfo: {
os: '加载中...',
os_version: '',
python_version: '加载中...',
cpu_usage: 0,
memory_usage: 0,
disk_usage: 0,
uptime: 0,
timestamp: '-'
}
}
},
computed: {
// 计算属性:格式化的运行时间
formattedUptime() {
const seconds = this.systemInfo.uptime;
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
let result = '';
if (days > 0) result += days + '天 ';
if (hours > 0 || days > 0) result += hours + '小时 ';
result += minutes + '分钟';
return result;
}
},
mounted() {
this.currentView = '1';
this.loadData();
// 加载系统信息
this.loadSystemInfo();
// 加载当前用户信息
this.loadCurrentUserInfo();
// 设置定时刷新系统信息每30秒
this.systemInfoTimer = setInterval(this.loadSystemInfo, 30000);
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.systemInfoTimer) {
clearInterval(this.systemInfoTimer);
}
},
methods: {
loadData() {
const days = parseInt(this.timeRange);
this.loadDashboardSummary(days);
this.loadPluginStats(days);
this.loadPluginTrend(days);
},
// 添加加载系统信息的方法
loadSystemInfo() {
axios.get('/api/system_info')
.then(response => {
if (response.data.success) {
this.systemInfo = response.data.data;
}
})
.catch(error => {
console.error('加载系统信息出错:', error);
});
},
// 添加获取当前用户信息的方法
loadCurrentUserInfo() {
axios.get('/api/current_user_info')
.then(response => {
this.currentUser = response.data;
})
.catch(error => {
console.error('加载当前用户信息出错:', error);
this.currentUser = {
success: false,
message: '获取用户信息失败',
data: {}
};
});
},
// 添加获取进度条状态的方法
getProgressStatus(value) {
if (value > 70) {
return 'exception'; // 红色 (高负载)
} else if (value > 50) {
return 'warning'; // 黄色 (中等负载)
} else {
return 'success'; // 绿色 (低负载)
}
},
// 删除不再使用的方法
// updateSystemInfoUI() { ... },
// setProgressColor() { ... },
// formatUptime() { ... },
loadDashboardSummary(days) {
axios.get(`/api/dashboard_summary?days=${days}`)
.then(response => {
if (response.data.success) {
const data = response.data.data;
this.totalCalls = parseInt(data.total_calls) || 0;
this.successRate = parseFloat(data.success_rate) || 0;
this.activeUsers = data.active_users || 0;
this.activeGroups = data.active_groups || 0;
this.avgResponseTime = parseFloat(data.avg_response_time) || 0;
this.topUsers = data.top_users || [];
this.topGroups = data.top_groups || [];
this.topPlugins = data.top_plugins || [];
}
})
.catch(error => {
console.error('加载仪表盘摘要数据出错:', error);
this.$message.error('加载仪表盘摘要数据出错');
});
},
loadPluginStats(days) {
axios.get(`/api/plugin_stats?days=${days}`)
.then(response => {
if (response.data.success) {
this.pluginStats = response.data.data || [];
this.$nextTick(() => {
this.renderPluginChart();
this.renderSuccessRateChart();
});
}
})
.catch(error => {
console.error('加载插件统计数据出错:', error);
this.$message.error('加载插件统计数据出错');
});
},
loadPluginTrend(days) {
axios.get(`/api/plugin_trend?days=${days}`)
.then(response => {
if (response.data.success) {
const trendData = response.data.data || [];
this.$nextTick(() => {
this.renderTrendChart(trendData);
});
}
})
.catch(error => {
console.error('加载插件趋势数据出错:', error);
this.$message.error('加载插件趋势数据出错');
});
},
renderPluginChart() {
const ctx = document.getElementById('pluginChart').getContext('2d');
// 销毁旧图表
if (this.charts.pluginChart) {
this.charts.pluginChart.destroy();
}
// 准备数据
const sortedData = [...this.pluginStats].sort((a, b) => b.total_calls - a.total_calls).slice(0, 10);
// 修改这里:将插件名称和指令组合作为标签
const labels = sortedData.map(item => `${item.plugin_name}(${item.command})`);
const data = sortedData.map(item => item.total_calls);
// 创建新图表
this.charts.pluginChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '调用次数',
data: data,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
},
renderSuccessRateChart() {
const ctx = document.getElementById('successRateChart').getContext('2d');
// 销毁旧图表
if (this.charts.successRateChart) {
this.charts.successRateChart.destroy();
}
// 准备数据
const sortedData = [...this.pluginStats]
.filter(item => item.total_calls > 0)
.sort((a, b) => (b.success_calls / b.total_calls) - (a.success_calls / a.total_calls))
.slice(0, 10);
// 修改这里:将插件名称和指令组合作为标签
const labels = sortedData.map(item => `${item.plugin_name}(${item.command})`);
const data = sortedData.map(item => (item.success_calls / item.total_calls) * 100);
// 创建新图表
this.charts.successRateChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '成功率(%)',
data: data,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
},
renderTrendChart(trendData) {
const ctx = document.getElementById('trendChart').getContext('2d');
// 销毁旧图表
if (this.charts && this.charts.trendChart) {
this.charts.trendChart.destroy();
}
// 确保charts对象存在
if (!this.charts) {
this.charts = {};
}
// 准备数据 - 修改字段名匹配和数据类型转换
const labels = trendData.map(item => item.date); // 使用date而不是stat_date
const totalData = trendData.map(item => parseInt(item.total_calls) || 0); // 确保转换为数字
const successData = trendData.map(item => parseInt(item.success_calls) || 0);
const errorData = trendData.map(item => parseInt(item.failed_calls) || 0); // 使用failed_calls而不是error_calls
console.log('处理后的趋势数据:', { labels, totalData, successData, errorData });
// 创建新图表
this.charts.trendChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '总调用',
data: totalData,
fill: false,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
tension: 0.1
},
{
label: '成功调用',
data: successData,
fill: false,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
tension: 0.1
},
{
label: '失败调用',
data: errorData,
fill: false,
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgba(255, 99, 132, 1)',
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}
});
</script>
{% endblock %}
{% block styles %}
<style>
.stats-card {
margin-bottom: 15px;
height: 120px;
}
.chart-container {
margin-bottom: 20px;
padding: 10px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.chart-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
color: #606266;
}
/* 系统状态卡片样式 */
.system-info-item {
margin-bottom: 15px;
}
.system-info-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.system-update-time {
text-align: right;
font-size: 12px;
color: #909399;
margin-top: 10px;
}
/* 进度条样式调整 */
.el-progress {
margin-bottom: 15px;
}
</style>
{% endblock %}