xdd 5 روز پیش
والد
کامیت
656a80bb7c
4فایلهای تغییر یافته به همراه770 افزوده شده و 7 حذف شده
  1. 4 3
      package.json
  2. 53 0
      src/api/statistics/statistics.js
  3. 4 4
      src/views/company/companyVoicePackage/index.vue
  4. 709 0
      src/views/statistics/index.vue

+ 4 - 3
package.json

@@ -44,13 +44,13 @@
     "bpmn-js-properties-panel": "^0.34.0",
     "bpmn-moddle": "^6.0.0",
     "clipboard": "2.0.4",
-    "cos-js-sdk-v5": "^1.8.3",
-    "vod-js-sdk-v6": "^1.7.0",
-    "esdk-obs-browserjs": "^3.24.3",
     "compression-webpack-plugin": "^5.0.1",
     "core-js": "3.6.5",
+    "cos-js-sdk-v5": "^1.8.3",
+    "dayjs": "^1.11.13",
     "echarts": "4.2.1",
     "element-ui": "2.15.5",
+    "esdk-obs-browserjs": "^3.24.3",
     "file-saver": "2.0.1",
     "form-making": "^1.2.9",
     "fuse.js": "3.4.4",
@@ -70,6 +70,7 @@
     "stylus": "^0.54.7",
     "stylus-loader": "^3.0.2",
     "v-clipboard": "^2.2.3",
+    "vod-js-sdk-v6": "^1.7.0",
     "vue": "2.6.10",
     "vue-clipboard2": "^0.3.1",
     "vue-count-to": "1.0.13",

+ 53 - 0
src/api/statistics/statistics.js

@@ -0,0 +1,53 @@
+import request from "@/utils/request";
+
+/**
+ * 分析概览
+ * @param query
+ * @returns {AxiosPromise}
+ */
+export function analysisPreview(query) {
+  return request({
+    url: '/index/statistics/analysisPreview',
+    method: 'post',
+    data: query
+  })
+}
+
+/**
+ * 数据概览
+ * @param query
+ * @returns {AxiosPromise}
+ */
+export function dealerAggregated() {
+  return request({
+    url: '/index/statistics/dealerAggregated',
+    method: 'get',
+    params: {}
+  })
+}
+
+
+/**
+ * 数据概览
+ * @param query
+ * @returns {AxiosPromise}
+ */
+export function smsBalance() {
+  return request({
+    url: '/index/statistics/smsBalance',
+    method: 'get',
+    params: {}
+  })
+}
+
+/**
+ * 授权信息
+ * @returns {*}
+ */
+export function authorizationInfo() {
+  return request({
+    url: '/index/statistics/authorizationInfo',
+    method: 'get',
+    params: {}
+  })
+}

+ 4 - 4
src/views/company/companyVoicePackage/index.vue

@@ -6,7 +6,7 @@
         style="width: 220px"
           v-model="queryParams.packageName"
           placeholder="请输入套餐名"
-          clearable
+          clearables
           size="small"
           @keyup.enter.native="handleQuery"
         />
@@ -91,7 +91,7 @@
         </template>
       </el-table-column>
     </el-table>
-    
+
     <pagination
       v-show="total>0"
       :total="total"
@@ -180,7 +180,7 @@ export default {
         status: [
           { required: true, message: "状态不能为空", trigger: "blur" }
         ],
-      } 
+      }
     };
   },
   created() {
@@ -247,7 +247,7 @@ export default {
       getCompanyVoicePackage(packageId).then(response => {
         this.form = response.data;
         this.form.status = response.data.status.toString();
-        
+
         this.open = true;
         this.title = "修改套餐";
       });

+ 709 - 0
src/views/statistics/index.vue

@@ -0,0 +1,709 @@
+<template>
+  <div class="statistics-dashboard">
+    <!-- 数据概览 (Data Overview) -->
+    <el-card class="overview-section" shadow="never">
+      <div slot="header" class="header">
+        <span>数据概览</span>
+        <el-dropdown class="dropdown-menu" trigger="click">
+          <span class="el-dropdown-link">
+            部门方案查看 <i class="el-icon-arrow-down el-icon--right"></i>
+          </span>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item>全部部门</el-dropdown-item>
+            <el-dropdown-item>销售部</el-dropdown-item>
+            <el-dropdown-item>市场部</el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
+      </div>
+
+      <el-row :gutter="20">
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-user-solid"></i>
+              分公司数量
+            </div>
+            <div class="card-value highlight">{{dealderCount}}</div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-user"></i>
+              销售数量
+            </div>
+            <div class="card-value highlight">{{groupMgrCount}}</div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-shopping-cart-full"></i>
+              会员数量
+            </div>
+            <div class="card-value highlight">{{memberCount}}</div>
+            <div class="card-badge">
+              <i class="el-icon-camera"></i>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-money"></i>
+              可用余额
+            </div>
+            <div class="card-value highlight">143,650.07</div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <span>今日消耗</span>
+            </div>
+            <div class="card-value highlight">1,093.70</div>
+            <div class="card-sub">
+              <span>昨日消耗(元)</span>
+              <span class="sub-value">1952.8</span>
+            </div>
+            <el-progress :percentage="74" :show-text="false" color="#409EFF"></el-progress>
+            <div class="card-desc">预测不足74天</div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <span class="cdn-label">CDN</span>
+              今日
+            </div>
+            <div class="card-value highlight">1.79T</div>
+            <div class="card-sub">
+              <span>本月</span>
+              <span class="sub-value">18.45T</span>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              <i class="el-icon-message"></i>
+              短信剩余条数
+            </div>
+            <div class="card-value highlight">{{smsRemainCount}}</div>
+          </div>
+        </el-col>
+
+        <el-col :span="3">
+          <div class="data-card">
+            <div class="card-title">
+              平台今日看课人数
+            </div>
+            <div class="card-value highlight">{{todayWatchUserCount}}</div>
+            <div class="card-sub">
+              <span>配额上限</span>
+              <span class="sub-value">{{todayWatchUserCount}}/{{versionLimit}}</span>
+            </div>
+            <el-progress :percentage="70" :show-text="false" color="#409EFF"></el-progress>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 分析概览 (Analysis Overview) -->
+    <el-card class="analysis-section" shadow="never">
+      <div slot="header" class="header">
+        <span>分析概览</span>
+        <div class="tab-group">
+          <el-radio-group v-model="queryTime" size="medium" @change="handleAnalysis">
+            <el-radio-button label="今日"></el-radio-button>
+            <el-radio-button label="昨日"></el-radio-button>
+            <el-radio-button label="本周"></el-radio-button>
+            <el-radio-button label="本月"></el-radio-button>
+            <el-radio-button label="上月"></el-radio-button>
+          </el-radio-group>
+        </div>
+
+        <div class="action-group">
+          <el-button size="small" plain icon="el-icon-refresh">手动刷新</el-button>
+          <el-button size="small" plain>自动刷新</el-button>
+          <el-button size="small" type="primary">刷新</el-button>
+        </div>
+      </div>
+
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <div class="analysis-card">
+            <div class="card-icon"><i class="el-icon-monitor"></i></div>
+            <div class="card-content">
+              <div class="card-row">
+                <span>观看人数</span>
+                <span class="highlight">{{watchUserCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>完播人数</span>
+                <span class="highlight">{{completedUserCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>完播率</span>
+                <span class="highlight">{{completedRate}}%</span>
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="6">
+          <div class="analysis-card">
+            <div class="card-icon"><i class="el-icon-video-play"></i></div>
+            <div class="card-content">
+              <div class="card-row">
+                <span>观看次数</span>
+                <span class="highlight">{{watchCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>完播次数</span>
+                <span class="highlight">{{completedCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>视频完播率</span>
+                <span class="highlight">{{watchRate}}</span>
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="6">
+          <div class="analysis-card">
+            <div class="card-icon"><i class="el-icon-headset"></i></div>
+            <div class="card-content">
+              <div class="card-row">
+                <span>答题人数</span>
+                <span class="highlight">{{answerMemberCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>正确人数</span>
+                <span class="highlight">{{correctUserCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>正确率</span>
+                <span class="highlight">{{correctRate}}%</span>
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="6">
+          <div class="analysis-card">
+            <div class="card-icon"><i class="el-icon-present"></i></div>
+            <div class="card-content">
+              <div class="card-row">
+                <span>答题红包个数</span>
+                <span class="highlight">{{rewardCount}}</span>
+              </div>
+              <div class="card-row">
+                <span>答题红包金额(元)</span>
+                <span class="highlight">{{rewardMoney}}</span>
+              </div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 图表区域 (Charts Area) -->
+    <el-row :gutter="20" class="charts-section">
+      <el-col :span="12">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>会员观看、完播人数趋势图</span>
+            <div class="legend">
+              <div class="legend-item">
+                <span class="dot viewer-dot"></span>
+                <span>观看人数</span>
+              </div>
+              <div class="legend-item">
+                <span class="dot complete-dot"></span>
+                <span>完播人数</span>
+              </div>
+            </div>
+            <el-button size="small" plain class="view-more">平台每日统计 <i class="el-icon-arrow-right"></i></el-button>
+          </div>
+          <div ref="viewerChart" class="chart-container"></div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="12">
+        <el-card shadow="never">
+          <div slot="header" class="chart-header">
+            <span>经销商会员观看TOP10</span>
+            <div class="legend">
+              <el-radio-group v-model="viewerType" size="small">
+                <el-radio-button label="viewers">按观看人数</el-radio-button>
+                <el-radio-button label="completed">按完播人数</el-radio-button>
+              </el-radio-group>
+            </div>
+            <el-button size="small" plain class="view-more">经销商统计 <i class="el-icon-arrow-right"></i></el-button>
+          </div>
+          <div ref="dealerChart" class="chart-container"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+import {analysisPreview, authorizationInfo, dealerAggregated, smsBalance} from "@/api/statistics/statistics";
+import dayjs from 'dayjs';
+
+export default {
+  name: 'StatisticsDashboard',
+  data() {
+    return {
+      smsRemainCount: 0,
+      viewerType: 'viewers',
+      viewerChart: null,
+      dealerChart: null,
+      // 分公司数量
+      dealderCount: 0,
+      // 销售数量
+      groupMgrCount: 0,
+      // 会员总数量
+      memberCount: 0,
+      // 正常会员数量
+      normalNum: 0,
+      // 黑名单会员数量
+      blackNum: 0,
+      // 观看人数
+      watchUserCount: 0,
+      // 完播人数
+      completedUserCount: 0,
+      // 完播率
+      completedRate: 0,
+      // 观看次数
+      watchCount:0,
+      // 完播次数
+      completedCount: 0,
+      // 视频完播率
+      watchRate: 0,
+      // 答题人数
+      answerMemberCount: 0,
+      // 正确人数
+      correctUserCount: 0,
+      correctRate: 0.0,
+      // 答题红包个数
+      rewardCount: 0,
+      // 答题红包金额
+      rewardMoney: 0.0,
+      queryTime: '今日',
+      todayWatchUserCount: 0,
+      versionLimit: 0
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initViewerChart()
+      this.initDealerChart()
+
+      // 监听窗口大小变化,重新渲染图表
+      window.addEventListener('resize', () => {
+        this.viewerChart && this.viewerChart.resize()
+        this.dealerChart && this.dealerChart.resize()
+      })
+    })
+  },
+  created() {
+    dealerAggregated().then(res=>{
+      if(res.code === 200){
+        this.dealderCount = res.data.dealderCount;
+        this.groupMgrCount = res.data.groupMgrCount;
+        this.memberCount = res.data.memberCount;
+        this.normalNum = res.data.normalNum;
+        this.blackNum = res.data.blackNum;
+      }
+    })
+    let param = {
+      startTime: '',
+      endTime: ''
+    };
+    // 获取当前日期时间
+    const today = dayjs();
+    param.startTime = this.formatDate(today);
+    param.endTime = this.formatDate(today);
+    analysisPreview(param).then(res=>{
+      if(res.code === 200){
+        this.watchUserCount = res.data.watchUserCount;
+        this.completedUserCount = res.data.completedUserCount;
+        this.completedRate = res.data.completedRate;
+        this.watchCount = res.data.watchCount;
+        this.completedCount = res.data.completedCount;
+        this.answerMemberCount = res.data.answerMemberCount;
+        this.correctUserCount = res.data.correctUserCount;
+        this.correctRate = res.data.correctRate;
+        this.rewardCount = res.data.rewardCount;
+        this.rewardMoney = res.data.rewardMoney;
+      }
+    })
+    smsBalance().then(res=>{
+      if(res.code === 200){
+        this.smsRemainCount = res.data;
+      }
+    })
+    authorizationInfo().then(res=>{
+      if(res.code === 200){
+        this.todayWatchUserCount = res.data.todayWatchUserCount;
+        this.versionLimit = res.data.versionLimit;
+      }
+    })
+  },
+  methods: {
+    formatDate(date) {
+      return dayjs(date).format('YYYY-MM-DD');
+    },
+    // 分析概览
+    handleAnalysis(){
+      let param = {
+        startTime: '',
+        endTime: ''
+      };
+      // 获取当前日期时间
+      const today = dayjs();
+
+      if (this.queryTime === '今日') {
+        param.startTime = this.formatDate(today);
+        param.endTime = this.formatDate(today);
+      } else if (this.queryTime === '昨日') {
+        const yesterday = today.subtract(1, 'day');
+        param.startTime = this.formatDate(yesterday);
+        param.endTime = this.formatDate(yesterday);
+      } else if (this.queryTime === '本周') {
+        param.startTime = this.formatDate(today.startOf('week'));
+        param.endTime = this.formatDate(today.endOf('week'));
+      } else if (this.queryTime === '本月') {
+        param.startTime = this.formatDate(today.startOf('month'));
+        param.endTime = this.formatDate(today.endOf('month'));
+      } else if (this.queryTime === '上月') {
+        const lastMonth = today.subtract(1, 'month');
+        param.startTime = this.formatDate(lastMonth.startOf('month'));
+        param.endTime = this.formatDate(lastMonth.endOf('month'));
+      } else {
+        // 可以添加一个默认处理或错误提示
+        console.warn(`未知的 queryTime: ${this.queryTime}, 默认使用今日`);
+        param.startTime = this.formatDate(today);
+        param.endTime = this.formatDate(today);
+      }
+
+      analysisPreview(param).then(res=>{
+        if(res.code === 200){
+          this.watchUserCount = res.data.watchUserCount;
+          this.completedUserCount = res.data.completedUserCount;
+          this.completedRate = res.data.completedRate;
+          this.watchCount = res.data.watchCount;
+          this.completedCount = res.data.completedCount;
+          this.answerMemberCount = res.data.answerMemberCount;
+          this.correctUserCount = res.data.correctUserCount;
+          this.correctRate = res.data.correctRate;
+          this.rewardCount = res.data.rewardCount;
+          this.rewardMoney = res.data.rewardMoney;
+        }
+      })
+    },
+    initViewerChart() {
+      this.viewerChart = echarts.init(this.$refs.viewerChart)
+
+      const option = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'shadow'
+          }
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          data: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: [
+          {
+            name: '观看人数',
+            type: 'bar',
+            data: [50, 20, 40, 120, 700, 800, 900, 780, 700, 700, 680, 580, 550, 450, 400, 320, 170, 0, 0, 0, 0, 0, 0, 0],
+            itemStyle: {
+              color: '#409EFF'
+            }
+          },
+          {
+            name: '完播人数',
+            type: 'bar',
+            data: [30, 10, 20, 300, 600, 700, 780, 700, 650, 650, 650, 520, 450, 400, 330, 120, 100, 0, 0, 0, 0, 0, 0, 0],
+            itemStyle: {
+              color: '#67C23A'
+            }
+          }
+        ]
+      }
+
+      this.viewerChart.setOption(option)
+    },
+    initDealerChart() {
+      this.dealerChart = echarts.init(this.$refs.dealerChart)
+
+      const option = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'shadow'
+          }
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'value'
+        },
+        yAxis: {
+          type: 'category',
+          data: ['七彩公司', '二级经销商A', '内蒙七彩', '呼和浩特分公司', '呼和浩特经销商', '海南经销', '重庆市分公司', '东方分销商', '银田分公司', '重庆一分销']
+        },
+        series: [
+          {
+            name: '观看人数',
+            type: 'bar',
+            data: [2780, 1050, 650, 600, 500, 350, 260, 230, 180, 120],
+            itemStyle: {
+              color: '#409EFF'
+            }
+          }
+        ]
+      }
+
+      this.dealerChart.setOption(option)
+    }
+  },
+  watch: {
+    viewerType(newVal) {
+      if (newVal === 'viewers') {
+        this.dealerChart.setOption({
+          series: [{
+            data: [2780, 1050, 650, 600, 500, 350, 260, 230, 180, 120]
+          }]
+        })
+      } else {
+        this.dealerChart.setOption({
+          series: [{
+            name: '完播人数',
+            data: [2500, 980, 620, 580, 480, 320, 240, 210, 160, 100]
+          }]
+        })
+      }
+    }
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.resizeHandler)
+    this.viewerChart && this.viewerChart.dispose()
+    this.dealerChart && this.dealerChart.dispose()
+  }
+}
+</script>
+
+<style scoped>
+::v-deep .el-radio-button__inner:hover {
+  color: #409EFF; /* 鼠标悬浮时的文字颜色,可以根据需要调整 */
+}
+::v-deep .el-radio-button.is-active .el-radio-button__inner {
+  background-color: #409EFF; /* 选中时的背景色 */
+  border-color: #409EFF;     /* 选中时的边框色 */
+  color: #FFFFFF;           /* 选中时的文字颜色 (通常是白色) */
+  box-shadow: -1px 0 0 0 #409EFF; /* 处理按钮间的连接缝隙 */
+}
+/* 如果需要,也可以修改非选中状态下的聚焦(focus)或悬浮(hover)样式 */
+/* 例如,让非选中按钮悬浮时边框和文字也变蓝 */
+::v-deep .el-radio-button:not(.is-active) .el-radio-button__inner:hover {
+  color: #409EFF;
+  /* border-color: #b3d8ff;  Element UI 默认悬浮边框色,可以按需修改 */
+}
+/* 聚焦时的外框,如果需要的话 */
+::v-deep .el-radio-button:focus:not(.is-checked) .el-radio-button__inner {
+  /* border-color: #409EFF; */ /* Element UI 默认的 focus 颜色通常关联主题色 */
+  /* box-shadow: 0 0 2px 2px rgba(64, 158, 255, 0.2); */ /* 示例 focus 光晕 */
+}
+.statistics-dashboard {
+  padding: 20px;
+  background-color: #f5f7fa;
+}
+
+.overview-section,
+.analysis-section {
+  margin-bottom: 20px;
+  border-radius: 4px;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 16px;
+  font-weight: 500;
+}
+
+.data-card {
+  background-color: #fff;
+  border-radius: 4px;
+  padding: 15px;
+  height: 100px;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+}
+
+.card-title {
+  color: #606266;
+  font-size: 14px;
+  margin-bottom: 10px;
+}
+
+.card-value {
+  font-size: 24px;
+  font-weight: bold;
+  margin-top: auto;
+}
+
+.highlight {
+  color: #409EFF;
+}
+
+.card-sub {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
+}
+
+.card-desc {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
+}
+
+.card-badge {
+  position: absolute;
+  top: 15px;
+  right: 15px;
+  background: #f0f9eb;
+  color: #67c23a;
+  padding: 2px 5px;
+  border-radius: 4px;
+}
+
+.cdn-label {
+  background-color: #409EFF;
+  color: white;
+  padding: 2px 5px;
+  border-radius: 4px;
+  margin-right: 5px;
+  font-size: 12px;
+}
+
+.tab-group {
+  display: flex;
+  gap: 10px;
+}
+
+.action-group {
+  display: flex;
+  gap: 10px;
+}
+
+.analysis-card {
+  background-color: #fff;
+  border-radius: 4px;
+  padding: 20px;
+  display: flex;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
+}
+
+.card-icon {
+  width: 50px;
+  height: 50px;
+  background-color: rgba(64, 158, 255, 0.1);
+  border-radius: 8px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 24px;
+  color: #409EFF;
+  margin-right: 20px;
+}
+
+.card-content {
+  flex: 1;
+}
+
+.card-row {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+
+.charts-section {
+  margin-top: 20px;
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.view-more {
+  font-size: 12px;
+}
+
+.legend {
+  display: flex;
+  gap: 15px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+}
+
+.dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  margin-right: 5px;
+}
+
+.viewer-dot {
+  background-color: #409EFF;
+}
+
+.complete-dot {
+  background-color: #67C23A;
+}
+
+.chart-container {
+  height: 350px;
+  width: 100%;
+}
+</style>