UI改版测试V0
This commit is contained in:
@@ -3,253 +3,34 @@
|
||||
{% block title %}插件统计 - 机器人管理后台{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- 插件统计 -->
|
||||
<div>
|
||||
<el-row {% raw %}:gutter="20"{% endraw %}>
|
||||
<el-col {% raw %}:span="24"{% endraw %}>
|
||||
<el-card shadow="hover">
|
||||
<div slot="header">
|
||||
<span>插件使用统计</span>
|
||||
</div>
|
||||
<el-table {% raw %}:data="pluginStats"{% endraw %} style="width: 100%" border>
|
||||
<el-table-column prop="plugin_name" label="插件名称"></el-table-column>
|
||||
<el-table-column prop="command" label="触发指令"></el-table-column>
|
||||
<el-table-column prop="total_calls" label="调用次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.total_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="success_calls" label="成功次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.success_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="failed_calls" label="失败次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.failed_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成功率" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.total_calls) > 0 ? ((parseInt(scope.row.success_calls) / parseInt(scope.row.total_calls)) * 100).toFixed(2) : '100.00' }}{% endraw %}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平均响应时间" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ (scope.row.avg_process_time || 0).toFixed(2) }}{% endraw %}ms
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" {% raw %}@click="viewPluginTrend(scope.row)"{% endraw %}>查看趋势</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table {% raw %}:data="pluginUsers"{% endraw %} style="width: 100%" border v-if="showUserStats">
|
||||
<el-table-column label="用户">
|
||||
<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="调用次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.total_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="success_calls" label="成功次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.success_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="failed_calls" label="失败次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.failed_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成功率" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.total_calls) > 0 ? ((parseInt(scope.row.success_calls) / parseInt(scope.row.total_calls)) * 100).toFixed(2) : '100.00' }}{% endraw %}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平均响应时间" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ (scope.row.avg_process_time || 0).toFixed(2) }}{% endraw %}ms
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" {% raw %}@click="viewPluginTrend(scope.row)"{% endraw %}>查看趋势</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table {% raw %}:data="pluginGroups"{% endraw %} style="width: 100%" border v-if="showGroupStats">
|
||||
<el-table-column label="群组">
|
||||
<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="调用次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.total_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="success_calls" label="成功次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.success_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="failed_calls" label="失败次数" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.failed_calls) || 0 }}{% endraw %}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成功率" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ parseInt(scope.row.total_calls) > 0 ? ((parseInt(scope.row.success_calls) / parseInt(scope.row.total_calls)) * 100).toFixed(2) : '100.00' }}{% endraw %}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平均响应时间" sortable>
|
||||
<template slot-scope="scope">
|
||||
{% raw %}{{ (scope.row.avg_process_time || 0).toFixed(2) }}{% endraw %}ms
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" {% raw %}@click="viewPluginTrend(scope.row)"{% endraw %}>查看趋势</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<div class="page-shell stats-page">
|
||||
<div class="page-hero"><div class="page-hero-copy"><div class="page-eyebrow">Plugins Analytics</div><h1>插件统计</h1><p>查看插件调用规模、成功率与响应效率,快速定位高频能力模块。</p></div></div>
|
||||
<el-row :gutter="16" class="overview-grid">
|
||||
<el-col :span="8"><el-card class="overview-card overview-card--primary"><div class="overview-label">插件总数</div><div class="overview-value">{% raw %}{{ pluginStats.length }}{% endraw %}</div><div class="overview-note">当前统计周期内被触发的插件</div></el-card></el-col>
|
||||
<el-col :span="8"><el-card class="overview-card"><div class="overview-label">总调用次数</div><div class="overview-value">{% raw %}{{ totalCalls }}{% endraw %}</div><div class="overview-note">统计周期内插件调用总和</div></el-card></el-col>
|
||||
<el-col :span="8"><el-card class="overview-card overview-card--soft"><div class="overview-label">平均成功率</div><div class="overview-value">{% raw %}{{ averageSuccessRate }}{% endraw %}%</div><div class="overview-note">插件整体稳定性表现</div></el-card></el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 插件趋势对话框 -->
|
||||
<el-dialog title="插件使用趋势" {% raw %}:visible.sync="pluginTrendVisible"{% endraw %} width="70%">
|
||||
<div class="chart-container">
|
||||
<h3>{% raw %}{{ selectedPlugin ? selectedPlugin.plugin_name : '' }}{% endraw %} 使用趋势</h3>
|
||||
<canvas id="pluginTrendChart" width="800" height="400"></canvas>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-card class="workspace-card" shadow="hover">
|
||||
<div slot="header" class="workspace-header"><div><h3>插件使用统计</h3><p>按插件查看调用、成功、失败与平均响应时间。</p></div></div>
|
||||
<el-table :data="pluginStats" style="width:100%">
|
||||
<el-table-column label="插件信息" min-width="260"><template slot-scope="scope"><div class="entity-cell"><div class="entity-badge">{% raw %}{{ scope.$index + 1 }}{% endraw %}</div><div class="entity-copy"><div class="entity-title">{% raw %}{{ scope.row.plugin_name }}{% endraw %}</div><div class="entity-subtitle">指令:{% raw %}{{ scope.row.command }}{% endraw %}</div></div></div></template></el-table-column>
|
||||
<el-table-column prop="total_calls" label="调用次数" sortable width="110"><template slot-scope="scope">{% raw %}{{ parseInt(scope.row.total_calls) || 0 }}{% endraw %}</template></el-table-column>
|
||||
<el-table-column prop="success_calls" label="成功次数" sortable width="110"><template slot-scope="scope">{% raw %}{{ parseInt(scope.row.success_calls) || 0 }}{% endraw %}</template></el-table-column>
|
||||
<el-table-column prop="failed_calls" label="失败次数" sortable width="110"><template slot-scope="scope">{% raw %}{{ parseInt(scope.row.failed_calls) || 0 }}{% endraw %}</template></el-table-column>
|
||||
<el-table-column label="成功率" sortable width="110"><template slot-scope="scope">{% raw %}{{ parseInt(scope.row.total_calls) > 0 ? ((parseInt(scope.row.success_calls) / parseInt(scope.row.total_calls)) * 100).toFixed(2) : '100.00' }}{% endraw %}%</template></el-table-column>
|
||||
<el-table-column label="平均响应时间" sortable width="140"><template slot-scope="scope">{% raw %}{{ (scope.row.avg_process_time || 0).toFixed(2) }}{% endraw %}ms</template></el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center"><template slot-scope="scope"><el-button size="mini" type="primary" plain @click="viewPluginTrend(scope.row)">查看趋势</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-dialog title="插件使用趋势" :visible.sync="pluginTrendVisible" width="70%"><div class="chart-shell"><div class="chart-heading">{% raw %}{{ selectedPlugin ? selectedPlugin.plugin_name : '' }}{% endraw %} 使用趋势</div><canvas id="pluginTrendChart" width="800" height="360"></canvas></div></el-dialog>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
mixins: [baseApp],
|
||||
data() {
|
||||
return {
|
||||
pluginStats: [],
|
||||
pluginTrendVisible: false,
|
||||
selectedPlugin: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.currentView = '2';
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
const days = parseInt(this.timeRange);
|
||||
this.loadPluginStats(days);
|
||||
},
|
||||
loadPluginStats(days) {
|
||||
axios.get(`/api/plugin_stats?days=${days}`)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
this.pluginStats = response.data.data || [];
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('加载插件统计数据出错:', error);
|
||||
this.$message.error('加载插件统计数据出错');
|
||||
});
|
||||
},
|
||||
viewPluginTrend(plugin) {
|
||||
this.selectedPlugin = plugin;
|
||||
this.pluginTrendVisible = true;
|
||||
|
||||
// 加载插件趋势数据
|
||||
const days = parseInt(this.timeRange);
|
||||
axios.get(`/api/plugin_trend?days=${days}&plugin_name=${plugin.plugin_name}`)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const trendData = response.data.data || [];
|
||||
this.$nextTick(() => {
|
||||
this.renderPluginTrendChart(trendData, plugin.plugin_name);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('加载插件趋势数据出错:', error);
|
||||
this.$message.error('加载插件趋势数据出错');
|
||||
});
|
||||
},
|
||||
renderPluginTrendChart(trendData, pluginName) {
|
||||
const ctx = document.getElementById('pluginTrendChart').getContext('2d');
|
||||
|
||||
// 销毁旧图表
|
||||
if (this.charts && this.charts.pluginTrendChart) {
|
||||
this.charts.pluginTrendChart.destroy();
|
||||
}
|
||||
|
||||
// 确保charts对象存在
|
||||
if (!this.charts) {
|
||||
this.charts = {};
|
||||
}
|
||||
|
||||
// 准备数据
|
||||
const labels = trendData.map(item => item.date || item.stat_date);
|
||||
const totalData = trendData.map(item => parseInt(item.total_calls) || 0);
|
||||
const successData = trendData.map(item => parseInt(item.success_calls) || 0);
|
||||
const failedData = trendData.map(item => parseInt(item.failed_calls) || 0);
|
||||
|
||||
// 创建新图表
|
||||
this.charts.pluginTrendChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: `${pluginName} 总调用`,
|
||||
data: totalData,
|
||||
fill: false,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
tension: 0.1
|
||||
},
|
||||
{
|
||||
label: `${pluginName} 成功调用`,
|
||||
data: successData,
|
||||
fill: false,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.6)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
tension: 0.1
|
||||
},
|
||||
{
|
||||
label: `${pluginName} 失败调用`,
|
||||
data: failedData,
|
||||
fill: false,
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
tension: 0.1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
new Vue({ el:'#app', mixins:[baseApp], data(){ return { pluginStats:[], pluginTrendVisible:false, selectedPlugin:null } }, computed:{ totalCalls(){ return this.pluginStats.reduce((s,i)=>s+(parseInt(i.total_calls)||0),0); }, averageSuccessRate(){ if(!this.pluginStats.length) return '0.00'; const sum=this.pluginStats.reduce((s,i)=>s+(parseInt(i.total_calls)>0?((parseInt(i.success_calls)/parseInt(i.total_calls))*100):100),0); return (sum/this.pluginStats.length).toFixed(2); } }, mounted(){ this.currentView='2'; this.loadData(); }, methods:{ loadData(){ this.loadPluginStats(parseInt(this.timeRange)); }, loadPluginStats(days){ axios.get(`/api/plugin_stats?days=${days}`).then(r=>{ if(r.data.success) this.pluginStats=r.data.data||[]; }).catch(e=>{ console.error('加载插件统计数据出错:',e); this.$message.error('加载插件统计数据出错'); }); }, viewPluginTrend(plugin){ this.selectedPlugin=plugin; this.pluginTrendVisible=true; const days=parseInt(this.timeRange); axios.get(`/api/plugin_trend?days=${days}&plugin_name=${plugin.plugin_name}`).then(r=>{ if(r.data.success){ const trendData=r.data.data||[]; this.$nextTick(()=>this.renderPluginTrendChart(trendData, plugin.plugin_name)); } }).catch(e=>{ console.error('加载插件趋势数据出错:',e); this.$message.error('加载插件趋势数据出错'); }); }, renderPluginTrendChart(trendData, pluginName){ const ctx=document.getElementById('pluginTrendChart').getContext('2d'); if(this.charts&&this.charts.pluginTrendChart)this.charts.pluginTrendChart.destroy(); if(!this.charts)this.charts={}; const labels=trendData.map(i=>i.date||i.stat_date); const totalData=trendData.map(i=>parseInt(i.total_calls)||0); const successData=trendData.map(i=>parseInt(i.success_calls)||0); const failedData=trendData.map(i=>parseInt(i.failed_calls)||0); this.charts.pluginTrendChart=new Chart(ctx,{ type:'line', data:{ labels, datasets:[ {label:`${pluginName} 总调用`,data:totalData,fill:false,backgroundColor:'rgba(79,70,229,.2)',borderColor:'rgba(79,70,229,1)',tension:.28,borderWidth:3,pointRadius:2},{label:`${pluginName} 成功调用`,data:successData,fill:false,backgroundColor:'rgba(16,185,129,.2)',borderColor:'rgba(16,185,129,1)',tension:.28,borderWidth:3,pointRadius:2},{label:`${pluginName} 失败调用`,data:failedData,fill:false,backgroundColor:'rgba(239,68,68,.2)',borderColor:'rgba(239,68,68,1)',tension:.28,borderWidth:3,pointRadius:2}]}, options:{ responsive:true, scales:{ y:{beginAtZero:true,grid:{color:'rgba(148,163,184,.12)'}}, x:{grid:{display:false}} } } }); } } });
|
||||
</script>
|
||||
{% endblock %}
|
||||
<style>
|
||||
.page-shell{display:flex;flex-direction:column;gap:16px}.page-hero{padding:24px 26px;border-radius:24px;background:linear-gradient(135deg, rgba(79,70,229,.10), rgba(59,130,246,.08), rgba(255,255,255,.9));border:1px solid rgba(148,163,184,.16);box-shadow:0 18px 40px rgba(15,23,42,.06)}.page-eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#6366f1;font-weight:700;margin-bottom:8px}.page-hero-copy h1{font-size:30px;line-height:1.1;margin-bottom:10px;color:#0f172a}.page-hero-copy p{color:#64748b;font-size:14px}.overview-grid .el-col{margin-bottom:16px}.overview-card{min-height:112px}.overview-card--primary{background:linear-gradient(180deg, rgba(79,70,229,.10), rgba(255,255,255,.94)) !important}.overview-card--soft{background:linear-gradient(180deg, rgba(59,130,246,.08), rgba(255,255,255,.94)) !important}.overview-label{font-size:13px;color:#64748b;margin-bottom:14px}.overview-value{font-size:30px;font-weight:700;color:#0f172a;margin-bottom:10px}.overview-note{font-size:12px;color:#94a3b8}.workspace-header{display:flex;align-items:center;justify-content:space-between;gap:16px}.workspace-header h3{font-size:18px;margin-bottom:4px}.workspace-header p{font-size:13px;color:#64748b}.entity-cell{display:flex;align-items:center;gap:12px}.entity-badge{width:30px;height:30px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:rgba(79,70,229,.10);color:#4f46e5;font-size:12px;font-weight:700;flex-shrink:0}.entity-title{font-size:14px;font-weight:600;color:#0f172a}.entity-subtitle{margin-top:4px;font-size:12px;color:#94a3b8}.chart-shell{padding:16px;border-radius:18px;background:linear-gradient(180deg, rgba(248,250,252,.78), rgba(255,255,255,.96));border:1px solid rgba(148,163,184,.12)}.chart-heading{margin-bottom:12px;font-size:14px;font-weight:600;color:#334155}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user