|
|
@@ -0,0 +1,399 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+
|
|
|
+ <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
|
|
|
+ <el-form-item label="统计维度" prop="dimension">
|
|
|
+ <el-radio-group v-model="queryParams.dimension" @change="handleDimensionChange">
|
|
|
+ <el-radio-button label="sales">销售维度</el-radio-button>
|
|
|
+ <el-radio-button label="dept">部门维度</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <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 label="下单时间" prop="orderTime">-->
|
|
|
+<!-- <el-date-picker v-model="orderTime" size="small" style="width: 220px" value-format="yyyy-MM-dd"-->
|
|
|
+<!-- type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"-->
|
|
|
+<!-- @change="ydChange"></el-date-picker>-->
|
|
|
+<!-- </el-form-item>-->
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="会员ID" prop="userId">
|
|
|
+ <el-input v-model="queryParams.userId" placeholder="请输入会员ID" clearable size="small" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="会员手机号" prop="userPhone">
|
|
|
+ <el-input v-model="queryParams.userPhone" placeholder="请输入会员手机号" clearable size="small" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="会员昵称" prop="nickName">
|
|
|
+ <el-input v-model="queryParams.nickName" placeholder="请输入会员昵称" clearable size="small" />
|
|
|
+ </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-if="queryParams.dimension === 'sales'" v-loading="loading" border :data="packageList">
|
|
|
+ <el-table-column label="销售" align="center" prop="salesName" />
|
|
|
+ <el-table-column label="APP会员数" align="center" prop="appUserCount" />
|
|
|
+ <el-table-column label="新注册APP会员数" align="center" prop="newAppUserCount" />
|
|
|
+ <el-table-column label="所属销售部门" align="center" prop="salesDept"/>
|
|
|
+ <el-table-column label="所属销售公司" align="center" prop="salesCompany"/>
|
|
|
+ <el-table-column label="训练营" align="center" prop="trainingCampName"/>
|
|
|
+ <el-table-column label="营期" align="center" prop="periodName"/>
|
|
|
+ <el-table-column label="课程小节" align="center" prop="videoTitle" />
|
|
|
+ <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="notAnsweredCount" />
|
|
|
+ <el-table-column label="红包金额" align="center" prop="redPacketAmount" />
|
|
|
+<!-- <el-table-column label="历史疗法订单数" align="center" prop="historyOrderCount" />-->
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 部门维度表格 -->
|
|
|
+ <el-table v-else 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="APP会员数" align="center" prop="appUserCount" />
|
|
|
+ <el-table-column label="新注册APP会员数" align="center" prop="newAppUserCount" />
|
|
|
+ <el-table-column label="所属销售公司" align="center" prop="salesCompany"/>
|
|
|
+ <el-table-column label="训练营" align="center" prop="trainingCampName"/>
|
|
|
+ <el-table-column label="营期" align="center" prop="periodName"/>
|
|
|
+ <el-table-column label="课程小节" align="center" prop="videoTitle" />
|
|
|
+ <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="notAnsweredCount" />
|
|
|
+ <el-table-column label="红包金额" align="center" prop="redPacketAmount" />
|
|
|
+<!-- <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.notAnsweredCount }}</span>
|
|
|
+ <span class="total-item">红包金额: {{ calculatedTotalData.redPacketAmount }}</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 Treeselect from "@riophae/vue-treeselect";
|
|
|
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
|
|
+import {getCampListLikeName} from "@/api/course/userCourseCamp";
|
|
|
+import {getPeriodListLikeName} from "@/api/course/userCoursePeriod";
|
|
|
+import { getCompanyList } from "@/api/company/company";
|
|
|
+import { selectDeptTree } from "@/api/company/companyDept";
|
|
|
+import { appSalesWatchLogReport, appSalesWatchLogReportExport } from "@/api/app/statistics/appStatistics";
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "AppWatchlogReportStatistics",
|
|
|
+ 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: [],
|
|
|
+ // 遮罩层
|
|
|
+ loading: true,
|
|
|
+ // 导出遮罩层
|
|
|
+ exportLoading: false,
|
|
|
+ // 显示搜索条件
|
|
|
+ showSearch: true,
|
|
|
+ // 总条数
|
|
|
+ total: 0,
|
|
|
+ createTime: [],
|
|
|
+ // orderTime: null,
|
|
|
+ // 表格数据
|
|
|
+ packageList: [],
|
|
|
+ // 总计数据
|
|
|
+ calculatedTotalData: {
|
|
|
+ finishedCount: 0,
|
|
|
+ unfinishedCount: 0,
|
|
|
+ completionRate: 0,
|
|
|
+ notWatchedCount: 0,
|
|
|
+ notAnsweredCount: 0,
|
|
|
+ redPacketAmount: 0,
|
|
|
+ historyOrderCount: 0
|
|
|
+ },
|
|
|
+ // 查询参数
|
|
|
+ queryParams: {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ companyId: null,
|
|
|
+ project: null,
|
|
|
+ trainingCampId: null,
|
|
|
+ periodId: null,
|
|
|
+ sTime: null,
|
|
|
+ eTime: null,
|
|
|
+ orderSTime: null,
|
|
|
+ orderETime: null,
|
|
|
+ deptId: null,
|
|
|
+ userId: null,
|
|
|
+ userPhone: null,
|
|
|
+ nickName: null,
|
|
|
+ dimension: 'sales'
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ // 设置默认看课时间为当天
|
|
|
+ const today = this.parseTime(new Date(), '{y}-{m}-{d}');
|
|
|
+ this.createTime = [today, today];
|
|
|
+ this.queryParams.sTime = today;
|
|
|
+ this.queryParams.eTime = today;
|
|
|
+
|
|
|
+ this.getTreeselect(this.$store.state.user.user.companyId);
|
|
|
+
|
|
|
+ getCampListLikeName({ "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;
|
|
|
+ }
|
|
|
+ this.camps.push({ companyId: "-1", companyName: "无" })
|
|
|
+ });
|
|
|
+
|
|
|
+ getCompanyList().then(response => {
|
|
|
+ this.companys = response.data;
|
|
|
+ if (this.companys != null && this.companys.length > 0) {
|
|
|
+ this.companyId = this.companys[0].companyId;
|
|
|
+ }
|
|
|
+ this.companys.push({ companyId: "-1", companyName: "无" })
|
|
|
+ });
|
|
|
+
|
|
|
+ this.getList();
|
|
|
+
|
|
|
+ this.getDicts("sys_course_project").then(e => {
|
|
|
+ this.projectList = e.data;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ /** 维度切换处理 */
|
|
|
+ handleDimensionChange(val) {
|
|
|
+ this.queryParams.dimension = val;
|
|
|
+ this.queryParams.pageNum = 1;
|
|
|
+ this.getList();
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 查询部门下拉树结构 */
|
|
|
+ getTreeselect(companyId) {
|
|
|
+ let queryParams = {};
|
|
|
+ if (companyId) {
|
|
|
+ queryParams.companyId = companyId;
|
|
|
+ }
|
|
|
+ selectDeptTree(queryParams).then(response => {
|
|
|
+ this.deptTreeOptions = response.data;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 训练营变更处理 */
|
|
|
+ handleCampChange(val) {
|
|
|
+ this.queryParams.trainingCampId = val;
|
|
|
+ this.queryParams.periodId = null; // 清空已选择的营期
|
|
|
+
|
|
|
+ if (val) {
|
|
|
+ // 获取对应的营期数据
|
|
|
+ this.getPeriodByCamp(val);
|
|
|
+ } else {
|
|
|
+ // 如果清空训练营,也清空营期选项
|
|
|
+ this.deptOptions = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 根据训练营获取营期数据 */
|
|
|
+ getPeriodByCamp(campId) {
|
|
|
+ const param = { campId: campId };
|
|
|
+ getPeriodListLikeName(param).then((response) => {
|
|
|
+ this.deptOptions = response.data.list || [];
|
|
|
+ }).catch(error => {
|
|
|
+ console.error('获取营期数据失败:', error);
|
|
|
+ this.deptOptions = [];
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ xdChange(val) {
|
|
|
+ if (val) {
|
|
|
+ this.queryParams.sTime = val[0];
|
|
|
+ this.queryParams.eTime = val[1];
|
|
|
+ } else {
|
|
|
+ this.queryParams.sTime = null;
|
|
|
+ this.queryParams.eTime = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ ydChange(val) {
|
|
|
+ if (val) {
|
|
|
+ this.queryParams.orderSTime = val[0];
|
|
|
+ this.queryParams.orderETime = val[1];
|
|
|
+ } else {
|
|
|
+ this.queryParams.orderSTime = null;
|
|
|
+ this.queryParams.orderETime = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 搜索按钮操作 */
|
|
|
+ handleQuery() {
|
|
|
+ this.queryParams.pageNum = 1;
|
|
|
+ this.getList();
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 重置按钮操作 */
|
|
|
+ resetQuery() {
|
|
|
+ this.resetForm("queryForm");
|
|
|
+ this.createTime = null;
|
|
|
+ // this.orderTime = null;
|
|
|
+ this.queryParams.sTime = null;
|
|
|
+ this.queryParams.eTime = null;
|
|
|
+ this.queryParams.orderSTime = null;
|
|
|
+ this.queryParams.orderETime = null;
|
|
|
+ this.queryParams.trainingCampId = null;
|
|
|
+ this.queryParams.periodId = null;
|
|
|
+ this.handleQuery();
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 计算总计 */
|
|
|
+ calculateTotals() {
|
|
|
+ // 重置总计数据
|
|
|
+ this.calculatedTotalData = {
|
|
|
+ finishedCount: 0,
|
|
|
+ unfinishedCount: 0,
|
|
|
+ completionRate: 0,
|
|
|
+ notWatchedCount: 0,
|
|
|
+ notAnsweredCount: 0,
|
|
|
+ redPacketAmount: 0,
|
|
|
+ historyOrderCount: 0
|
|
|
+ };
|
|
|
+ // 遍历当前页数据计算总和
|
|
|
+ this.packageList.forEach(item => {
|
|
|
+ this.calculatedTotalData.finishedCount += item.finishedCount || 0;
|
|
|
+ this.calculatedTotalData.unfinishedCount += item.unfinishedCount || 0;
|
|
|
+ this.calculatedTotalData.notWatchedCount += item.notWatchedCount || 0;
|
|
|
+ this.calculatedTotalData.notAnsweredCount += item.notAnsweredCount || 0;
|
|
|
+ this.calculatedTotalData.redPacketAmount += item.redPacketAmount || 0;
|
|
|
+ this.calculatedTotalData.historyOrderCount += item.historyOrderCount || 0;
|
|
|
+ });
|
|
|
+ // 计算完课率
|
|
|
+ const total = this.calculatedTotalData.finishedCount + this.calculatedTotalData.unfinishedCount;
|
|
|
+ this.calculatedTotalData.completionRate = total > 0
|
|
|
+ ? ((this.calculatedTotalData.finishedCount / total) * 100).toFixed(2) + '%'
|
|
|
+ : '0%';
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 查询列表 */
|
|
|
+ getList() {
|
|
|
+ this.loading = true;
|
|
|
+ // this.queryParams={
|
|
|
+ // pageNum: 1,
|
|
|
+ // pageSize: 10,
|
|
|
+ // }
|
|
|
+ appSalesWatchLogReport(this.queryParams).then(response => {
|
|
|
+ this.packageList = response.rows;
|
|
|
+ this.total = response.total;
|
|
|
+ this.calculateTotals();
|
|
|
+ this.loading = false;
|
|
|
+ }).catch(() => {
|
|
|
+ this.loading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 导出按钮操作 */
|
|
|
+ handleExport() {
|
|
|
+ const queryParams = this.queryParams;
|
|
|
+ const dimensionText = queryParams.dimension === 'sales' ? '销售维度' : '部门维度';
|
|
|
+ this.$confirm('是否确认导出' + dimensionText + 'APP看课统计数据项?', "警告", {
|
|
|
+ confirmButtonText: "确定",
|
|
|
+ cancelButtonText: "取消",
|
|
|
+ type: "warning"
|
|
|
+ }).then(() => {
|
|
|
+ this.exportLoading = true;
|
|
|
+ return appSalesWatchLogReportExport(queryParams);
|
|
|
+ }).then(response => {
|
|
|
+ this.download(response.msg);
|
|
|
+ this.exportLoading = false;
|
|
|
+ }).catch(() => {});
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.total-summary {
|
|
|
+ margin-top: 15px;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+.total-title {
|
|
|
+ font-weight: bold;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+.total-item {
|
|
|
+ margin-right: 20px;
|
|
|
+}
|
|
|
+</style>
|