63 lines
8.0 KiB
HTML
63 lines
8.0 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">Users 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 %}{{ userStats.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="userStats" 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.user_name || scope.row.user_id }}{% endraw %}</div><div class="entity-subtitle">{% raw %}{{ scope.row.user_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="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="viewUserDetail(scope.row)">查看详情</el-button></template></el-table-column>
|
|
</el-table>
|
|
</el-card>
|
|
|
|
<el-dialog title="用户详情" :visible.sync="detailDialogVisible" width="72%" top="6vh">
|
|
<div v-loading="detailLoading">
|
|
<template v-if="detailData">
|
|
<el-descriptions :column="2" border>
|
|
<el-descriptions-item label="用户">{% raw %}{{ detailData.user_name || detailData.user_id }}{% endraw %}</el-descriptions-item>
|
|
<el-descriptions-item label="用户ID">{% raw %}{{ detailData.user_id }}{% endraw %}</el-descriptions-item>
|
|
<el-descriptions-item label="近30天调用">{% raw %}{{ detailData.summary.total_calls }}{% endraw %}</el-descriptions-item>
|
|
<el-descriptions-item label="成功率">{% raw %}{{ detailData.summary.success_rate }}{% endraw %}%</el-descriptions-item>
|
|
<el-descriptions-item label="成功次数">{% raw %}{{ detailData.summary.success_calls }}{% endraw %}</el-descriptions-item>
|
|
<el-descriptions-item label="失败次数">{% raw %}{{ detailData.summary.failed_calls }}{% endraw %}</el-descriptions-item>
|
|
<el-descriptions-item label="使用插件数">{% raw %}{{ detailData.summary.plugin_count }}{% endraw %}</el-descriptions-item>
|
|
<el-descriptions-item label="最近活跃">{% raw %}{{ detailData.summary.last_used_at || '暂无' }}{% endraw %}</el-descriptions-item>
|
|
</el-descriptions>
|
|
|
|
<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="success_calls" label="成功" width="90"></el-table-column>
|
|
<el-table-column prop="failed_calls" 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{userStats:[],detailDialogVisible:false,detailLoading:false,detailData:null}}, computed:{ totalCalls(){return this.userStats.reduce((s,i)=>s+(parseInt(i.total_calls)||0),0)}, averageSuccessRate(){ if(!this.userStats.length) return '0.00'; const sum=this.userStats.reduce((s,i)=>s+(i.total_calls?((i.success_calls/i.total_calls)*100):0),0); return (sum/this.userStats.length).toFixed(2)} }, mounted(){ this.currentView='3'; this.loadData(); }, methods:{ loadData(){ this.loadUserStats(parseInt(this.timeRange)); }, loadUserStats(days){ axios.get(`/api/user_stats?days=${days}&limit=20`).then(r=>{ if(r.data.success) this.userStats=r.data.data||[]; }).catch(e=>{ console.error('加载用户统计数据出错:',e); this.$message.error('加载用户统计数据出错'); }); }, viewUserDetail(user){ if(!user||!user.user_id)return; this.detailDialogVisible=true; this.detailLoading=true; this.detailData=null; axios.get(`/api/user_stats/${encodeURIComponent(user.user_id)}?days=30`).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(15,118,110,.10), rgba(14,165,233,.08), rgba(255,255,255,.9));border:1px solid rgba(101,121,113,.16);box-shadow:0 18px 40px rgba(21,33,27,.06)}.page-eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#0b5e57;font-weight:700;margin-bottom:8px}.page-hero-copy h1{font-size:30px;line-height:1.1;margin-bottom:10px;color:#15211b}.page-hero-copy p{color:#4f6258;font-size:14px}.overview-grid .el-col{margin-bottom:16px}.overview-card{min-height:112px}.overview-card--primary{background:linear-gradient(180deg, rgba(15,118,110,.10), rgba(255,255,255,.94)) !important}.overview-card--soft{background:linear-gradient(180deg, rgba(14,165,233,.08), rgba(255,255,255,.94)) !important}.overview-label{font-size:13px;color:#4f6258;margin-bottom:14px}.overview-value{font-size:30px;font-weight:700;color:#15211b;margin-bottom:10px}.overview-note{font-size:12px;color:#74897f}.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:#4f6258}.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(15,118,110,.10);color:#0f766e;font-size:12px;font-weight:700;flex-shrink:0}.entity-title{font-size:14px;font-weight:600;color:#15211b}.entity-subtitle{margin-top:4px;font-size:12px;color:#74897f}.detail-section{margin-top:18px}.section-title{font-size:15px;font-weight:700;color:#15211b;margin-bottom:10px}
|
|
</style>
|
|
{% endblock %}
|