wangxy 22 часов назад
Родитель
Сommit
88eb1abf73

+ 18 - 0
src/api/store/package.js

@@ -102,6 +102,24 @@ export function exportWatchLogReport(query) {
   })
 }
 
+// 飞书看课统计报表(部门/销售维度)
+export function feiShuWatchLogReport(query) {
+  return request({
+    url: '/store/store/statistics/feiShuWatchLogReport',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出飞书看课统计报表
+export function exportFeiShuWatchLogReport(query) {
+  return request({
+    url: '/store/store/statistics/feiShuWatchLogReportExport',
+    method: 'get',
+    params: query
+  })
+}
+
 // App会员看课统计
 export function appWatchLogReportSales(query) {
   return request({

+ 322 - 0
src/views/statistics/feishuWatchlogReportCompany.vue

@@ -0,0 +1,322 @@
+<template>
+  <div class="app-container">
+
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="所属部门" prop="deptId">
+        <treeselect style="width:205.4px" v-model="queryParams.deptId" :options="deptTreeOptions" :show-count="true" placeholder="请选择所属部门" />
+      </el-form-item>
+      <el-form-item label="项目" prop="project">
+        <el-select filterable v-model="queryParams.project" placeholder="请选择项目"
+                   clearable size="small">
+          <el-option
+            v-for="item in projectList"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="训练营" prop="trainingCampId">
+        <el-select filterable v-model="queryParams.trainingCampId" placeholder="请选择训练营"
+                   clearable size="small"  @change="handleCampChange">
+          <el-option
+            v-for="item in camps"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <treeselect style="width: 220px" v-model="queryParams.periodId" :options="deptOptions"
+                    clearable :show-count="true" placeholder="请选择归属营期" value-consists-of="LEAF_PRIORITY"
+                    :normalizer="normalizer" />
+      </el-form-item>
+      <el-form-item>
+        <el-form-item label="看课时间" prop="createTime">
+          <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd"
+                          type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                          @change="xdChange"></el-date-picker>
+        </el-form-item>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="packageList">
+      <el-table-column label="销售" align="center" prop="salesName" />
+      <el-table-column label="所属销售部门" align="center" prop="salesDept"/>
+      <el-table-column label="所属销售公司" align="center" prop="salesCompany"/>
+      <el-table-column label="完课数" align="center" prop="finishedCount" />
+      <el-table-column label="未完课" align="center" prop="unfinishedCount" />
+      <el-table-column label="完课率" align="center" prop="completionRate" />
+      <el-table-column label="未看数" align="center" prop="notWatchedCount" />
+      <el-table-column label="历史疗法订单数" align="center" prop="historyOrderCount" />
+    </el-table>
+    <div class="total-summary">
+      <span class="total-title">总计:</span>
+      <span class="total-item">完课数: {{ calculatedTotalData.finishedCount }}</span>
+      <span class="total-item">未完课: {{ calculatedTotalData.unfinishedCount }}</span>
+      <span class="total-item">完课率: {{ calculatedTotalData.completionRate }}</span>
+      <span class="total-item">未看数: {{ calculatedTotalData.notWatchedCount }}</span>
+      <span class="total-item">历史疗法订单数: {{ calculatedTotalData.historyOrderCount }}</span>
+    </div>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+                @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import {
+  feiShuWatchLogReport, exportFeiShuWatchLogReport
+} from "@/api/store/package";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { getCampList, getPeriodList } from "@/api/store/storeProduct";
+import { getCompanyList } from "@/api/company/company";
+import { selectDeptTree } from "@/api/company/companyDept";
+
+export default {
+  name: "FeiShuWatchlogReportCompany",
+  components: { Treeselect },
+  data() {
+    return {
+      normalizer: function(node) {
+        return {
+          id: node.id || node.dictValue,
+          label: node.label || node.dictLabel,
+          children: node.children
+        }
+      },
+      projectList: [],
+      companys: [],
+      camps: [],
+      deptOptions: [],
+      deptTreeOptions: [],
+      calculatedTotalData: {
+        finishedCount: 0,
+        unfinishedCount: 0,
+        completionRate: 0,
+        notWatchedCount: 0,
+        historyOrderCount: 0
+      },
+      loading: true,
+      exportLoading: false,
+      showSearch: true,
+      total: 0,
+      createTime: null,
+      packageList: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        project: null,
+        trainingCampId: null,
+        periodId: null,
+        sTime: null,
+        eTime: null,
+        deptId: null,
+        dimension: 'sales'
+      }
+    };
+  },
+  created() {
+    this.getTreeselect(this.$store.state.user.user.companyId);
+    if (!this.queryParams.dimension) {
+      this.queryParams.dimension = 'sales';
+    }
+    getCampList({ "pageNum": 1, "pageSize": 100 }).then(response => {
+      this.camps = response.data.list;
+      if (this.camps != null && this.camps.length > 0) {
+        this.companyId = this.camps[0].dictValue;
+      }
+    });
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      this.companys.push({ companyId: "-1", companyName: "无" });
+    });
+    this.getList();
+    this.getDicts("sys_course_project").then(e => {
+      this.projectList = e.data;
+    });
+  },
+  methods: {
+    getList() {
+      this.loading = true;
+      feiShuWatchLogReport(this.queryParams).then(response => {
+        this.packageList = response.rows;
+        this.total = response.total;
+        this.calculateTotals();
+        this.loading = false;
+      });
+    },
+    calculateTotals() {
+      this.calculatedTotalData = {
+        finishedCount: 0,
+        unfinishedCount: 0,
+        completionRate: 0,
+        notWatchedCount: 0,
+        historyOrderCount: 0
+      };
+      this.packageList.forEach(item => {
+        this.calculatedTotalData.finishedCount += Number(item.finishedCount) || 0;
+        this.calculatedTotalData.unfinishedCount += Number(item.unfinishedCount) || 0;
+        this.calculatedTotalData.notWatchedCount += Number(item.notWatchedCount) || 0;
+        this.calculatedTotalData.historyOrderCount += Number(item.historyOrderCount) || 0;
+      });
+      const totalCount = this.calculatedTotalData.finishedCount + this.calculatedTotalData.unfinishedCount;
+      if (totalCount > 0) {
+        this.calculatedTotalData.completionRate = (this.calculatedTotalData.finishedCount / totalCount * 100).toFixed(2) + '%';
+      } else {
+        this.calculatedTotalData.completionRate = '0%';
+      }
+    },
+    getTreeselect(companyId) {
+      selectDeptTree({ companyId: companyId }).then(response => {
+        this.deptTreeOptions = response.data;
+      });
+    },
+    handleCampChange(val) {
+      this.queryParams.trainingCampId = val;
+      this.queryParams.periodId = null;
+      if (val) {
+        this.getPeriodByCamp(val);
+      } else {
+        this.deptOptions = [];
+      }
+      this.handleQuery();
+    },
+    getPeriodByCamp(campId) {
+      getPeriodList({ campId: campId }).then(response => {
+        this.deptOptions = response.data.list || [];
+      }).catch(() => {
+        this.deptOptions = [];
+      });
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.handleQuery();
+    },
+    handleExport() {
+      this.$confirm('是否确认导出飞书看课统计(公司)数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportFeiShuWatchLogReport(this.queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+    xdChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    }
+  }
+};
+</script>
+
+<style>
+.icon-button {
+  border-radius: 0;
+}
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409EFF;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 150px;
+  height: 150px;
+  line-height: 150px !important;
+  text-align: center;
+}
+.total-summary {
+  margin-top: 15px;
+  padding: 15px 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f4 100%);
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.total-title {
+  font-weight: bold;
+  font-size: 16px;
+  color: #303133;
+  margin-right: 20px;
+  flex-shrink: 0;
+}
+.total-item {
+  margin-right: 25px;
+  padding: 5px 10px;
+  background: white;
+  border-radius: 3px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+  display: inline-block;
+  margin-bottom: 5px;
+  font-size: 13px;
+  color: #606266;
+}
+.total-item::before {
+  content: "";
+  display: inline-block;
+  width: 3px;
+  height: 3px;
+  background: #409eff;
+  border-radius: 50%;
+  margin-right: 5px;
+  vertical-align: middle;
+}
+@media (max-width: 768px) {
+  .total-summary {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  .total-title {
+    margin-bottom: 10px;
+  }
+  .total-item {
+    margin-right: 10px;
+    margin-bottom: 8px;
+  }
+}
+</style>

+ 322 - 0
src/views/statistics/feishuWatchlogReportdept.vue

@@ -0,0 +1,322 @@
+<template>
+  <div class="app-container">
+
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="所属部门" prop="deptId">
+        <treeselect style="width:205.4px" v-model="queryParams.deptId" :options="deptTreeOptions" :show-count="true" placeholder="请选择所属部门" />
+      </el-form-item>
+      <el-form-item label="项目" prop="project">
+        <el-select filterable v-model="queryParams.project" placeholder="请选择项目"
+                   clearable size="small">
+          <el-option
+            v-for="item in projectList"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="训练营" prop="trainingCampId">
+        <el-select filterable v-model="queryParams.trainingCampId" placeholder="请选择训练营"
+                   clearable size="small"  @change="handleCampChange">
+          <el-option
+            v-for="item in camps"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <treeselect style="width: 220px" v-model="queryParams.periodId" :options="deptOptions"
+                    clearable :show-count="true" placeholder="请选择归属营期" value-consists-of="LEAF_PRIORITY"
+                    :normalizer="normalizer" />
+      </el-form-item>
+      <el-form-item>
+        <el-form-item label="看课时间" prop="createTime">
+          <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd"
+                          type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                          @change="xdChange"></el-date-picker>
+        </el-form-item>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="packageList">
+      <el-table-column label="所属销售部门" align="center" prop="salesDept"/>
+      <el-table-column label="销售数" align="center" prop="salesCount" />
+      <el-table-column label="所属销售公司" align="center" prop="salesCompany"/>
+      <el-table-column label="完课数" align="center" prop="finishedCount" />
+      <el-table-column label="未完课" align="center" prop="unfinishedCount" />
+      <el-table-column label="完课率" align="center" prop="completionRate" />
+      <el-table-column label="未看数" align="center" prop="notWatchedCount" />
+      <el-table-column label="历史疗法订单数" align="center" prop="historyOrderCount" />
+    </el-table>
+    <div class="total-summary">
+      <span class="total-title">总计:</span>
+      <span class="total-item">完课数: {{ calculatedTotalData.finishedCount }}</span>
+      <span class="total-item">未完课: {{ calculatedTotalData.unfinishedCount }}</span>
+      <span class="total-item">完课率: {{ calculatedTotalData.completionRate }}</span>
+      <span class="total-item">未看数: {{ calculatedTotalData.notWatchedCount }}</span>
+      <span class="total-item">历史疗法订单数: {{ calculatedTotalData.historyOrderCount }}</span>
+    </div>
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
+                @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import {
+  feiShuWatchLogReport, exportFeiShuWatchLogReport
+} from "@/api/store/package";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { getCampList, getPeriodList } from "@/api/store/storeProduct";
+import { getCompanyList } from "@/api/company/company";
+import { selectDeptTree } from "@/api/company/companyDept";
+
+export default {
+  name: "FeiShuWatchlogReportDept",
+  components: { Treeselect },
+  data() {
+    return {
+      normalizer: function(node) {
+        return {
+          id: node.id || node.dictValue,
+          label: node.label || node.dictLabel,
+          children: node.children
+        }
+      },
+      projectList: [],
+      companys: [],
+      camps: [],
+      deptOptions: [],
+      deptTreeOptions: [],
+      calculatedTotalData: {
+        finishedCount: 0,
+        unfinishedCount: 0,
+        completionRate: 0,
+        notWatchedCount: 0,
+        historyOrderCount: 0
+      },
+      loading: true,
+      exportLoading: false,
+      showSearch: true,
+      total: 0,
+      createTime: null,
+      packageList: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        project: null,
+        trainingCampId: null,
+        periodId: null,
+        sTime: null,
+        eTime: null,
+        deptId: null,
+        dimension: 'company'
+      }
+    };
+  },
+  created() {
+    this.getTreeselect(this.$store.state.user.user.companyId);
+    if (!this.queryParams.dimension) {
+      this.queryParams.dimension = 'company';
+    }
+    getCampList({ "pageNum": 1, "pageSize": 100 }).then(response => {
+      this.camps = response.data.list;
+      if (this.camps != null && this.camps.length > 0) {
+        this.companyId = this.camps[0].dictValue;
+      }
+    });
+    getCompanyList().then(response => {
+      this.companys = response.data;
+      this.companys.push({ companyId: "-1", companyName: "无" });
+    });
+    this.getList();
+    this.getDicts("sys_course_project").then(e => {
+      this.projectList = e.data;
+    });
+  },
+  methods: {
+    getList() {
+      this.loading = true;
+      feiShuWatchLogReport(this.queryParams).then(response => {
+        this.packageList = response.rows;
+        this.total = response.total;
+        this.calculateTotals();
+        this.loading = false;
+      });
+    },
+    calculateTotals() {
+      this.calculatedTotalData = {
+        finishedCount: 0,
+        unfinishedCount: 0,
+        completionRate: 0,
+        notWatchedCount: 0,
+        historyOrderCount: 0
+      };
+      this.packageList.forEach(item => {
+        this.calculatedTotalData.finishedCount += Number(item.finishedCount) || 0;
+        this.calculatedTotalData.unfinishedCount += Number(item.unfinishedCount) || 0;
+        this.calculatedTotalData.notWatchedCount += Number(item.notWatchedCount) || 0;
+        this.calculatedTotalData.historyOrderCount += Number(item.historyOrderCount) || 0;
+      });
+      const totalCount = this.calculatedTotalData.finishedCount + this.calculatedTotalData.unfinishedCount;
+      if (totalCount > 0) {
+        this.calculatedTotalData.completionRate = (this.calculatedTotalData.finishedCount / totalCount * 100).toFixed(2) + '%';
+      } else {
+        this.calculatedTotalData.completionRate = '0%';
+      }
+    },
+    getTreeselect(companyId) {
+      selectDeptTree({ companyId: companyId }).then(response => {
+        this.deptTreeOptions = response.data;
+      });
+    },
+    handleCampChange(val) {
+      this.queryParams.trainingCampId = val;
+      this.queryParams.periodId = null;
+      if (val) {
+        this.getPeriodByCamp(val);
+      } else {
+        this.deptOptions = [];
+      }
+      this.handleQuery();
+    },
+    getPeriodByCamp(campId) {
+      getPeriodList({ campId: campId }).then(response => {
+        this.deptOptions = response.data.list || [];
+      }).catch(() => {
+        this.deptOptions = [];
+      });
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.handleQuery();
+    },
+    handleExport() {
+      this.$confirm('是否确认导出飞书看课统计(部门)数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportFeiShuWatchLogReport(this.queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    },
+    xdChange() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    }
+  }
+};
+</script>
+
+<style>
+.icon-button {
+  border-radius: 0;
+}
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409EFF;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 150px;
+  height: 150px;
+  line-height: 150px !important;
+  text-align: center;
+}
+.total-summary {
+  margin-top: 15px;
+  padding: 15px 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f4 100%);
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.total-title {
+  font-weight: bold;
+  font-size: 16px;
+  color: #303133;
+  margin-right: 20px;
+  flex-shrink: 0;
+}
+.total-item {
+  margin-right: 25px;
+  padding: 5px 10px;
+  background: white;
+  border-radius: 3px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+  display: inline-block;
+  margin-bottom: 5px;
+  font-size: 13px;
+  color: #606266;
+}
+.total-item::before {
+  content: "";
+  display: inline-block;
+  width: 3px;
+  height: 3px;
+  background: #409eff;
+  border-radius: 50%;
+  margin-right: 5px;
+  vertical-align: middle;
+}
+@media (max-width: 768px) {
+  .total-summary {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  .total-title {
+    margin-bottom: 10px;
+  }
+  .total-item {
+    margin-right: 10px;
+    margin-bottom: 8px;
+  }
+}
+</style>