Selaa lähdekoodia

Merge remote-tracking branch 'origin/master'

吴树波 1 kuukausi sitten
vanhempi
commit
2a10d7a065

+ 9 - 0
src/api/company/company.js

@@ -99,3 +99,12 @@ export function crmDayCountlist(query) {
     params: query
   })
 }
+
+// 获取公司选项列表
+export function getCompanyListLikeName(query) {
+  return request({
+    url: '/company/company/getCompanyListLikeName',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/company/companyUser.js

@@ -94,3 +94,12 @@ export function changeCompanyUser(data, params) {
     params: params
   })
 }
+
+// 获取销售选项列表
+export function getCompanyUserListLikeName(query){
+  return request({
+    url: '/company/companyUser/getCompanyUserListLikeName',
+    method: 'get',
+    params: query
+  })
+}

+ 8 - 0
src/api/course/userCourseCamp.js

@@ -43,4 +43,12 @@ export function copyCamp(trainingCampId) {
   })
 }
 
+// 获取训练营选项列表
+export function getCampListLikeName(query) {
+  return request({
+    url: '/course/trainingCamp/getCampListLikeName',
+    method: 'get',
+    params: query
+  })
+}
 

+ 18 - 0
src/api/course/userCoursePeriod.js

@@ -130,3 +130,21 @@ export function periodCountSelect(data) {
     data: data
   })
 }
+
+// 获取营期选项列表
+export function getPeriodListLikeName(query) {
+  return request({
+    url: '/course/period/getPeriodListLikeName',
+    method: 'get',
+    params: query
+  })
+}
+
+// 营期课程上移下移
+export function periodCourseMove(data) {
+  return request({
+    url: '/course/period/courseMove',
+    method: 'put',
+    params: data
+  })
+}

+ 9 - 0
src/api/course/userCourseVideo.js

@@ -93,4 +93,13 @@ export function getVideoListByCourseId(query) {
   })
 }
 
+// 获取选项列表
+export function getVideoListLikeName(query) {
+  return request({
+    url: '/course/userCourseVideo/getVideoListLikeName',
+    method: 'get',
+    params: query
+  })
+}
+
 

+ 12 - 0
src/api/statistics/member.js

@@ -0,0 +1,12 @@
+import request from "@/utils/request"
+
+/**
+ * 获取会员统计数据
+ */
+export function dailyData(query) {
+  return request({
+    url: '/stats/member/dailyData',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/users/user.js

@@ -71,3 +71,12 @@ export function transferUser(data) {
   })
 }
 
+// 查询会员选项列表
+export function getUserListLikeName(query) {
+  return request({
+    url: '/user/fsUser/getUserListLikeName',
+    method: 'get',
+    params: query
+  })
+}
+

+ 11 - 0
src/directive/select/elSelectLoadMore.js

@@ -0,0 +1,11 @@
+export default {
+  inserted(el, binding, vnode) {
+    const SELECT_WRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
+    SELECT_WRAP_DOM.addEventListener('scroll', function() {
+      const condition = this.scrollHeight - this.scrollTop <= this.clientHeight
+      if (condition) {
+        binding.value()
+      }
+    })
+  }
+}

+ 7 - 0
src/directive/select/index.js

@@ -0,0 +1,7 @@
+import elSelectLoadMore from './elSelectLoadMore'
+
+const install = function(Vue) {
+  Vue.directive('select-load-more', elSelectLoadMore)
+}
+
+export default install

+ 2 - 0
src/main.js

@@ -14,6 +14,7 @@ import App from './App'
 import store from './store'
 import router from './router'
 import permission from './directive/permission'
+import select from './directive/select'
 
 import VueClipboard from 'vue-clipboard2'
 
@@ -92,6 +93,7 @@ Vue.component('FileUpload', FileUpload)
 Vue.component('ImageUpload', ImageUpload)
 
 Vue.use(permission)
+Vue.use(select)
 
 /**
  * If you don't want to use mock-server

+ 1 - 1
src/utils/cos.js

@@ -3,7 +3,7 @@ import { Message } from 'element-ui';
 import { getTmpSecretKey } from '@/api/common';
 
 const config = {
-    Bucket: 'fby-1323137866',
+    Bucket: 'hylj-1323137866',
     Region: 'ap-chongqing',
 };
 

+ 1 - 1
src/utils/obs.js

@@ -29,7 +29,7 @@ export const uploadToOBS = async(file,progressCallback,type) =>  {
         return new Promise((resolve, reject) => {
             //上传对象
             obsClient.putObject({
-                Bucket: 'fby-hw079058881',//桶名称
+                Bucket: 'ylrz-obs2024',//桶名称
                 Key: key,//文件名
                 Body: file,
                 ProgressCallback: callback,//进度回调

+ 83 - 1
src/views/course/userCoursePeriod/index.vue

@@ -164,6 +164,7 @@
           <el-table-column type="selection" width="55" align="center" />
           <el-table-column label="营期名称" align="center" prop="periodName" />
           <el-table-column label="公司名称" align="center" prop="companyName" />
+          <el-table-column label="营期状态" align="center" prop="periodStatus" width="100" :formatter="periodStatusFormatter" />
           <el-table-column label="开营开始时间" align="center" prop="periodStartingTime" width="180" />
           <el-table-column label="开营结束时间" align="center" prop="periodEndTime" width="180" />
           <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
@@ -402,6 +403,22 @@
             <el-table-column label="小节" align="center" prop="videoName" />
             <el-table-column label="营期时间" align="center" prop="dayDate" width="150" />
             <el-table-column label="创建时间" align="center" prop="createTime" width="150" />
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+              <template slot-scope="scope">
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-top"
+                  @click="handleTop(scope.row)"
+                >上移</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-bottom"
+                @click="handleBottom(scope.row)"
+              >下移</el-button>
+              </template>
+            </el-table-column>
           </el-table>
         </el-tab-pane>
         <el-tab-pane label="公司列表" name="company">
@@ -432,7 +449,7 @@
 </template>
 
 <script>
-import {addPeriod, delPeriod, exportPeriod, getPeriod, pagePeriod, updatePeriod, getDays, addCourse, updateListCourseData} from "@/api/course/userCoursePeriod";
+import {addPeriod, delPeriod, exportPeriod, getPeriod, pagePeriod, updatePeriod, getDays, addCourse, updateListCourseData, periodCourseMove} from "@/api/course/userCoursePeriod";
 import {getCompanyList} from "@/api/company/company";
 import { listCamp, addCamp, editCamp, delCamp, copyCamp } from "@/api/course/userCourseCamp";
 import { courseList,videoList } from '@/api/course/courseRedPacketLog'
@@ -1152,6 +1169,71 @@ export default {
         this.redPacketVisible = true;
       }
     },
+    /** 上移课程 */
+    handleTop(row) {
+      const currentIndex = this.course.list.findIndex(item => item.id === row.id);
+      if (currentIndex <= 0) {
+        this.$message.warning('已经是第一条数据');
+        return;
+      }
+
+      // 获取上一条数据
+      const prevRow = this.course.list[currentIndex - 1];
+      console.log({
+        id: row.id,
+        targetId: prevRow.id,
+        type: 1 //上移
+      })
+      periodCourseMove({
+        id: row.id,
+        targetId: prevRow.id,
+        type: 1 //上移
+      }).then(response => {
+        if (response.code === 200) {
+          this.$message.success('上移成功');
+          this.getCourseList();
+        } else {
+          this.$message.error(response.msg || '上移失败');
+        }
+      }).catch(() => {
+        this.$message.error('上移失败');
+      });
+    },
+    /** 下移课程 */
+    handleBottom(row) {
+      const currentIndex = this.course.list.findIndex(item => item.id === row.id);
+      if (currentIndex === -1 || currentIndex >= this.course.list.length - 1) {
+        this.$message.warning('已经是最后一条数据');
+        return;
+      }
+
+      // 获取下一条数据
+      const nextRow = this.course.list[currentIndex + 1];
+
+      periodCourseMove({
+        id: row.id,
+        targetId: nextRow.id,
+        type: 2 //下移
+      }).then(response => {
+        if (response.code === 200) {
+          this.$message.success('下移成功');
+          this.getCourseList(); // 重新加载列表
+        } else {
+          this.$message.error(response.msg || '下移失败');
+        }
+      }).catch(() => {
+        this.$message.error('下移失败');
+      });
+    },
+    /** 营期状态格式化 */
+    periodStatusFormatter(row) {
+      const statusMap = {
+        1: '未开始',
+        2: '进行中',
+        3: '已结束'
+      };
+      return statusMap[row.periodStatus] || '未知状态';
+    },
   },
 };
 </script>

+ 1 - 1
src/views/course/userCoursePeriod/redPacket.vue

@@ -11,7 +11,7 @@
               size="mini"
               type="text"
               @click="handleInputAmount(scope.row)"
-            >录入金额</el-button>
+            >设置红包</el-button>
           </template>
         </el-table-column>
       </el-table>

+ 57 - 38
src/views/course/videoResource/index.vue

@@ -84,11 +84,6 @@
                 {{ scope.$index + 1 }}
               </template>
             </el-table-column>
-      <el-table-column label="主键ID" align="center" width="80">
-        <template slot-scope="scope">
-          <a @click="copy(scope.row.id)" style="color: #409EFF;">ID</a>
-        </template>
-      </el-table-column>
       <el-table-column label="素材名称" align="center" :show-overflow-tooltip="true" prop="resourceName" width="300"/>
       <el-table-column label="文件名称" align="center" :show-overflow-tooltip="true" prop="fileName" width="300"/>
       <el-table-column label="分类" align="center" width="120">
@@ -122,7 +117,7 @@
       <el-table-column label="关联题目" align="center" width="150">
         <template slot-scope="scope">
           <a
-            @click="handleViewProject(scope.row)"
+            @click="handleViewProject(scope.row, 3)"
             :style="scope.row.projectIds ? {
               backgroundColor: '#409EFF',
               color: 'white',
@@ -137,7 +132,7 @@
               backgroundColor: 'rgb(154 156 159)',
               color: 'white',
               border: 'none',
-              cursor: 'not-allowed',
+              cursor: 'pointer',
               borderRadius: '4px',
               padding: '4px 12px',
               fontSize: '12px',
@@ -188,7 +183,7 @@
           <el-input v-model="form.resourceName" placeholder="请输入" />
         </el-form-item>
 
-        <el-form-item label="文件名称" prop="fileName" style="margin-top: 20px">
+        <el-form-item label="文件名称" prop="fileName" style="margin-top: 20px;display: none">
           <el-input v-model="form.fileName" placeholder="请输入" />
         </el-form-item>
 
@@ -330,8 +325,7 @@
         <el-table-column label="关联项目" align="center" min-width="100">
           <template slot-scope="scope">
           <a
-            :diabled="scope.row.progress < 100"
-            @click="handleViewProject(scope.row)"
+            @click="handleViewProject(scope.row, 4)"
             :style="scope.row.projectIds.length > 0 ? {
               backgroundColor: '#409EFF',
               color: 'white',
@@ -346,7 +340,7 @@
               backgroundColor: 'rgb(154 156 159)',
               color: 'white',
               border: 'none',
-              cursor: 'not-allowed',
+              cursor: 'pointer',
               borderRadius: '4px',
               padding: '4px 12px',
               fontSize: '12px',
@@ -428,7 +422,7 @@
             </el-select>
           </el-form-item>
 
-          <el-form-item label="关联题目" prop="projectIds">
+          <el-form-item label="关联题目" prop="projectIds" style="display: none">
             <el-select
               ref="customSelect"
               class="custom-select-class"
@@ -469,7 +463,7 @@
             <el-input v-model="batchEditDialog.form.resourceName" placeholder="请输入" />
           </el-form-item>
 
-          <el-form-item label="文件名称" prop="fileName" style="margin-top: 20px">
+          <el-form-item label="文件名称" prop="fileName" style="margin-top: 20px;display: none">
             <el-input v-model="batchEditDialog.form.fileName" placeholder="请输入" />
           </el-form-item>
 
@@ -731,10 +725,6 @@ export default {
       batchAddVisible: false,
       batchLoading: false,
       videoList: [],
-      batchForm: {
-        resourceName: null,
-        typeId: null
-      },
 
       // 批量上传相关
       showUpload: false,
@@ -777,7 +767,7 @@ export default {
             { required: true, message: "文件名称不能为空", trigger: "blur" }
           ]
         },
-      }
+      },
     }
   },
   watch: {
@@ -892,9 +882,9 @@ export default {
       const id = row.id
 
       // 获取数据并设置表单
-      getVideoResource(id).then(response => {
+      getVideoResource(id).then(async response => {
         this.form = response.data;
-        this.changeCateType(this.form.typeId)
+        await this.changeCateType(this.form.typeId)
 
         // 处理projectIds,确保是数组格式
         if (this.form.projectIds && typeof this.form.projectIds === 'string') {
@@ -906,7 +896,7 @@ export default {
         // 如果存在关联项目,获取项目详情用于回显
         if (this.form.projectIds && this.form.projectIds.length > 0) {
           // 加载项目列表信息用于回显
-          getByIds({ids: this.form.projectIds.join(',')}).then(reponse => {
+          await getByIds({ids: this.form.projectIds.join(',')}).then(reponse => {
             this.projectShowList = reponse.data
           });
         }
@@ -982,12 +972,12 @@ export default {
         this.rootTypeList = response.data
       });
     },
-    changeCateType(val) {
+    async changeCateType(val) {
       if (!val) {
         this.subTypeList = []
         return
       }
-      getCateListByPid(val).then(response => {
+      await getCateListByPid(val).then(response => {
         this.subTypeList = response.data
       })
     },
@@ -1023,7 +1013,6 @@ export default {
     //获取第一帧封面
     async getFirstThumbnail(file, form){
       getThumbnail(file).then(response => {
-        console.log("获取到第一帧为封面=======>",response.url)
         form.thumbnail = response.url;
       })
     },
@@ -1032,7 +1021,7 @@ export default {
       try {
         const data = await uploadObject(file, (progress) => {
           const progressEvent = {
-            percent: Math.floor(progress.percent * 100) / 2, // COS SDK 百分比是 0-1,el-upload 需要 0-100
+            percent: Math.floor(progress.percent * 100 / 2), // COS SDK 百分比是 0-1,el-upload 需要 0-100
             loaded: progress.loaded,
             total: progress.total,
             lengthComputable: true // 文件上传通常总大小可知
@@ -1040,7 +1029,7 @@ export default {
           onProgress(progressEvent);
         }, 1);
 
-        let line_1 = `https://blytcpv.ylrzcloud.com${data.urlPath}`;
+        let line_1 = `https://rttcpv.ylrzcloud.com${data.urlPath}`;
 
         form.fileKey = data.urlPath.substring(1);
         form.videoUrl = line_1;
@@ -1064,7 +1053,7 @@ export default {
           onProgress(progressEvent);
         }, 1);
 
-        form.line2 = `https://blyobs.ylrztop.com/${data.urlPath}`;
+        form.line2 = `https://rtobs.ylrztop.com/${data.urlPath}`;
         this.$message.success("线路二上传成功");
       } catch (error) {
         this.$message.error("线路二上传失败");
@@ -1107,13 +1096,13 @@ export default {
     /** 批量新增 */
     handleBatchAdd() {
       this.batchAddVisible = true;
-      this.batchForm = {
-        resourceName: null,
-        typeId: null
-      };
       this.videoList = []; // 清空之前的视频列表
     },
     cancelBeforeBatch(done, cancel) {
+      if (!this.videoList || this.videoList.length === 0) {
+        done()
+        return
+      }
       this.$confirm('关闭窗口视频需要重新上传,确定关闭吗?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
@@ -1126,6 +1115,10 @@ export default {
     },
     /** 取消批量 */
     cancelBatch() {
+      if (!this.videoList || this.videoList.length === 0) {
+        this.batchAddVisible = false
+        return
+      }
       this.$confirm('关闭窗口视频需要重新上传,确定关闭吗?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
@@ -1177,7 +1170,7 @@ export default {
       this.batchEditDialog.open = false
     },
     batchEditSubmitForm() {
-      let temp = this.videoList.find(item => item.fileKey === this.batchEditDialog.form.fileKey)
+      let temp = this.videoList.find(item => item.tempId === this.batchEditDialog.form.tempId)
       Object.assign(temp, this.batchEditDialog.form)
       this.batchEditDialog.open = false
     },
@@ -1191,7 +1184,7 @@ export default {
         // 从视频列表中删除该项
         const index = this.videoList.findIndex(item => {
           // 通过文件名和大小确定唯一性,因为临时视频可能没有id
-          return item.fileKey === row.fileKey
+          return item.tempId === row.tempId
         });
 
         if (index !== -1) {
@@ -1232,6 +1225,7 @@ export default {
 
       // 创建临时视频对象添加到列表中
       const tempVideo = {
+        tempId: Math.random().toString(36).substring(2, 15),
         resourceName: file.name.split('.')[0], // 使用文件名作为视频名称
         fileName: file.name,
         thumbnail: null,
@@ -1256,7 +1250,7 @@ export default {
       const video = document.createElement('video');
       video.preload = 'metadata';
       video.onloadedmetadata = () => {
-        const index = this.videoList.findIndex(item => item.resourceName === tempVideo.resourceName);
+        const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
         if (index !== -1) {
           tempVideo.duration = Math.round(video.duration);
         }
@@ -1337,7 +1331,9 @@ export default {
     /** 打开项目选择弹窗 */
     openProjectDialog(projectIds, type) {
       this.$nextTick(() => {
-        this.$refs.customSelect.blur();
+        if (this.$refs.customSelect) {
+          this.$refs.customSelect.blur();
+        }
       });
       // 重置查询参数
       this.projectQueryParams = {
@@ -1377,12 +1373,34 @@ export default {
       // 更新表单中的项目ID
       if (this.selectedType === 0) {
         this.form.projectIds = this.selectedProjectIds;
-      } else if (this.selectedType === 1) {
+      }
+
+      else if (this.selectedType === 1) {
         this.batchUploadForm.projectIds = this.selectedProjectIds;
-      } else if (this.selectedType === 2) {
+      }
+
+      else if (this.selectedType === 2) {
         this.batchEditDialog.form.projectIds = this.selectedProjectIds;
       }
 
+      else if (this.selectedType === 3) {
+        const params = {
+          id: this.currentRow.id,
+          projectIds: this.selectedProjectIds.join(",")
+        }
+        updateVideoResource(params).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          }
+        });
+      }
+
+      else if (this.selectedType === 4) {
+        this.currentRow.projectIds = this.selectedProjectIds
+      }
+
       this.projectDialogVisible = false;
     },
 
@@ -1418,7 +1436,7 @@ export default {
     },
 
     /** 处理查看项目 */
-    handleViewProject(row) {
+    handleViewProject(row, type) {
       // 保存当前选择的行,以便后续操作
       this.currentRow = row;
 
@@ -1426,6 +1444,7 @@ export default {
       this.projectShowList = [];
 
       if (!row.projectIds || row.projectIds.length === 0) {
+        this.openProjectDialog(null, type)
         return;
       }
 

+ 649 - 0
src/views/statistics/member/index.vue

@@ -0,0 +1,649 @@
+<template>
+  <div class="member-statistics-page">
+  <!-- 会员统计页面容器 -->
+   <div class="member-statistics-container">
+    <!-- =================== 顶部查询区域 =================== -->
+      <el-form class="filter-container" :inline="true" :model="queryParams" ref="queryForm">
+        <div class="filter-item">
+          <!-- 按日/按月切换 -->
+          <el-radio-group v-model="queryParams.dateType" size="small" @input="handleChangeDateType">
+            <el-radio-button label="day">按每日</el-radio-button>
+            <el-radio-button label="month">按每月</el-radio-button>
+          </el-radio-group>
+        </div>
+
+        <div class="filter-item">
+          <!-- 日期选择器 -->
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            :key="queryParams.dateType"
+            :type="queryParams.dateType === 'day' ? 'daterange' : 'monthrange'"
+            size="small"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width: 260px;"
+            :clearable="false"
+          />
+        </div>
+
+        <!-- 经销商选择 -->
+        <el-form-item class="filter-item" label="经销商" prop="dealerId">
+          <!-- 经销商选择 -->
+          <el-select
+            v-model="queryParams.dealerId"
+            placeholder="请选择经销商"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadCompanyOptions"
+            v-select-load-more="loadMoreCompanyOptions"
+            :loading="companyOptionsLoading"
+            @change="handleChangeDealer"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in dealerOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="群管" prop="groupId">
+          <!-- 分组选择 -->
+          <el-select
+            v-model="queryParams.groupId"
+            placeholder="请选择群管"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadCompanyUserOptions"
+            v-select-load-more="loadMoreCompanyUserOptions"
+            :loading="companyUserOptionsLoading"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in groupOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="会员" prop="memberId">
+          <!-- 会员选择 -->
+          <el-select
+            v-model="queryParams.memberId"
+            placeholder="请选择会员"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadUserOptions"
+            v-select-load-more="loadMoreUserOptions"
+            :loading="userOptionsLoading"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in memberOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="训练营" prop="trainerId">
+          <!-- 训练营选择 -->
+          <el-select
+            v-model="queryParams.trainerId"
+            placeholder="请选择训练营"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadTrainerOptions"
+            v-select-load-more="loadMoreTrainerOptions"
+            :loading="trainerOptionsLoading"
+            @change="changeTrainer"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in trainerOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="营期" prop="campPeriod">
+          <!-- 营期选择 -->
+          <el-select
+            v-model="queryParams.campPeriod"
+            placeholder="请选择营期"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadPeriodOptions"
+            v-select-load-more="loadMorePeriodOptions"
+            :loading="campPeriodOptionsLoading"
+            @change="changeCampPeriod"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in campPeriodOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="课程" prop="courseId">
+          <!-- 课程选择 -->
+          <el-select
+            v-model="queryParams.courseId"
+            placeholder="请选择课程"
+            size="small"
+            clearable filterable remote
+            :remote-method="loadCourseOptions"
+            v-select-load-more="loadMoreCourseOptions"
+            :loading="courseOptionsLoading"
+            style="width: 140px;"
+          >
+            <el-option v-for="item in courseOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="filter-item" label="手机号" prop="mobile">
+          <!-- 手机号输入框 -->
+          <el-input
+            v-model="queryParams.mobile"
+            placeholder="请输入手机号"
+            size="small"
+            clearable
+            style="width: 200px;"
+          />
+        </el-form-item>
+
+        <div class="filter-item">
+         <!-- 操作按钮组 -->
+         <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
+        </div>
+
+        <div class="filter-item" style="margin-left: auto; display: none;">
+         <el-button size="small">更换所属群管</el-button>
+         <el-button type="primary" size="small">导出全部</el-button>
+        </div>
+      </el-form>
+    </div>
+
+    <!-- =================== 表格数据展示区域 =================== -->
+    <div class="table-container">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        border
+        style="width: 100%;"
+        show-summary
+        :summary-row-style="{ background: '#fafafa' }"
+      >
+        <!-- 选择列 -->
+        <el-table-column type="selection" width="50" align="center" />
+        <!-- 序号列 -->
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <!-- 统计日期列 -->
+        <el-table-column prop="date" label="统计日期" min-width="100" align="center" />
+        <!-- 会员名称列 -->
+        <el-table-column prop="memberName" label="会员名称" min-width="100" align="center" />
+        <!-- 性别列 -->
+        <el-table-column prop="gender" label="性别" width="60" align="center" />
+        <!-- 手机号列 -->
+        <el-table-column prop="mobile" label="手机号" min-width="100" align="center" />
+        <!-- 会员标签列 -->
+        <el-table-column prop="memberTag" label="会员标签" min-width="100" align="center" />
+        <!-- 所属群管列 -->
+        <el-table-column prop="groupName" label="所属群管" min-width="100" align="center" />
+        <!-- 所属经销商列 -->
+        <el-table-column prop="dealerName" label="所属经销商" min-width="100" align="center" />
+        <!-- 观看课程列 -->
+        <el-table-column prop="viewCount" label="观看课程" min-width="80" align="center" />
+        <!-- 完课课程列 -->
+        <el-table-column prop="finishCount" label="完课课程" min-width="80" align="center" />
+        <!-- 完课率列 -->
+        <el-table-column prop="finishRate" label="完课率" min-width="80" align="center" />
+        <!-- 观看次数列 -->
+        <el-table-column prop="viewTimes" label="观看次数" min-width="80" align="center" />
+        <!-- 完课次数列 -->
+        <el-table-column prop="finishTimes" label="完课次数" min-width="80" align="center" />
+        <!-- 视频率列 -->
+        <el-table-column prop="videoRate" label="视频率" min-width="80" align="center" />
+
+        <!-- 操作列 -->
+        <el-table-column label="操作" fixed="right" min-width="160" align="center">
+          <template slot-scope="scope">
+            <!-- 会员分析按钮 -->
+            <el-button type="text" size="small" style="color: #409EFF;">会员分析</el-button>
+            <!-- 红包记录按钮 -->
+            <el-button type="text" size="small" style="color: #409EFF;">红包记录</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="queryParams.total > 0"
+        :total="queryParams.total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import {dailyData} from '@/api/statistics/member'
+import {getCompanyListLikeName} from '@/api/company/company'
+import {getCompanyUserListLikeName} from '@/api/company/companyUser'
+import {getUserListLikeName} from '@/api/users/user'
+import {getCampListLikeName} from '@/api/course/userCourseCamp'
+import {getPeriodListLikeName} from '@/api/course/userCoursePeriod'
+import {getVideoListLikeName} from '@/api/course/userCourseVideo'
+import moment from "moment"
+
+export default {
+  // 组件名称
+  name: 'MemberStatistics',
+
+  // 组件数据
+  data() {
+    return {
+      // ============= 查询参数 =============
+      queryParams: {
+        dateType: 'day', // 日期类型:day-按日,month-按月
+        dateRange: [new Date(), new Date()], // 日期范围
+        dealerId: '', // 经销商ID
+        groupId: '', // 群组ID
+        memberId: '', // 会员ID
+        trainerId: '', // 训练官ID
+        campPeriod: '', // 营期
+        courseId: '', // 课程ID
+        mobile: '', // 手机号
+        pageNum: 1, // 当前页码
+        pageSize: 30, // 每页记录数
+        total: 0, // 总记录数
+      },
+
+      // ============= 页面状态 =============
+      loading: false, // 加载状态
+
+      // ============= 数据相关 =============
+      tableData: [], // 表格数据
+
+      // ============= 下拉选项数据 =============
+      companyOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyOptionsLoading: false,
+      dealerOptions: [], // 经销商选项
+      companyUserOptionsParams: {
+        name: undefined,
+        companyId: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      companyUserOptionsLoading: false,
+      groupOptions: [], // 群组选项
+      userOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      userOptionsLoading: false,
+      memberOptions: [], // 会员选项
+      trainerOptionsParams: {
+        name: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      trainerOptionsLoading: false,
+      trainerOptions: [], // 训练营选项
+      campPeriodOptionsParams: {
+        name: undefined,
+        campId: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      campPeriodOptionsLoading: false,
+      campPeriodOptions: [], // 营期选项
+      courseOptionsParams: {
+        name: undefined,
+        periodId: undefined,
+        hasNextPage: false,
+        pageNum: 1,
+        pageSize: 10
+      },
+      courseOptionsLoading: false,
+      courseOptions: [] // 课程选项
+    }
+  },
+
+  // ============= 生命周期钩子 =============
+  created() {
+    // 页面创建时初始化数据
+    this.getList() // 加载表格数据
+  },
+
+  // ============= 组件方法 =============
+  methods: {
+    /**
+     * 获取表格数据
+     * 根据查询参数从服务器获取会员统计数据
+     */
+    getList() {
+      this.loading = true // 显示加载中状态
+      // 模拟异步请求
+      let query = {
+        type: this.queryParams.dateType === 'day' ? 1 : 2,
+        startDate: moment(this.queryParams.dateRange[0]).format('YYYY-MM-DD'),
+        endDate: moment(this.queryParams.dateRange[1]).format('YYYY-MM-DD'),
+        companyId: this.queryParams.dealerId,
+        companyUserId: this.queryParams.groupId,
+        userId: this.queryParams.memberId,
+        phone: this.queryParams.mobile,
+        trainCampId: this.queryParams.trainerId,
+        periodId: this.queryParams.campPeriod,
+        videoId: this.queryParams.courseId,
+        pageNum: this.queryParams.pageNum,
+        pageSize: this.queryParams.pageSize
+      }
+      dailyData(query).then(response => {
+        if (response && response.code === 200) {
+          this.tableData = response.data.list
+          this.queryParams.total = response.data.total
+          this.loading = false // 隐藏加载中状态
+        }
+      })
+    },
+
+    // 切换日期类型
+    handleChangeDateType() {
+      this.queryParams.dateRange  = [new Date(), new Date()]
+
+      // 重新请求数据
+      this.getList()
+    },
+
+    /**
+     * 查询按钮点击事件
+     * 根据筛选条件重新查询数据
+     */
+    handleQuery() {
+      this.queryParams.pageNum = 1 // 重置为第一页
+      this.getList() // 重新获取数据
+    },
+
+    /**
+     * 重置查询条件
+     * 清空所有筛选条件并重新加载数据
+     */
+    resetQuery() {
+      // 重置所有查询条件
+      this.queryParams = {
+        dateType: 'day', // 重置日期类型为按天
+        dateRange: [], // 清空日期范围
+        publicId: '', // 清空公众号ID
+        dealerId: '', // 清空经销商ID
+        groupId: '', // 清空群组ID
+        memberId: '', // 清空会员ID
+        trainerId: '', // 清空训练官ID
+        campPeriod: '', // 清空营期
+        courseId: '', // 清空课程ID
+        mobile: '' // 清空手机号
+      }
+      this.getList() // 重新获取数据
+    },
+
+    // 加载经销商选项
+    loadCompanyOptions(query) {
+      this.dealerOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyOptionsParams.pageNum = 1
+      this.companyOptionsParams.name = query
+      this.companyOptionsLoading = true;
+      this.getCompanyListLikeName()
+    },
+    getCompanyListLikeName() {
+      getCompanyListLikeName(this.companyOptionsParams).then(response => {
+        this.dealerOptions = [...this.dealerOptions, ...response.data.list]
+        this.companyOptionsParams.hasNextPage = response.data.hasNextPage
+        this.companyOptionsLoading = false;
+      });
+    },
+    loadMoreCompanyOptions() {
+      if (!this.companyOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyOptionsParams.pageNum += 1
+      this.getCompanyListLikeName()
+    },
+    handleChangeDealer(val) {
+      this.groupOptions = [];
+      this.companyUserOptionsLoading = true;
+      if (!val) {
+        this.companyUserOptionsParams.companyId = undefined
+        this.getCompanyUserListLikeName()
+        return
+      }
+
+      this.companyUserOptionsParams.companyId = val
+      this.companyUserOptionsParams.pageNum = 1
+      this.getCompanyUserListLikeName()
+    },
+
+    // 群组选项
+    loadCompanyUserOptions(query) {
+      this.groupOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum = 1
+      this.companyUserOptionsParams.name = query
+      this.companyUserOptionsLoading = true;
+      this.getCompanyUserListLikeName()
+    },
+    getCompanyUserListLikeName() {
+      getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
+        this.groupOptions = [...this.groupOptions, ...response.data.list]
+        this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
+        this.companyUserOptionsLoading = false;
+      });
+    },
+    loadMoreCompanyUserOptions() {
+      if (!this.companyUserOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.companyUserOptionsParams.pageNum += 1
+      this.getCompanyUserListLikeName()
+    },
+
+    // 会员选项
+    loadUserOptions(query) {
+      this.memberOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.userOptionsParams.pageNum = 1
+      this.userOptionsParams.name = query
+      this.userOptionsLoading = true;
+      this.getUserListLikeName()
+    },
+    getUserListLikeName() {
+      getUserListLikeName(this.userOptionsParams).then(response => {
+        this.memberOptions = [...this.memberOptions, ...response.data.list]
+        this.userOptionsParams.hasNextPage = response.data.hasNextPage
+        this.userOptionsLoading = false;
+      });
+    },
+    loadMoreUserOptions() {
+      if (!this.userOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.userOptionsParams.pageNum += 1
+      this.getUserListLikeName()
+    },
+
+    // 训练营选项
+    loadTrainerOptions(query) {
+      this.trainerOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.trainerOptionsParams.pageNum = 1
+      this.trainerOptionsParams.name = query
+      this.trainerOptionsLoading = true;
+      this.getTrainerListLikeName()
+    },
+    getTrainerListLikeName() {
+      getCampListLikeName(this.trainerOptionsParams).then(response => {
+        this.trainerOptions = [...this.trainerOptions, ...response.data.list]
+        this.trainerOptionsParams.hasNextPage = response.data.hasNextPage
+        this.trainerOptionsLoading = false;
+      });
+    },
+    loadMoreTrainerOptions() {
+      if (!this.trainerOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.trainerOptionsParams.pageNum += 1
+      this.getTrainerListLikeName()
+    },
+    changeTrainer(val) {
+      this.campPeriodOptions = [];
+      this.campPeriodOptionsLoading = true;
+      if (!val) {
+        this.campPeriodOptionsParams.companyId = undefined
+        this.getPeriodListLikeName()
+        return
+      }
+
+      this.campPeriodOptionsParams.companyId = val
+      this.campPeriodOptionsParams.pageNum = 1
+      this.getPeriodListLikeName()
+    },
+
+    // 营期选项
+    loadPeriodOptions(query) {
+      this.campPeriodOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.campPeriodOptionsParams.pageNum = 1
+      this.campPeriodOptionsParams.name = query
+      this.campPeriodOptionsLoading = true;
+      this.getPeriodListLikeName()
+    },
+    getPeriodListLikeName() {
+      getPeriodListLikeName(this.campPeriodOptionsParams).then(response => {
+        this.campPeriodOptions = [...this.campPeriodOptions, ...response.data.list]
+        this.campPeriodOptionsParams.hasNextPage = response.data.hasNextPage
+        this.campPeriodOptionsLoading = false;
+      });
+    },
+    loadMorePeriodOptions() {
+      if (!this.campPeriodOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.campPeriodOptionsParams.pageNum += 1
+      this.getPeriodListLikeName()
+    },
+    changeCampPeriod(val) {
+      this.courseOptions = []
+      this.courseOptionsLoading = true;
+      if (!val) {
+        this.courseOptionsParams.periodId = undefined
+        this.getCourseListLikeName()
+        return
+      }
+
+      this.courseOptionsParams.periodId = val
+      this.courseOptionsParams.pageNum = 1
+      this.getCourseListLikeName()
+    },
+
+    // 课程选项
+    loadCourseOptions(query) {
+      this.courseOptions = [];
+      if (query === '') {
+        return;
+      }
+
+      this.courseOptionsParams.pageNum = 1
+      this.courseOptionsParams.name = query
+      this.courseOptionsLoading = true;
+      this.getCourseListLikeName()
+    },
+    getCourseListLikeName() {
+      getVideoListLikeName(this.courseOptionsParams).then(response => {
+        this.courseOptions = [...this.courseOptions, ...response.data.list]
+        this.courseOptionsParams.hasNextPage = response.data.hasNextPage
+        this.courseOptionsLoading = false;
+      });
+    },
+    loadMoreCourseOptions() {
+      if (!this.courseOptionsParams.hasNextPage) {
+        return;
+      }
+
+      this.courseOptionsParams.pageNum += 1
+      this.getCourseListLikeName()
+    },
+  }
+}
+</script>
+
+<style scoped>
+.member-statistics-page {
+  padding: 10px;
+}
+/* 会员统计页面容器样式 */
+.member-statistics-container {
+  padding: 10px;
+  background-color: #fff;
+}
+
+/* 筛选区域样式 */
+.filter-container {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  background-color: #fff;
+  padding: 15px;
+  border-radius: 4px;
+}
+
+.filter-item {
+  margin-right: 15px;
+  margin-bottom: 10px;
+}
+
+/* 表格容器样式 */
+.table-container {
+  background-color: #FFF;
+  height: 72vh;
+}
+
+/* 表格深度样式 */
+::v-deep .el-table th {
+  color: #606266;
+  font-weight: bold;
+}
+
+::v-deep .el-table--border th, ::v-deep .el-table--border td {
+  border-right: 1px solid #EBEEF5;
+}
+
+::v-deep .el-table {
+  border: 1px solid #EBEEF5;
+  border-bottom: none;
+}
+
+::v-deep .el-input__inner {
+  border-radius: 4px;
+}
+
+::v-deep .el-button {
+  border-radius: 4px;
+}
+</style>