소스 검색

Merge branch 'master' into 企微聊天

# Conflicts:
#	src/views/qw/externalContact/index.vue
ct 1 일 전
부모
커밋
bcda7ac0bc

+ 26 - 0
.env.prod-sczy

@@ -0,0 +1,26 @@
+# 页面标题
+VUE_APP_TITLE =致医SCRM销售端
+# 公司名称
+VUE_APP_COMPANY_NAME =云联融智互联网医院有限公司
+# ICP备案号
+VUE_APP_ICP_RECORD =渝ICP备2024031984号-1
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/sczy.png
+
+# 生产环境配置
+ENV = 'production'
+
+# FS管理系统/开发环境
+VUE_APP_BASE_API = '/prod-api'
+
+#默认 1、会员 2、企微
+VUE_APP_COURSE_DEFAULT = 2
+
+
+#项目所属
+VUE_APP_PROJECT_FROM=sczy
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 1 - 0
package.json

@@ -46,6 +46,7 @@
     "build:prod-hyt": "vue-cli-service build --mode prod-hyt",
     "build:prod-hst": "vue-cli-service build --mode prod-hst",
     "build:prod-hat": "vue-cli-service build --mode prod-hat",
+    "build:prod-sczy": "vue-cli-service build --mode prod-sczy",
     "build:prod-ddgy": "vue-cli-service build --mode prod-ddgy",
     "build:prod-czt": "vue-cli-service build --mode prod-czt",
     "build:prod-jnsyj": "vue-cli-service build --mode prod-jnsyj",

+ 19 - 0
src/api/live/liveOrder.js

@@ -17,6 +17,25 @@ export function getLiveOrder(orderId) {
   })
 }
 
+
+// 查询订单列表
+export function listLiveOrderZm(query) {
+  return request({
+    url: '/live/liveOrder/listZm',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出订单
+export function exportLiveOrderZm(query) {
+  return request({
+    url: '/live/liveOrder/exportZm',
+    method: 'get',
+    params: query
+  })
+}
+
 // 新增订单
 export function addLiveOrder(data) {
   return request({

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

@@ -324,3 +324,63 @@ export function getWatchCourseStatisticsData(param){
     data: safeParam
   })
 }
+
+/**
+ * 日综合统计(按照天分组)
+ * @returns {*}
+ */
+export function getStatisticsData(data) {
+  return request({
+    url: '/crm/ComprehensiveStatistics/statisticMain',
+    method: 'post',
+    data: data  // 使用 data 而不是 params
+  })
+}
+
+/**
+ * 获取公司下拉
+ * @returns {*}
+ */
+export function getSearchCompanyInfo(param){
+  return request({
+    url: '/crm/ComprehensiveStatistics/getSearchCompanyInfo',
+    method: 'get',
+    params: param
+  })
+}
+
+/**
+ * 获取部门下拉
+ * @returns {*}
+ */
+export function getSearchDeptInfo(param){
+  return request({
+    url: '/crm/ComprehensiveStatistics/getSearchDeptInfo',
+    method: 'get',
+    params: param
+  })
+}
+
+/**
+ * 获取用户下拉
+ * @returns {*}
+ */
+export function getSearchUserInfo(param){
+  return request({
+    url: '/crm/ComprehensiveStatistics/getSearchUserInfo',
+    method: 'get',
+    params: param
+  })
+}
+
+/**
+ * 综合统计
+ * @returns {*}
+ */
+export function getStatisticsDataN(data) {
+  return request({
+    url: '/crm/ComprehensiveStatistics/statisticMainN',
+    method: 'post',
+    data: data  // 使用 data 而不是 params
+  })
+}

BIN
src/assets/logo/sczy.png


+ 1 - 1
src/views/course/courseUserStatistics/qw/index.vue

@@ -84,7 +84,7 @@
           size="mini"
           :loading="exportLoading"
           @click="handleExport"
-          v-hasPermi="['course:courseRedPacketLog:export']"
+          v-hasPermi="['course:courseWatchLog:export']"
           >导出</el-button
         >
       </el-col>

+ 3 - 2
src/views/course/courseWatchLog/watchLog.vue

@@ -393,6 +393,7 @@
       <el-table-column label="更新时间" align="center" prop="updateTime" width="100px" />
       <el-table-column label="完课时间" align="center" prop="finishTime" width="100px" />
       <el-table-column label="营期时间" align="center" prop="campPeriodTime" />
+      <el-table-column label="进线时间" align="center" prop="qecCreateTime" />
       <el-table-column label="是否领奖" align="center" prop="rewardType" >
         <template slot-scope="scope">
           <el-tag
@@ -695,7 +696,7 @@ export default {
       createTimeText: '',
       scheduleTimeText: '',  // 新增
       updateTimeText: '',    // 新增
-      qecCreateTimeText: '', // 新增
+      qecCreateTimeText: [], // 新增
       periodTimeText: '', // 营期课程时间
 
       scheduleTime: [],  // 改为数组
@@ -1081,7 +1082,7 @@ export default {
           return;
         }
 
-        this.qecCreateTimeText = this.formatDateRange(qecCreateTime);
+        // this.qecCreateTimeText = this.formatDateRange(qecCreateTime);
         this.queryParams.qecSTime = qecCreateTime[0] || null;
         this.queryParams.qecETime = qecCreateTime[1] || null;
       } else {

+ 49 - 56
src/views/course/userCoursePeriod/index.vue

@@ -33,40 +33,6 @@
     </el-form>
 
     <el-row :gutter="10" class="mb8">
-<!--      <el-col :span="1.5">-->
-<!--        <el-button-->
-<!--          type="primary"-->
-<!--          plain-->
-<!--          icon="el-icon-plus"-->
-<!--          size="mini"-->
-<!--          @click="handleAdd"-->
-<!--          v-hasPermi="['course:period:add']"-->
-<!--        >新增-->
-<!--        </el-button>-->
-<!--      </el-col>-->
-<!--      <el-col :span="1.5">-->
-<!--        <el-button-->
-<!--          type="warning"-->
-<!--          plain-->
-<!--          icon="el-icon-download"-->
-<!--          size="mini"-->
-<!--          @click="handleExport"-->
-<!--          v-hasPermi="['course:period:export']"-->
-<!--        >导出-->
-<!--        </el-button>-->
-<!--      </el-col>-->
-<!--      <el-col :span="1.5">-->
-<!--        <el-button-->
-<!--          type="primary"-->
-<!--          plain-->
-<!--          icon="el-icon-edit"-->
-<!--          size="mini"-->
-<!--          @click="handleBatchSetRedPacket"-->
-<!--          v-hasPermi="['course:period:export']"-->
-<!--          :disabled="batchSetRedPacketDisabled"-->
-<!--        >批量设置红包-->
-<!--        </el-button>-->
-<!--      </el-col>-->
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -95,6 +61,31 @@
             @click="handlePeriodSettings(scope.row)"
           >营期相关设置
           </el-button>
+          <!-- 根据 isNeedRegisterMember 动态显示按钮 -->
+          <el-button
+            v-if="scope.row.isNeedRegisterMember === 0"
+            size="mini"
+            type="text"
+            icon="el-icon-open"
+            @click="handlePeriodUser(scope.row, 1)"
+          >开启单独注册会员
+          </el-button>
+          <el-button
+            v-else-if="scope.row.isNeedRegisterMember === 1"
+            size="mini"
+            type="text"
+            icon="el-icon-turn-off"
+            @click="handlePeriodUser(scope.row, 0)"
+          >关闭单独注册会员
+          </el-button>
+          <el-button
+            v-else
+            size="mini"
+            type="text"
+            icon="el-icon-open"
+            @click="handlePeriodUser(scope.row, 1)"
+          >开启单独注册会员
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -248,13 +239,6 @@
       </div>
     </el-dialog>
 
-    <!--    <red-packet-->
-    <!--      :visible.sync="redPacketVisible"-->
-    <!--      :periodId="currentRedPacketData.periodId"-->
-    <!--      :videoId="currentRedPacketData.videoId"-->
-    <!--      @success="handleRedPacketSuccess"-->
-    <!--    />-->
-
     <!-- 营期相关设置抽屉 -->
     <el-drawer
       title="营期相关设置"
@@ -455,7 +439,6 @@ export default {
     };
   },
   created() {
-
     courseList().then(response => {
       this.courseList = response.list;
     });
@@ -569,6 +552,30 @@ export default {
         this.isDisabledDateRange = true;
       });
     },
+    /** 处理单独注册会员开关 */
+    handlePeriodUser(data, open) {
+      const actionText = open === 1 ? '开启' : '关闭';
+      this.$confirm(`确定要${actionText}【${data.periodName}】的单独注册会员功能吗?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        data.isNeedRegisterMember = open;
+        updatePeriod(data).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess(`${actionText}成功`);
+            this.getList();
+          } else {
+            this.msgError(response.msg || `${actionText}失败`);
+          }
+        }).catch(error => {
+          console.error(`${actionText}单独注册会员失败:`, error);
+          this.msgError(`${actionText}失败`);
+        });
+      }).catch(() => {
+        this.$message.info('已取消操作');
+      });
+    },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {
@@ -646,19 +653,6 @@ export default {
       this.batchRedPacketVisible = true;
     },
 
-    /** 处理批量设置红包保存 */
-    // handleBatchRedPacketSave(data) {
-    //   // 这里等待接口提供后补充具体实现
-    //   // 示例代码:
-    //   // batchSetRedPacket(data).then(response => {
-    //   //   if (response.code === 200) {
-    //   //     this.$message.success('批量设置成功');
-    //   //     this.getList();
-    //   //   }
-    //   // });
-    //   this.batchRedPacketVisible = false;
-    // },
-
     /** 获取公司下拉列表*/
     getCompanyList() {
       this.loading = true;
@@ -942,7 +936,6 @@ export default {
         this.form.periodStartingTime = this.form.dateRange[0];
         this.form.periodEndTime = this.form.dateRange[1];
 
-
         // 转换为天数
         let days = this.getDiff(this.form.periodStartingTime, this.form.periodEndTime);
         for (let i = 0; i < days; i++) {

+ 634 - 0
src/views/crm/statistics/comprehensiveStatistics.vue

@@ -0,0 +1,634 @@
+<template>
+  <div class="app-container">
+    <div class="app-content">
+      <div class="title">日综合统计看板</div>
+
+      <el-form class="search-form" :inline="true" label-width="90px">
+        <el-row :gutter="10">
+          <!-- 时间范围选择 -->
+          <el-col :span="6">
+            <el-form-item label="时间范围">
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                :picker-options="pickerOptions"
+                style="width: 100%">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+
+          <!-- 统计维度选择 -->
+          <el-col :span="4">
+            <el-form-item label="统计维度">
+              <el-select
+                v-model="queryParams.dimension"
+                placeholder="请选择统计维度"
+                @change="handleDimensionChange"
+                clearable
+                style="width: 100%">
+                <el-option label="个人" :value="1"></el-option>
+                <el-option label="公司" :value="2"></el-option>
+                <el-option label="部门" :value="3"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 公司选择 -->
+          <el-col :span="4" v-if="showCompanySelect">
+            <el-form-item label="选择公司">
+              <el-select
+                v-model="queryParams.companyId"
+                placeholder="请选择公司"
+                @change="handleCompanyChange"
+                clearable
+                :disabled="!queryParams.dimension"
+                style="width: 100%">
+                <el-option
+                  v-for="company in companyList"
+                  :key="company.companyId"
+                  :label="company.companyName"
+                  :value="company.companyId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 部门选择 -->
+          <el-col :span="4" v-if="showDepartmentSelect">
+            <el-form-item label="选择部门">
+              <el-cascader
+                v-model="selectedDeptIds"
+                :options="deptTreeData"
+                :props="deptCascaderProps"
+                placeholder="请选择部门"
+                clearable
+                style="width: 100%"
+                @change="handleDeptChange">
+              </el-cascader>
+            </el-form-item>
+          </el-col>
+
+          <!-- 操作按钮 -->
+          <el-col :span="6">
+            <el-form-item label-width="0" style="padding-top: 35px;">
+              <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-button type="success" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading">导出</el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <!-- 数据表格 -->
+    <div class="table-section">
+      <el-table v-loading="loading" :data="paginatedTableData" border style="width: 100%" height="600">
+        <!-- 添加时间列 -->
+        <el-table-column prop="companyName" label="公司名称"/>
+        <el-table-column prop="deptName" label="部门名称" v-if="showDeptNameColumn" />
+        <!-- 根据维度决定是否显示人员姓名 -->
+        <el-table-column prop="companyUserName" label="人员名称" v-if="showUserNameColumn" />
+        <!-- 更新以下列为新的字段名 -->
+        <el-table-column prop="sendCount" label="发课数">
+          <template slot-scope="scope">
+            {{ scope.row.sendCount || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="completeNum" label="完课数">
+          <template slot-scope="scope">
+            {{ scope.row.completeNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 完播率 -->
+        <el-table-column prop="completeRate" label="完播率" width="100">
+          <template slot-scope="scope">
+            {{ calculateCompleteRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="answerNum" label="答题数">
+          <template slot-scope="scope">
+            {{ scope.row.answerNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 答题率 -->
+        <el-table-column prop="answerRate" label="答题率" width="100">
+          <template slot-scope="scope">
+            {{ calculateAnswerRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketNum" label="红包领取数">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketNum || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketAmount" label="红包金额(元)">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketAmount || 0 }}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="currentPage"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="pageSize"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.length"        style="margin-top: 20px; text-align: right;">
+      </el-pagination>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { getStatisticsDataN, getSearchUserInfo, getSearchCompanyInfo, getSearchDeptInfo } from "@/api/statistics/statistics";
+
+export default {
+  data() {
+    return {
+      exportLoading: false, // 添加导出加载状态
+      loading: false,
+      lastQueryDimension: null, // 记录上次查询的维度
+      companyList: [],
+      deptList: [], // 原始部门数据
+      deptTreeData: [], // 树形部门数据
+      selectedDeptIds: [], // 选中的部门ID路径
+      userList: [],
+      rawData: [],
+      tableData: [],
+      currentPage: 1,
+      pageSize: 20,
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近三个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
+      },
+      deptCascaderProps: {
+        value: 'deptId',
+        label: 'deptName',
+        children: 'children',
+        expandTrigger: 'hover',
+        checkStrictly: true, // 允许选择任意一级
+        emitPath: false // 只返回选中节点的值,而不是整个路径
+      },
+      queryParams: {
+        dimension: null,
+        companyId: null,
+        deptId: null,
+        userId: null
+      }
+    };
+  },
+
+  computed: {
+    paginatedTableData() {
+      const start = (this.currentPage - 1) * this.pageSize;
+      const end = start + this.pageSize;
+      return this.tableData.slice(start, end);
+    },
+
+    showCompanySelect() {
+      // 公司维度不需要显示公司选择,其他维度都需要
+      return this.queryParams.dimension && this.queryParams.dimension !== 2;
+    },
+
+    showDepartmentSelect() {
+      // 部门维度不需要显示部门选择,个人维度需要显示
+      return this.queryParams.dimension === 1 && this.queryParams.companyId;
+    },
+
+
+
+    // 控制部门名称列显示
+    showDeptNameColumn() {
+      // 基于上次查询的维度来决定是否显示部门名称列
+      return this.lastQueryDimension && this.lastQueryDimension !== 2;
+    },
+
+    // 控制人员姓名列显示
+    showUserNameColumn() {
+      // 只有个人维度显示人员姓名列
+      return this.lastQueryDimension === 1;
+    }
+  },
+
+  mounted() {
+    // 设置默认时间为当天
+    const today = new Date();
+    this.dateRange = [today, today];
+    // 默认设置统计维度为公司
+    this.queryParams.dimension = 2;
+    this.loadData();
+    // 页面初始化时查询一次数据
+    this.fetchStatisticsData();
+  },
+
+  methods: {
+    // 在 loadData 方法中也移除自动查询
+    loadData() {
+      // 使用 getSearchCompanyInfo 获取公司下拉数据
+      getSearchCompanyInfo().then(response => {
+        this.companyList = response.data || [];
+
+        // 默认选择第一个公司(如果存在)
+        if (this.companyList.length > 0 && this.queryParams.dimension !== 2) {
+          this.queryParams.companyId = this.companyList[0].companyId;
+          // 加载该公司的部门数据
+          return this.loadDeptData(this.queryParams.companyId);
+        }
+      }).then(() => {
+        // 移除自动查询调用,只保留初始加载
+        // return this.fetchStatisticsData();
+      }).catch(error => {
+        console.error('数据加载失败:', error);
+        this.$message.error('数据加载失败');
+      });
+    },
+
+    loadDeptData(companyId) {
+      if (!companyId) return Promise.resolve();
+
+      return getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+    },
+
+    // 构建部门树形结构
+    buildDeptTree(deptList) {
+      if (!deptList || deptList.length === 0) return [];
+
+      // 创建部门映射
+      const deptMap = {};
+      deptList.forEach(dept => {
+        // 如果后端返回的数据中已经有children字段且有值,则使用后端的children
+        // 否则初始化为空数组
+        deptMap[dept.deptId] = {
+          ...dept,
+          children: (dept.children && dept.children.length > 0) ? dept.children : []
+        };
+      });
+
+      // 构建树形结构
+      const tree = [];
+      deptList.forEach(dept => {
+        if (dept.parentId === 0 || dept.parentId === null) {
+          // 顶级部门
+          tree.push(deptMap[dept.deptId]);
+        } else {
+          // 子部门 - 只有在后端没有返回children的情况下才手动构建
+          if (deptMap[dept.parentId] && (!dept.children || dept.children.length === 0)) {
+            deptMap[dept.parentId].children.push(deptMap[dept.deptId]);
+          }
+        }
+      });
+
+      // 清理空的children数组,保持数据结构整洁
+      const cleanEmptyChildren = (nodes) => {
+        nodes.forEach(node => {
+          if (node.children && node.children.length === 0) {
+            delete node.children;
+          } else if (node.children && node.children.length > 0) {
+            cleanEmptyChildren(node.children);
+          }
+        });
+      };
+      cleanEmptyChildren(tree);
+
+      return tree;
+    },
+
+    // 添加时间格式化方法
+    formatDateTime(dateString) {
+      if (!dateString) return '';
+      // 移除时区信息并格式化日期时间
+      const date = new Date(dateString.replace(/\.\d{3}\+\d{4}$/, ''));
+      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}`;
+    },
+
+    formatDate(date) {
+      if (!date) return '';
+      const d = new Date(date);
+      const year = d.getFullYear();
+      const month = String(d.getMonth() + 1).padStart(2, '0');
+      const day = String(d.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    fetchStatisticsData() {
+      this.loading = true; // 开始请求时设置 loading 为 true
+      // 构造请求参数对象
+      const params = {
+        dimension: this.queryParams.dimension,
+        startTime: this.formatDate(this.dateRange[0]),
+        endTime: this.formatDate(this.dateRange[1])
+      };
+
+      // 根据新规则设置id参数
+      if (this.queryParams.dimension === 1 && this.queryParams.deptId) {
+        // 个人维度传递部门ID
+        params.id = this.queryParams.deptId;
+      } else if (this.queryParams.dimension === 3 && this.queryParams.companyId) {
+        // 部门维度传递公司ID
+        params.id = this.queryParams.companyId;
+      }
+      // 公司维度不传递ID参数
+
+      // 以POST方式发送请求体
+      return getStatisticsDataN(params).then(response => {
+        this.rawData = response.data || [];
+        this.tableData = [...this.rawData];
+        this.currentPage = 1;
+      }).catch(error => {
+        console.error('统计数据加载失败:', error);
+        this.$message.error('统计数据加载失败');
+      }).finally(() => {
+        this.loading = false; // 请求完成后设置 loading 为 false
+      });
+    },
+
+    handleCompanyChange(companyId) {
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+
+      if (!companyId) return;
+
+      // 使用 getSearchDeptInfo 获取部门下拉数据
+      getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        // 仅在个人维度下默认选择第一个部门
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+      // 移除.then()后面的自动查询调用
+    },
+    handleDeptChange(selectedDeptId) {
+      // 更新选中的部门ID
+      this.queryParams.deptId = selectedDeptId;
+    },
+
+    handleQuery() {
+      // 记录当前查询的维度
+      this.lastQueryDimension = this.queryParams.dimension;
+      // 触发统计数据请求
+      this.fetchStatisticsData();
+    },
+
+    handleDimensionChange() {
+      // 重置后续选择项
+      this.queryParams.companyId = null;
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+    },
+
+    resetQuery() {
+      this.queryParams = {
+        dimension: 2, // 重置为公司维度
+        companyId: null,
+        deptId: null,
+        userId: null
+      };
+      this.selectedDeptIds = []; // 重置部门选择
+      // 重置时间为当天
+      const today = new Date();
+      this.dateRange = [today, today];
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+      this.tableData = [...this.rawData];
+      this.currentPage = 1;
+      // 重置后立即发送请求
+      this.lastQueryDimension = 2; // 同时更新lastQueryDimension
+      this.fetchStatisticsData();
+    },
+
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+    },
+
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+
+    // 计算完播率:完课数 / 发送数
+    calculateCompleteRate(row) {
+      const sendCount = row.sendCount || 0;
+      const completeNum = row.completeNum || 0;
+
+      if (sendCount === 0) {
+        return '0%';
+      }
+
+      const rate = (completeNum / sendCount * 100).toFixed(2);
+      return rate + '%';
+    },
+
+    // 计算答题率:答题数 / 完课数
+    calculateAnswerRate(row) {
+      const completeNum = row.completeNum || 0;
+      const answerNum = row.answerNum || 0;
+
+      if (completeNum === 0) {
+        return '0%';
+      }
+
+      const rate = (answerNum / completeNum * 100).toFixed(2);
+      return rate + '%';
+    },
+    // 添加导出方法
+    handleExport() {
+      if (this.tableData.length === 0) {
+        this.$message.warning('没有可导出的数据');
+        return;
+      }
+
+      this.exportLoading = true;
+
+      try {
+        // 创建 CSV 内容
+        const headers = [
+         '公司名称', '部门名称', '人员名称',
+          '发课数', '完课数', '完播率', '答题数', '答题率',
+          '红包领取数', '红包金额(元)'
+        ];
+
+        const csvContent = [
+          headers.join(','),
+          ...this.tableData.map(row => [
+            row.companyName || '',
+            row.deptName || '',
+            row.companyUserName || '',
+            row.sendCount || 0,
+            row.completeNum || 0,
+            this.calculateCompleteRate(row),
+            row.answerNum || 0,
+            this.calculateAnswerRate(row),
+            row.redPacketNum || 0,
+            row.redPacketAmount || 0
+          ].map(field => `"${field}"`).join(','))
+        ].join('\n');
+
+        // 创建下载链接
+        const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
+        const link = document.createElement('a');
+        const url = URL.createObjectURL(blob);
+
+        link.setAttribute('href', url);
+
+        // link.setAttribute('download', `综合统计看板_${new Date().toISOString().slice(0, 10)}.csv`);
+        // 修改为:
+        const now = new Date();
+        const timestamp = now.getFullYear() +
+          String(now.getMonth() + 1).padStart(2, '0') +
+          String(now.getDate()).padStart(2, '0') +
+          String(now.getHours()).padStart(2, '0') +
+          String(now.getMinutes()).padStart(2, '0') +
+          String(now.getSeconds()).padStart(2, '0');
+        link.setAttribute('download', `综合统计看板_${timestamp}.csv`);
+        link.style.visibility = 'hidden';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+
+        this.$message.success('导出成功');
+      } catch (error) {
+        console.error('导出失败:', error);
+        this.$message.error('导出失败');
+      } finally {
+        this.exportLoading = false;
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.app-container {
+  border: 1px solid #e6e6e6;
+  padding: 12px;
+  background-color: #f5f7fa;
+}
+
+.app-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
+
+.title {
+  text-align: center;
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20px;
+}
+
+.search-form {
+  margin-bottom: 0;
+}
+
+.search-form .el-form-item {
+  margin-bottom: 18px;
+  width: 100%;
+}
+
+.search-form .el-row {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}
+
+.table-section {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-section .el-table {
+  width: 100% !important;
+}
+
+@media (min-width: 1200px) {
+  .table-section {
+    padding: 20px 50px;
+  }
+}
+</style>

+ 640 - 0
src/views/crm/statistics/dayComprehensiveStatistics.vue

@@ -0,0 +1,640 @@
+<template>
+  <div class="app-container">
+    <div class="app-content">
+      <div class="title">综合统计看板</div>
+
+      <el-form class="search-form" :inline="true" label-width="90px">
+        <el-row :gutter="10">
+          <!-- 时间范围选择 -->
+          <el-col :span="6">
+            <el-form-item label="时间范围">
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                :picker-options="pickerOptions"
+                style="width: 100%">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+
+          <!-- 统计维度选择 -->
+          <el-col :span="4">
+            <el-form-item label="统计维度">
+              <el-select
+                v-model="queryParams.dimension"
+                placeholder="请选择统计维度"
+                @change="handleDimensionChange"
+                clearable
+                style="width: 100%">
+                <el-option label="个人" :value="1"></el-option>
+                <el-option label="公司" :value="2"></el-option>
+                <el-option label="部门" :value="3"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 公司选择 -->
+          <el-col :span="4" v-if="showCompanySelect">
+            <el-form-item label="选择公司">
+              <el-select
+                v-model="queryParams.companyId"
+                placeholder="请选择公司"
+                @change="handleCompanyChange"
+                clearable
+                :disabled="!queryParams.dimension"
+                style="width: 100%">
+                <el-option
+                  v-for="company in companyList"
+                  :key="company.companyId"
+                  :label="company.companyName"
+                  :value="company.companyId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 部门选择 -->
+          <el-col :span="4" v-if="showDepartmentSelect">
+            <el-form-item label="选择部门">
+              <el-cascader
+                v-model="selectedDeptIds"
+                :options="deptTreeData"
+                :props="deptCascaderProps"
+                placeholder="请选择部门"
+                clearable
+                style="width: 100%"
+                @change="handleDeptChange">
+              </el-cascader>
+            </el-form-item>
+          </el-col>
+
+          <!-- 操作按钮 -->
+          <el-col :span="6">
+            <el-form-item label-width="0" style="padding-top: 35px;">
+              <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-button type="success" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading">导出</el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <!-- 数据表格 -->
+    <div class="table-section">
+      <el-table v-loading="loading" :data="paginatedTableData" border style="width: 100%" height="600">
+        <!-- 添加时间列 -->
+        <el-table-column prop="statisticsTime" label="统计时间" width="180">
+          <template slot-scope="scope">
+            {{ formatDateTime(scope.row.statisticsTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="companyName" label="公司名称"/>
+        <el-table-column prop="deptName" label="部门名称" v-if="showDeptNameColumn" />
+        <!-- 根据维度决定是否显示人员姓名 -->
+        <el-table-column prop="companyUserName" label="人员名称" v-if="showUserNameColumn" />
+        <!-- 更新以下列为新的字段名 -->
+        <el-table-column prop="sendCount" label="发课数">
+          <template slot-scope="scope">
+            {{ scope.row.sendCount || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="completeNum" label="完课数">
+          <template slot-scope="scope">
+            {{ scope.row.completeNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 完播率 -->
+        <el-table-column prop="completeRate" label="完播率" width="100">
+          <template slot-scope="scope">
+            {{ calculateCompleteRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="answerNum" label="答题数">
+          <template slot-scope="scope">
+            {{ scope.row.answerNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 答题率 -->
+        <el-table-column prop="answerRate" label="答题率" width="100">
+          <template slot-scope="scope">
+            {{ calculateAnswerRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketNum" label="红包领取数">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketNum || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketAmount" label="红包金额(元)">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketAmount || 0 }}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="currentPage"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="pageSize"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.length"        style="margin-top: 20px; text-align: right;">
+      </el-pagination>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { getStatisticsData, getSearchUserInfo, getSearchCompanyInfo, getSearchDeptInfo } from "@/api/statistics/statistics";
+
+export default {
+  data() {
+    return {
+      exportLoading: false, // 添加导出加载状态
+      loading: false,
+      lastQueryDimension: null, // 记录上次查询的维度
+      companyList: [],
+      deptList: [], // 原始部门数据
+      deptTreeData: [], // 树形部门数据
+      selectedDeptIds: [], // 选中的部门ID路径
+      userList: [],
+      rawData: [],
+      tableData: [],
+      currentPage: 1,
+      pageSize: 20,
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近三个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
+      },
+      deptCascaderProps: {
+        value: 'deptId',
+        label: 'deptName',
+        children: 'children',
+        expandTrigger: 'hover',
+        checkStrictly: true, // 允许选择任意一级
+        emitPath: false // 只返回选中节点的值,而不是整个路径
+      },
+      queryParams: {
+        dimension: null,
+        companyId: null,
+        deptId: null,
+        userId: null
+      }
+    };
+  },
+
+  computed: {
+    paginatedTableData() {
+      const start = (this.currentPage - 1) * this.pageSize;
+      const end = start + this.pageSize;
+      return this.tableData.slice(start, end);
+    },
+
+    showCompanySelect() {
+      // 公司维度不需要显示公司选择,其他维度都需要
+      return this.queryParams.dimension && this.queryParams.dimension !== 2;
+    },
+
+    showDepartmentSelect() {
+      // 部门维度不需要显示部门选择,个人维度需要显示
+      return this.queryParams.dimension === 1 && this.queryParams.companyId;
+    },
+
+
+
+    // 控制部门名称列显示
+    showDeptNameColumn() {
+      // 基于上次查询的维度来决定是否显示部门名称列
+      return this.lastQueryDimension && this.lastQueryDimension !== 2;
+    },
+
+    // 控制人员姓名列显示
+    showUserNameColumn() {
+      // 只有个人维度显示人员姓名列
+      return this.lastQueryDimension === 1;
+    }
+  },
+
+  mounted() {
+    // 设置默认时间为当天
+    const today = new Date();
+    this.dateRange = [today, today];
+    // 默认设置统计维度为公司
+    this.queryParams.dimension = 2;
+    this.loadData();
+    // 页面初始化时查询一次数据
+    this.fetchStatisticsData();
+  },
+
+  methods: {
+    // 在 loadData 方法中也移除自动查询
+    loadData() {
+      // 使用 getSearchCompanyInfo 获取公司下拉数据
+      getSearchCompanyInfo().then(response => {
+        this.companyList = response.data || [];
+
+        // 默认选择第一个公司(如果存在)
+        if (this.companyList.length > 0 && this.queryParams.dimension !== 2) {
+          this.queryParams.companyId = this.companyList[0].companyId;
+          // 加载该公司的部门数据
+          return this.loadDeptData(this.queryParams.companyId);
+        }
+      }).then(() => {
+        // 移除自动查询调用,只保留初始加载
+        // return this.fetchStatisticsData();
+      }).catch(error => {
+        console.error('数据加载失败:', error);
+        this.$message.error('数据加载失败');
+      });
+    },
+
+    loadDeptData(companyId) {
+      if (!companyId) return Promise.resolve();
+
+      return getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+    },
+
+    // 构建部门树形结构
+    buildDeptTree(deptList) {
+      if (!deptList || deptList.length === 0) return [];
+
+      // 创建部门映射
+      const deptMap = {};
+      deptList.forEach(dept => {
+        // 如果后端返回的数据中已经有children字段且有值,则使用后端的children
+        // 否则初始化为空数组
+        deptMap[dept.deptId] = {
+          ...dept,
+          children: (dept.children && dept.children.length > 0) ? dept.children : []
+        };
+      });
+
+      // 构建树形结构
+      const tree = [];
+      deptList.forEach(dept => {
+        if (dept.parentId === 0 || dept.parentId === null) {
+          // 顶级部门
+          tree.push(deptMap[dept.deptId]);
+        } else {
+          // 子部门 - 只有在后端没有返回children的情况下才手动构建
+          if (deptMap[dept.parentId] && (!dept.children || dept.children.length === 0)) {
+            deptMap[dept.parentId].children.push(deptMap[dept.deptId]);
+          }
+        }
+      });
+
+      // 清理空的children数组,保持数据结构整洁
+      const cleanEmptyChildren = (nodes) => {
+        nodes.forEach(node => {
+          if (node.children && node.children.length === 0) {
+            delete node.children;
+          } else if (node.children && node.children.length > 0) {
+            cleanEmptyChildren(node.children);
+          }
+        });
+      };
+      cleanEmptyChildren(tree);
+
+      return tree;
+    },
+
+    // 添加时间格式化方法
+    formatDateTime(dateString) {
+      if (!dateString) return '';
+      // 移除时区信息并格式化日期时间
+      const date = new Date(dateString.replace(/\.\d{3}\+\d{4}$/, ''));
+      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}`;
+    },
+
+    formatDate(date) {
+      if (!date) return '';
+      const d = new Date(date);
+      const year = d.getFullYear();
+      const month = String(d.getMonth() + 1).padStart(2, '0');
+      const day = String(d.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    fetchStatisticsData() {
+      this.loading = true; // 开始请求时设置 loading 为 true
+      // 构造请求参数对象
+      const params = {
+        dimension: this.queryParams.dimension,
+        startTime: this.formatDate(this.dateRange[0]),
+        endTime: this.formatDate(this.dateRange[1])
+      };
+
+      // 根据新规则设置id参数
+      if (this.queryParams.dimension === 1 && this.queryParams.deptId) {
+        // 个人维度传递部门ID
+        params.id = this.queryParams.deptId;
+      } else if (this.queryParams.dimension === 3 && this.queryParams.companyId) {
+        // 部门维度传递公司ID
+        params.id = this.queryParams.companyId;
+      }
+      // 公司维度不传递ID参数
+
+      // 以POST方式发送请求体
+      return getStatisticsData(params).then(response => {
+        this.rawData = response.data || [];
+        this.tableData = [...this.rawData];
+        this.currentPage = 1;
+      }).catch(error => {
+        console.error('统计数据加载失败:', error);
+        this.$message.error('统计数据加载失败');
+      }).finally(() => {
+        this.loading = false; // 请求完成后设置 loading 为 false
+      });
+    },
+
+    handleCompanyChange(companyId) {
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+
+      if (!companyId) return;
+
+      // 使用 getSearchDeptInfo 获取部门下拉数据
+      getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        // 仅在个人维度下默认选择第一个部门
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+      // 移除.then()后面的自动查询调用
+    },
+    handleDeptChange(selectedDeptId) {
+      // 更新选中的部门ID
+      this.queryParams.deptId = selectedDeptId;
+    },
+
+    handleQuery() {
+      // 记录当前查询的维度
+      this.lastQueryDimension = this.queryParams.dimension;
+      // 触发统计数据请求
+      this.fetchStatisticsData();
+    },
+
+    handleDimensionChange() {
+      // 重置后续选择项
+      this.queryParams.companyId = null;
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+    },
+
+    resetQuery() {
+      this.queryParams = {
+        dimension: 2, // 重置为公司维度
+        companyId: null,
+        deptId: null,
+        userId: null
+      };
+      this.selectedDeptIds = []; // 重置部门选择
+      // 重置时间为当天
+      const today = new Date();
+      this.dateRange = [today, today];
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+      this.tableData = [...this.rawData];
+      this.currentPage = 1;
+      // 重置后立即发送请求
+      this.lastQueryDimension = 2; // 同时更新lastQueryDimension
+      this.fetchStatisticsData();
+    },
+
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+    },
+
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+
+    // 计算完播率:完课数 / 发送数
+    calculateCompleteRate(row) {
+      const sendCount = row.sendCount || 0;
+      const completeNum = row.completeNum || 0;
+
+      if (sendCount === 0) {
+        return '0%';
+      }
+
+      const rate = (completeNum / sendCount * 100).toFixed(2);
+      return rate + '%';
+    },
+
+    // 计算答题率:答题数 / 完课数
+    calculateAnswerRate(row) {
+      const completeNum = row.completeNum || 0;
+      const answerNum = row.answerNum || 0;
+
+      if (completeNum === 0) {
+        return '0%';
+      }
+
+      const rate = (answerNum / completeNum * 100).toFixed(2);
+      return rate + '%';
+    },
+    // 添加导出方法
+    handleExport() {
+      if (this.tableData.length === 0) {
+        this.$message.warning('没有可导出的数据');
+        return;
+      }
+
+      this.exportLoading = true;
+
+      try {
+        // 创建 CSV 内容
+        const headers = [
+          '统计时间', '公司名称', '部门名称', '人员名称',
+          '发课数', '完课数', '完播率', '答题数', '答题率',
+          '红包领取数', '红包金额(元)'
+        ];
+
+        const csvContent = [
+          headers.join(','),
+          ...this.tableData.map(row => [
+            this.formatDateTime(row.statisticsTime),
+            row.companyName || '',
+            row.deptName || '',
+            row.companyUserName || '',
+            row.sendCount || 0,
+            row.completeNum || 0,
+            this.calculateCompleteRate(row),
+            row.answerNum || 0,
+            this.calculateAnswerRate(row),
+            row.redPacketNum || 0,
+            row.redPacketAmount || 0
+          ].map(field => `"${field}"`).join(','))
+        ].join('\n');
+
+        // 创建下载链接
+        const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
+        const link = document.createElement('a');
+        const url = URL.createObjectURL(blob);
+
+        link.setAttribute('href', url);
+
+        // link.setAttribute('download', `综合统计看板_${new Date().toISOString().slice(0, 10)}.csv`);
+        // 修改为:
+        const now = new Date();
+        const timestamp = now.getFullYear() +
+          String(now.getMonth() + 1).padStart(2, '0') +
+          String(now.getDate()).padStart(2, '0') +
+          String(now.getHours()).padStart(2, '0') +
+          String(now.getMinutes()).padStart(2, '0') +
+          String(now.getSeconds()).padStart(2, '0');
+        link.setAttribute('download', `综合统计看板_${timestamp}.csv`);
+        link.style.visibility = 'hidden';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+
+        this.$message.success('导出成功');
+      } catch (error) {
+        console.error('导出失败:', error);
+        this.$message.error('导出失败');
+      } finally {
+        this.exportLoading = false;
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.app-container {
+  border: 1px solid #e6e6e6;
+  padding: 12px;
+  background-color: #f5f7fa;
+}
+
+.app-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
+
+.title {
+  text-align: center;
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20px;
+}
+
+.search-form {
+  margin-bottom: 0;
+}
+
+.search-form .el-form-item {
+  margin-bottom: 18px;
+  width: 100%;
+}
+
+.search-form .el-row {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}
+
+.table-section {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-section .el-table {
+  width: 100% !important;
+}
+
+@media (min-width: 1200px) {
+  .table-section {
+    padding: 20px 50px;
+  }
+}
+</style>

+ 1 - 1
src/views/live/live/index.vue

@@ -298,7 +298,7 @@
         </el-form-item>
         <el-form-item label="直播类型" prop="liveType">
           <el-radio-group v-model="form.liveType" :disabled="isViewOnly">
-            <el-radio :label="1">直播</el-radio>
+<!--            <el-radio :label="1">直播</el-radio>-->
             <el-radio :label="2">录播</el-radio>
           </el-radio-group>
         </el-form-item>

+ 12 - 1
src/views/live/liveConfig/task.vue

@@ -746,7 +746,7 @@ export default {
           this.form.content = content.couponId;
         }else if(this.form.taskType == 6){
           this.form.goodsId = content.goodsId;
-          this.form.goodsStatus = content.goodsStatus;
+          this.form.goodsStatus = content.status;
         }
         this.open = true;
         this.title = "修改直播间自动化任务配置";
@@ -759,6 +759,17 @@ export default {
         this.msgError("请选择直播间");
         return;
       }
+      if(this.form.taskType == 6){
+        if(this.form.goodsId == null) {
+          this.msgError("请选择商品");
+          return;
+        }
+        this.form.content = JSON.stringify({
+          goodsId: this.form.goodsId,
+          status: this.form.goodsStatus
+        })
+
+      }
       this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.form.id != null) {

+ 424 - 0
src/views/live/liveOrder/index-old.vue

@@ -0,0 +1,424 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="公司名" prop="companyId">
+        <el-select filterable v-model="queryParams.companyId" placeholder="请选择公司名" @change="companyChange" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item>
+        <treeselect style="width: 220px" :clearable="false" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择归属部门" />
+      </el-form-item>
+
+      <el-form-item label="商品名称" prop="productName">
+        <el-input
+          v-model="queryParams.productName"
+          placeholder="请输入商品名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="商品规格" prop="productSpec">
+        <el-input
+          v-model="queryParams.productSpec"
+          placeholder="请输入商品规格"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="商品数量" prop="totalNum">
+        <el-input
+          v-model="queryParams.totalNum"
+          placeholder="请输入商品数量"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="销售价格" prop="price">
+        <el-input
+          v-model="queryParams.price"
+          placeholder="请输入销售价格"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="收货地址" prop="userAddress">
+        <el-input
+          v-model="queryParams.userAddress"
+          placeholder="请输入收货地址"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="商品ID" prop="productId">
+        <el-input
+          v-model="queryParams.productId"
+          placeholder="请输入商品ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="成本价格" prop="cost">
+        <el-input
+          v-model="queryParams.cost"
+          placeholder="请输入成本价格"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="供应商" prop="supplierName">
+        <el-input
+          v-model="queryParams.supplierName"
+          placeholder="请输入供应商名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <!-- 下单时间范围选择 -->
+      <el-form-item label="下单时间" prop="orderTimeRange">
+        <el-date-picker
+          v-model="orderTimeRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd"
+          size="small"
+          @change="handleOrderTimeChange"
+        />
+      </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"
+          v-hasPermi="['live:liveOrder:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="actName" @tab-click="handleClickX">
+      <el-tab-pane label="全部订单" name="10"></el-tab-pane>
+      <el-tab-pane v-for="(item,index) in orderOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
+    </el-tabs>
+
+    <el-table border v-loading="loading" :data="liveOrderList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="销售ID" align="center" prop="companyUserId" >
+        <template slot-scope="scope">
+          <span v-if="scope.row.companyUserId > 0" >{{ scope.row.companyUserId }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="绑定销售昵称" align="center" prop="companyUserName" />
+      <el-table-column label="客户编码" align="center" prop="userId" />
+      <el-table-column label="会员等级" align="center" prop="userLevel">
+      </el-table-column>
+      <el-table-column label="销售绑定手机号" align="center" prop="companyUserPhone" />
+      <el-table-column label="销售创建时间" align="center" prop="companyUserCreateTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ scope.row.companyUserCreateTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="客户ID" align="center" prop="userId" />
+      <el-table-column label="客户昵称" align="center" prop="nickName" />
+      <el-table-column label="客户绑定手机号" align="center" prop="userBindPhone" />
+      <el-table-column label="收货手机号" align="center" prop="userPhone" />
+      <el-table-column label="累计成交笔数" align="center" prop="totalOrderCount" />
+      <el-table-column label="累计成交总额" align="center" prop="totalOrderAmount" />
+      <el-table-column label="最新绑定时间" align="center" prop="latestBindTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ scope.row.latestBindTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="客户状态" align="center" prop="customerStatus">
+        <template slot-scope="scope">
+          <dict-tag :options="customerStatusOptions" :value="scope.row.customerStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="所属店铺" align="center" prop="storeId" />
+      <el-table-column label="所属店铺名称" align="center" prop="storeName" />
+      <el-table-column label="商品ID" align="center" prop="productId" />
+      <el-table-column label="商品名称" align="center" prop="productName" width="150" />
+      <el-table-column label="商品规格" align="center" prop="productSpec" width="120" />
+      <el-table-column label="商品数量" align="center" prop="totalNum" />
+      <el-table-column label="销售价格" align="center" prop="totalPrice" />
+      <el-table-column label="成本价格" align="center" prop="costPrice" />
+      <el-table-column label="收货地址" align="center" prop="userAddress" width="200" />
+      <el-table-column label="对应供应商" align="center" prop="supplierName" width="120" />
+      <el-table-column label="下单时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="订单状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :options="orderStatusOptions" :value="scope.row.status"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handledetails(scope.row)"
+            v-hasPermi="['live:liveOrder:edit']"
+          >查看</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <el-drawer
+      :with-header="false"
+      size="75%"
+      :title="show.title" :visible.sync="show.open">
+      <liveOrderDetails ref="Details" />
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import { listLiveOrderZm, exportLiveOrderZm } from "@/api/live/liveOrder";
+import liveOrderDetails from './liveOrderDetails.vue';
+import {getCompanyList} from "@/api/company/company";
+import Treeselect from "@riophae/vue-treeselect";
+import {treeselect} from "@/api/company/companyDept";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {parseTime} from "../../../utils/common";
+
+export default {
+  name: "LiveOrder",
+  components: {Treeselect, liveOrderDetails },
+  data() {
+    return {
+      // 字典
+      orderStatusOptions: [],
+      memberLevelOptions: [],
+      customerStatusOptions: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 订单表格数据
+      liveOrderList: [],
+      // 公司列表
+      companys: [],
+      companyId: null,
+      // 部门树选项
+      deptOptions: [],
+
+      // 下单时间范围选择器绑定的值
+      orderTimeRange: [],
+
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        liveId: null,
+        pageSize: 10,
+        companyId: null,
+        deptId: null,
+        productName: null,
+        productSpec: null,
+        productNum: null,
+        salePrice: null,
+        userAddress: null,
+        productId: null,
+        costPrice: null,
+        supplierName: null,
+        orderStartTime: null,
+        orderEndTime: null,
+        status: null,
+      },
+      orderOptions: [],
+      actName: "10",
+      show: {
+        title: "订单详情",
+        open: false,
+      },
+    };
+  },
+  created() {
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      if(this.companys != null && this.companys.length > 0){
+        this.companyId = this.companys[0].companyId;
+        this.getTreeselect();
+      }
+    });
+    this.queryParams.liveId = this.$route.query.liveId
+    this.getList();
+    this.getDicts("sys_live_order_status").then(response => {
+      this.orderStatusOptions = response.data;
+    });
+    this.getDicts("sys_order_status").then(response => {
+      this.orderOptions = response.data;
+    });
+    this.getDicts("sys_user_level").then(response => {
+      this.memberLevelOptions = response.data;
+    });
+    this.getDicts("sys_customer_status").then(response => {
+      this.customerStatusOptions = response.data;
+    });
+  },
+  methods: {
+    parseTime,
+
+    // 下单时间范围选择变化处理
+    handleOrderTimeChange(value) {
+      if (value && value.length === 2) {
+        this.queryParams.createTimeStart = value[0] + ' 00:00:00';
+        this.queryParams.createTimeEnd = value[1] + ' 23:59:59';
+      } else {
+        this.queryParams.createTimeStart = null;
+        this.queryParams.createTimeEnd = null;
+      }
+    },
+
+    // 查看详情
+    handledetails(row){
+      this.show.open = true;
+      setTimeout(() => {
+        this.$refs.Details.getDetails(row.orderId, row.nickName, row.storeName);
+      }, 1);
+    },
+
+    // 订单tab切换
+    handleClickX(tab) {
+      this.orderStatus = tab.name;
+      if(tab.name == "10"){
+        this.queryParams.status = null;
+      } else {
+        this.queryParams.status = tab.name;
+      }
+      this.handleQuery();
+    },
+
+    /** 查询订单列表 */
+    getList() {
+      this.loading = true;
+      listLiveOrderZm(this.queryParams).then(response => {
+        this.liveOrderList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+
+    orderStatusFormatter(row, column) {
+      return this.selectDictLabel(this.orderStatusOptions, row.status);
+    },
+
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      // 同时清空范围选择器的值
+      this.orderTimeRange = [];
+      // 清空时间范围参数
+      this.queryParams.orderStartTime = null;
+      this.queryParams.orderEndTime = null;
+      this.handleQuery();
+    },
+
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.orderId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有订单数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportLiveOrderZm(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+
+    getTreeselect() {
+      var param = {companyId: this.companyId}
+      treeselect(param).then((response) => {
+        this.deptOptions = response.data;
+        if(response.data != null && response.data.length > 0){
+          // this.queryParams.deptId = response.data[0].id;
+        }
+      });
+    },
+
+    companyChange(val){
+      this.companyId = val;
+      this.getTreeselect();
+    },
+
+    currDeptChange(val){
+      this.queryParams.deptId = val;
+      this.getList();
+    },
+  }
+};
+</script>

+ 2 - 2
src/views/member/mylist.vue

@@ -326,7 +326,7 @@
 </template>
 
 <script>
-import { listUser, getUser, addUser, updateUser, delUser, exportUser, auditUser } from "@/api/user/fsUser";
+import { listUser, getUser, addUser, updateUser, delUser, exportUser, auditUser,myListUser } from "@/api/user/fsUser";
 import {transferUser} from "@/api/users/user";
 import {getUserList} from "@/api/company/companyUser";
 import userDetails from '@/views/store/components/userDetails.vue';
@@ -503,7 +503,7 @@ export default {
         this.queryParams.registerEndTime = null;
       }
 
-      listUser(this.queryParams).then(response => {
+      myListUser(this.queryParams).then(response => {
         this.userList = response.rows;
         this.total = response.total;
         this.loading = false;

+ 1 - 1
src/views/qw/externalContact/index.vue

@@ -210,7 +210,7 @@
         <el-date-picker
           v-model="createTime"
           size="small"
-          style="width: 340px"
+          style="width: 240px"
           value-format="yyyy-MM-dd HH:mm:ss"
           type="datetimerange"
           range-separator="-"

+ 1 - 1
src/views/qw/sop/ImageUpload.vue

@@ -152,7 +152,7 @@ export default {
         return false;
       }
       if (this.fileSize) {
-        const isLt = file.size / 1024 / 1024 < this.fileSize;
+        const isLt = file.size / 10 / 1024 / 1024 < this.fileSize;
         if (!isLt) {
           this.$message.error(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
           return false;