xgb 3 дней назад
Родитель
Сommit
5b09b470c0

+ 19 - 0
src/api/app/statistics/memberReport.js

@@ -0,0 +1,19 @@
+import request from '@/utils/request'
+
+// 查询销售会员统计报表
+export function getMemberReport(query) {
+  return request({
+    url: '/app/statistics/memberReport',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出销售会员统计报表
+export function exportMemberReport(query) {
+  return request({
+    url: '/app/statistics/exportMemberReport',
+    method: 'get',
+    params: query
+  })
+}

+ 3 - 3
src/views/app/statistics/appWatchCourseStatistics.vue

@@ -66,8 +66,8 @@
       height="600"
     >
       <el-table-column label="销售名称" align="center" prop="salesName" />
-      <el-table-column label="APP 会员数" align="center" prop="appUserCount" />
-      <el-table-column label="新注册 APP 会员数" align="center" prop="newAppUserCount" />
+<!--      <el-table-column label="APP 会员数" align="center" prop="appUserCount" />-->
+<!--      <el-table-column label="新注册 APP 会员数" align="center" prop="newAppUserCount" />-->
       <el-table-column label="课程名称" align="center" prop="courseName" />
       <el-table-column label="课程小节" align="center" prop="videoTitle" />
       <el-table-column label="完课数" align="center" prop="finishedCount" />
@@ -209,7 +209,7 @@ export default {
 
         const values = data.map(item => Number(item[column.property]));
 
-        if (['appUserCount', 'newAppUserCount', 'finishedCount', 'notWatchedCount', 'interruptCount', 'watchingCount', 'answeredCount', 'correctCount', 'redPacketCount'].includes(column.property)) {
+        if (['finishedCount', 'notWatchedCount', 'interruptCount', 'watchingCount', 'answeredCount', 'correctCount', 'redPacketCount'].includes(column.property)) {
           if (!values.every(value => isNaN(value))) {
             sums[index] = values.reduce((prev, curr) => {
               const value = Number(curr);

+ 277 - 0
src/views/app/statistics/salesMemberReport.vue

@@ -0,0 +1,277 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+<!--      <el-form-item label="销售人员" prop="userId">-->
+<!--        <el-select filterable v-model="queryParams.userId" placeholder="请选择销售人员" clearable size="small">-->
+<!--          <el-option-->
+<!--            v-for="item in userList"-->
+<!--            :key="item.userId"-->
+<!--            :label="item.userName"-->
+<!--            :value="item.userId"-->
+<!--          />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
+      <el-form-item label="统计时间" prop="createTime">
+        <el-date-picker
+          v-model="createTime"
+          size="small"
+          style="width: 220px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="dateChange"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table height="500" v-loading="loading" border :data="memberReportList">
+      <el-table-column label="销售人员" align="center" prop="companyUserName" width="150"/>
+<!--      <el-table-column label="所属部门" align="center" prop="deptName" width="150"/>-->
+      <el-table-column label="会员总数" align="center" prop="totalMemberCount"/>
+      <el-table-column label="APP会员数" align="center" prop="appMemberCount"/>
+      <el-table-column label="新增会员数" align="center" prop="newMemberCount"/>
+<!--      <el-table-column label="活跃会员数" align="center" prop="activeMemberCount"/>-->
+    </el-table>
+
+    <div class="total-summary">
+      <span class="total-title">总计:</span>
+      <span class="total-item">会员总数:{{ calculatedTotalData.totalMemberCount }}</span>
+      <span class="total-item">APP会员数:{{ calculatedTotalData.appMemberCount }}</span>
+      <span class="total-item">新增会员数:{{ calculatedTotalData.newMemberCount }}</span>
+<!--      <span class="total-item">活跃会员数:{{ calculatedTotalData.activeMemberCount }}</span>-->
+    </div>
+  </div>
+</template>
+
+<script>
+import { getUserList } from "@/api/company/companyUser";
+import { getMemberReport, exportMemberReport } from "@/api/app/statistics/memberReport";
+
+export default {
+  name: "SalesMemberReport",
+  data() {
+    return {
+      userList: [],
+      loading: true,
+      exportLoading: false,
+      showSearch: true,
+      createTime: null,
+      memberReportList: [],
+      calculatedTotalData: {
+        totalMemberCount: 0,
+        appMemberCount: 0,
+        newMemberCount: 0,
+        // activeMemberCount: 0
+      },
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        sTime: null,
+        eTime: null
+      }
+    };
+  },
+  created() {
+    const today = new Date();
+    const formatDate = (date) => {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    };
+    const todayStr = formatDate(today);
+    this.createTime = [todayStr, todayStr];
+    this.queryParams.sTime = todayStr;
+    this.queryParams.eTime = todayStr;
+
+    this.getSalesList();
+    this.getList();
+  },
+  methods: {
+    getSalesList() {
+      getUserList().then(response => {
+        this.userList = response.data || [];
+      });
+    },
+    getList() {
+      this.loading = true;
+      const params = { ...this.queryParams };
+
+      if (params.sTime) {
+        params.sTime = params.sTime + ' 00:00:00';
+      }
+      if (params.eTime) {
+        params.eTime = params.eTime + ' 23:59:59';
+      }
+
+      getMemberReport(params).then(response => {
+        this.memberReportList = response.rows || response.data || [];
+        this.calculateTotals();
+        this.loading = false;
+        setTimeout(() => {
+          this.$forceUpdate();
+        }, 100);
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    calculateTotals() {
+      this.calculatedTotalData = {
+        totalMemberCount: 0,
+        appMemberCount: 0,
+        newMemberCount: 0,
+        // activeMemberCount: 0
+      };
+
+      this.memberReportList.forEach(item => {
+        this.calculatedTotalData.totalMemberCount += Number(item.totalMemberCount) || 0;
+        this.calculatedTotalData.appMemberCount += Number(item.appMemberCount) || 0;
+        this.calculatedTotalData.newMemberCount += Number(item.newMemberCount) || 0;
+        // this.calculatedTotalData.activeMemberCount += Number(item.activeMemberCount) || 0;
+      });
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        sTime: null,
+        eTime: null
+      };
+      this.handleQuery();
+    },
+    dateChange() {
+      if (this.createTime != null && this.createTime.length === 2) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    handleExport() {
+      const now = new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, '0');
+      const firstDay = `${year}-${month}-01`;
+      const lastDay = new Date(year, now.getMonth() + 1, 0);
+      const lastDayStr = `${year}-${month}-${String(lastDay.getDate()).padStart(2, '0')}`;
+
+      const exportParams = {
+        ...this.queryParams,
+        sTime: this.queryParams.sTime || firstDay,
+        eTime: this.queryParams.eTime || lastDayStr
+      };
+
+      if (exportParams.sTime) {
+        exportParams.sTime = exportParams.sTime + ' 00:00:00';
+      }
+      if (exportParams.eTime) {
+        exportParams.eTime = exportParams.eTime + ' 23:59:59';
+      }
+
+      this.$confirm('是否确认导出销售会员统计报表', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportMemberReport(exportParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+        this.exportLoading = false;
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.total-summary {
+  margin-top: 15px;
+  padding: 15px 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f4 100%);
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+
+.total-title {
+  font-weight: bold;
+  font-size: 16px;
+  color: #303133;
+  margin-right: 20px;
+  flex-shrink: 0;
+}
+
+.total-item {
+  margin-right: 25px;
+  padding: 5px 10px;
+  background: white;
+  border-radius: 3px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+  display: inline-block;
+  margin-bottom: 5px;
+  font-size: 13px;
+  color: #606266;
+}
+
+.total-item::before {
+  content: "";
+  display: inline-block;
+  width: 3px;
+  height: 3px;
+  background: #409eff;
+  border-radius: 50%;
+  margin-right: 5px;
+  vertical-align: middle;
+}
+
+@media (max-width: 768px) {
+  .total-summary {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  .total-title {
+    margin-bottom: 10px;
+  }
+
+  .total-item {
+    margin-right: 10px;
+    margin-bottom: 8px;
+  }
+}
+</style>