Просмотр исходного кода

feat(web): 管理端首页增加分享/访问/购买热点 Top5

Xiaogang Liao 3 недель назад
Родитель
Сommit
7427f3981e
1 измененных файлов с 137 добавлено и 1 удалено
  1. 137 1
      cfc-web/src/views/Dashboard.vue

+ 137 - 1
cfc-web/src/views/Dashboard.vue

@@ -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>