Files
abot/admin/dashboard/templates/groups.html
2026-04-15 17:02:34 +08:00

129 lines
9.6 KiB
HTML

{% extends "base.html" %}
{% block title %}群组统计 - 机器人管理后台{% endblock %}
{% block content %}
<div class="page-shell stats-page">
<div class="page-hero">
<div class="page-hero-copy">
<div class="page-eyebrow">Groups 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 %}{{ groupStats.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-card class="workspace-card" shadow="hover">
<div slot="header" class="workspace-header"><div><h3>群组活跃度排行</h3><p>查看调用量、插件覆盖与用户活跃情况。</p></div></div>
<el-table :data="groupStats" style="width: 100%">
<el-table-column label="群组信息" min-width="300">
<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.group_name || scope.row.group_id }}{% endraw %}</div><div class="entity-subtitle">{% raw %}{{ scope.row.group_id }}{% endraw %}</div></div></div></template>
</el-table-column>
<el-table-column prop="total_calls" label="调用次数" sortable width="120"></el-table-column>
<el-table-column prop="used_plugins" label="使用插件数" sortable width="120"></el-table-column>
<el-table-column prop="unique_users" label="唯一用户数" sortable width="120"></el-table-column>
<el-table-column prop="success_calls" label="成功次数" sortable width="120"></el-table-column>
<el-table-column prop="failed_calls" label="失败次数" sortable width="120"></el-table-column>
<el-table-column label="成功率" sortable width="120">
<template slot-scope="scope">{% raw %}{{ scope.row.total_calls ? (scope.row.success_calls / scope.row.total_calls * 100).toFixed(2) : '0.00' }}{% endraw %}%</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center"><template slot-scope="scope"><el-button size="mini" type="primary" plain @click="viewGroupDetail(scope.row)">查看详情</el-button></template></el-table-column>
</el-table>
</el-card>
<el-dialog title="群组详情" :visible.sync="detailDialogVisible" width="76%" top="5vh">
<div v-loading="detailLoading">
<template v-if="detailData">
<el-descriptions :column="2" border>
<el-descriptions-item label="群组">{% raw %}{{ detailData.group_name || detailData.group_id }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="群ID">{% raw %}{{ detailData.group_id }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="健康度">{% raw %}{{ detailData.health_score }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="启用功能数">{% raw %}{{ detailData.permissions.enabled_count }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="近30天消息">{% raw %}{{ detailData.overview.message_count_30d }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="近30天插件调用">{% raw %}{{ detailData.overview.plugin_call_count_30d }}{% endraw %}</el-descriptions-item>
</el-descriptions>
<div class="detail-section">
<div class="section-title">启用功能</div>
<div class="detail-chip-list">
<el-tag v-for="item in detailData.permissions.enabled_features" :key="item" size="small" effect="plain">
{% raw %}{{ item }}{% endraw %}
</el-tag>
<span v-if="!detailData.permissions.enabled_features.length" class="empty-inline">暂无启用功能</span>
</div>
</div>
<div class="detail-section">
<div class="section-title">运营建议</div>
<el-alert
v-for="(item, index) in detailData.operation_suggestions"
:key="index"
:title="item.title"
:description="item.desc"
:type="item.level"
:closable="false"
show-icon
class="suggestion-item">
</el-alert>
<div v-if="!detailData.operation_suggestions.length" class="empty-inline">当前暂无额外运营建议</div>
</div>
<div class="detail-section">
<div class="section-title">插件调用 Top</div>
<el-table :data="detailData.plugin_stats || []" size="mini">
<el-table-column prop="plugin_name" label="插件"></el-table-column>
<el-table-column prop="command" label="命令" min-width="120"></el-table-column>
<el-table-column prop="total_calls" label="调用" width="90"></el-table-column>
<el-table-column prop="unique_users" label="用户数" width="90"></el-table-column>
</el-table>
</div>
</template>
</div>
</el-dialog>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el:'#app', mixins:[baseApp],
data(){ return { groupStats:[], detailDialogVisible:false, detailLoading:false, detailData:null } },
computed:{
totalCalls(){ return this.groupStats.reduce((s,i)=>s+(parseInt(i.total_calls)||0),0); },
averageSuccessRate(){ if(!this.groupStats.length) return '0.00'; const sum=this.groupStats.reduce((s,i)=>s+(i.total_calls?((i.success_calls/i.total_calls)*100):0),0); return (sum/this.groupStats.length).toFixed(2); }
},
mounted(){ this.currentView='4'; this.loadData(); },
methods:{
loadData(){ this.loadGroupStats(parseInt(this.timeRange)); },
loadGroupStats(days){ axios.get(`/api/group_stats?days=${days}&limit=20`).then(r=>{ if(r.data.success) this.groupStats=r.data.data||[]; }).catch(e=>{ console.error('加载群组统计数据出错:',e); this.$message.error('加载群组统计数据出错'); }); },
viewGroupDetail(group){
if(!group || !group.group_id) return;
this.detailDialogVisible = true;
this.detailLoading = true;
this.detailData = null;
axios.get(`/robot/api/group/${encodeURIComponent(group.group_id)}/detail`).then(r=>{
if(r.data.success){
this.detailData = r.data.data || null;
}else{
this.$message.error(r.data.error || '加载群组详情失败');
}
}).catch(e=>{
console.error('加载群组详情出错:',e);
this.$message.error('加载群组详情出错');
}).finally(()=>{
this.detailLoading = false;
});
}
}
});
</script>
<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}.detail-section{margin-top:18px}.section-title{font-size:15px;font-weight:700;color:#0f172a;margin-bottom:10px}.detail-chip-list{display:flex;gap:8px;flex-wrap:wrap}.suggestion-item{margin-bottom:10px}.empty-inline{font-size:13px;color:#94a3b8}
</style>
{% endblock %}