|
|
@@ -83,6 +83,55 @@
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
+<!-- 热点 Top5 -->
|
|
|
+<el-row :gutter="20" class="hot-row">
|
|
|
+ <el-col :xs="24" :md="8">
|
|
|
+ <el-card class="hot-card" shadow="never" v-loading="trafficLoading">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span class="card-title"><i class="el-icon-share"></i> 分享热点(点击量 Top5)</span>
|
|
|
+ </div>
|
|
|
+ <div class="hot-list">
|
|
|
+ <div class="hot-item" v-for="(item, idx) in hotShares" :key="item.id || idx">
|
|
|
+ <span class="hot-rank" :class="'rank-' + (idx + 1)">{{ idx + 1 }}</span>
|
|
|
+ <span class="hot-title" :title="item.shareTitle || shareTypeLabel(item.targetType)">{{ item.shareTitle || shareTypeLabel(item.targetType) }}</span>
|
|
|
+ <span class="hot-value"><i class="el-icon-share"></i> {{ item.clickCount || 0 }}次</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="hotShares.length === 0" class="hot-empty">暂无分享数据</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :md="8">
|
|
|
+ <el-card class="hot-card" shadow="never" v-loading="trafficLoading">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span class="card-title"><i class="el-icon-top"></i> 访问热点(文章阅读量 Top5)</span>
|
|
|
+ </div>
|
|
|
+ <div class="hot-list">
|
|
|
+ <div class="hot-item" v-for="(item, idx) in hotArticles" :key="item.id || idx">
|
|
|
+ <span class="hot-rank" :class="'rank-' + (idx + 1)">{{ idx + 1 }}</span>
|
|
|
+ <span class="hot-title" :title="item.title">{{ item.title }}</span>
|
|
|
+ <span class="hot-value"><i class="el-icon-view"></i> {{ item.viewCount || 0 }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="hotArticles.length === 0" class="hot-empty">暂无阅读数据</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :md="8">
|
|
|
+ <el-card class="hot-card" shadow="never" v-loading="trafficLoading">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span class="card-title"><i class="el-icon-shopping-cart-2"></i> 购买热点(近30天销量 Top5)</span>
|
|
|
+ </div>
|
|
|
+ <div class="hot-list">
|
|
|
+ <div class="hot-item" v-for="(item, idx) in hotProducts" :key="item.id || idx">
|
|
|
+ <span class="hot-rank" :class="'rank-' + (idx + 1)">{{ idx + 1 }}</span>
|
|
|
+ <span class="hot-title" :title="item.productName">{{ item.productName }}</span>
|
|
|
+ <span class="hot-value"><i class="el-icon-s-promotion"></i> {{ item.salesCount || 0 }}件</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="hotProducts.length === 0" class="hot-empty">暂无购买数据</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+</el-row>
|
|
|
+
|
|
|
<!-- 内容区域 -->
|
|
|
<el-row :gutter="20" class="content-row">
|
|
|
<el-col :span="14">
|
|
|
@@ -277,7 +326,7 @@
|
|
|
|
|
|
<script>
|
|
|
import * as echarts from 'echarts'
|
|
|
-import { getDashboardStats, getMembershipStats, getTrendStats, getDashboardOverview, getSystemHealth } from '@/api/admin'
|
|
|
+import { getDashboardStats, getMembershipStats, getTrendStats, getDashboardOverview, getSystemHealth, getTrafficAnalytics } from '@/api/admin'
|
|
|
|
|
|
export default {
|
|
|
name: 'Dashboard',
|
|
|
@@ -297,7 +346,11 @@ export default {
|
|
|
recentTasks: [],
|
|
|
trendLoading: false,
|
|
|
memberLoading: false,
|
|
|
+ trafficLoading: false,
|
|
|
membershipData: {},
|
|
|
+ hotShares: [],
|
|
|
+ hotArticles: [],
|
|
|
+ hotProducts: [],
|
|
|
trendData: { familyTrend: {}, userTrend: {} },
|
|
|
trendChart: null,
|
|
|
memberChart: null,
|
|
|
@@ -330,6 +383,7 @@ mounted() {
|
|
|
this.loadDashboardData()
|
|
|
this.loadTrend()
|
|
|
this.loadMembership()
|
|
|
+ this.loadTraffic()
|
|
|
this.loadServerHealth()
|
|
|
this.resizeHandler = () => {
|
|
|
this.trendChart && this.trendChart.resize()
|
|
|
@@ -367,6 +421,26 @@ beforeDestroy() {
|
|
|
console.error('加载仪表盘数据失败:', error)
|
|
|
}
|
|
|
},
|
|
|
+ async loadTraffic() {
|
|
|
+ this.trafficLoading = true
|
|
|
+ try {
|
|
|
+ const res = await getTrafficAnalytics()
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const d = res.data
|
|
|
+ this.hotShares = (d.hotShareList || []).slice(0, 5)
|
|
|
+ this.hotArticles = (d.hotArticleList || []).slice(0, 5)
|
|
|
+ this.hotProducts = (d.hotProductList || []).slice(0, 5)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载热点数据失败:', error)
|
|
|
+ } finally {
|
|
|
+ this.trafficLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ shareTypeLabel(type) {
|
|
|
+ const map = { article: '文章', activity: '活动', product: '商品', package: '任务模板', invite: '邀请', other: '其它' }
|
|
|
+ return map[type] || type || '分享'
|
|
|
+ },
|
|
|
async loadServerHealth() {
|
|
|
try {
|
|
|
const res = await getSystemHealth()
|
|
|
@@ -866,6 +940,68 @@ initMemberPieChart() {
|
|
|
height: 260px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
+/* 热点 Top5 */
|
|
|
+.hot-row {
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+.hot-card {
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid var(--adm-border-split);
|
|
|
+ min-height: 232px;
|
|
|
+}
|
|
|
+.hot-list {
|
|
|
+ padding: 4px 0;
|
|
|
+}
|
|
|
+.hot-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8px 0;
|
|
|
+ border-bottom: 1px solid var(--adm-fill);
|
|
|
+ transition: background 0.2s;
|
|
|
+}
|
|
|
+.hot-item:hover {
|
|
|
+ background: var(--adm-bg-header);
|
|
|
+}
|
|
|
+.hot-item:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+.hot-rank {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 700;
|
|
|
+ margin-right: 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ background: var(--adm-bg-header);
|
|
|
+ color: var(--adm-text-45);
|
|
|
+}
|
|
|
+.rank-1 { background: #FEF3C7; color: var(--adm-warning); }
|
|
|
+.rank-2 { background: var(--adm-bg-header); color: var(--adm-text-45); }
|
|
|
+.rank-3 { background: #FFF7ED; color: #EA580C; }
|
|
|
+.hot-title {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--adm-text-65);
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.hot-value {
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--adm-text-45);
|
|
|
+ margin-left: 8px;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.hot-empty {
|
|
|
+ text-align: center;
|
|
|
+ color: var(--adm-text-45);
|
|
|
+ padding: 20px 0;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
</style>
|
|
|
|
|
|
<style>
|