|
|
@@ -0,0 +1,443 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+ <el-card class="search-card" shadow="never">
|
|
|
+ <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
|
|
|
+ <el-form-item label="课程" prop="courseId">
|
|
|
+ <el-select
|
|
|
+ filterable
|
|
|
+ v-model="queryParams.courseId"
|
|
|
+ placeholder="请选择课程"
|
|
|
+ clearable
|
|
|
+ size="small"
|
|
|
+ style="width: 200px"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="dict in campData"
|
|
|
+ :key="dict.trainingCampId"
|
|
|
+ :label="dict.trainingCampName"
|
|
|
+ :value="dict.trainingCampId"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="search-buttons">
|
|
|
+ <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">查询</el-button>
|
|
|
+ <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
|
|
|
+ <el-button v-show="false"
|
|
|
+ type="warning"
|
|
|
+ icon="el-icon-download"
|
|
|
+ size="small"
|
|
|
+ :loading="exportLoading"
|
|
|
+ @click="handleExport"
|
|
|
+ v-hasPermi="['qw:user:export']"
|
|
|
+ >导出数据</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 统计结果展示区域 -->
|
|
|
+ <el-row :gutter="20" class="statistics-row">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-card class="statistics-card" shadow="hover">
|
|
|
+ <div slot="header" class="clearfix">
|
|
|
+ <span class="card-title">
|
|
|
+ <i class="el-icon-data-analysis"></i>
|
|
|
+ 详情
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <div v-if="loading" class="loading-content">
|
|
|
+ <el-skeleton :rows="5" animated />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据展示 -->
|
|
|
+ <div v-else-if="statisticsResult" class="data-content">
|
|
|
+ <div class="data-display">
|
|
|
+ <div class="data-value">
|
|
|
+ {{ formatValue(statisticsResult.lostData) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 无数据状态 -->
|
|
|
+ <div v-else class="empty-content">
|
|
|
+ <el-empty description="暂无统计数据">
|
|
|
+ <el-button type="primary" size="small" @click="resetQuery">重新查询</el-button>
|
|
|
+ </el-empty>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { statisticsList, statisticsExport } from "@/api/course/qw/courseWatchLog";
|
|
|
+import { shortListCamp } from '@/api/course/userCourseCamp'
|
|
|
+import { courseLost, videoList } from '@/api/course/courseRedPacketLog'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "CourseLost",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 查询相关
|
|
|
+ createTime: null,
|
|
|
+ courseLists: [],
|
|
|
+ videoList: [],
|
|
|
+
|
|
|
+ // 状态相关
|
|
|
+ loading: true,
|
|
|
+ exportLoading: false,
|
|
|
+ infoDialogVisible: false,
|
|
|
+
|
|
|
+ // 数据相关
|
|
|
+ statisticsResult: null,
|
|
|
+ //课程数据
|
|
|
+ campData: null,
|
|
|
+ showSearch: true,
|
|
|
+ // 查询参数
|
|
|
+ queryParams: {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ courseId: null,
|
|
|
+ sTime: null,
|
|
|
+ eTime: null,
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ shortListCamp().then(response => {
|
|
|
+ this.campData = response.data;
|
|
|
+ })
|
|
|
+ this.getStatisticsData();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 格式化显示值
|
|
|
+ formatValue(value) {
|
|
|
+ if (value === null || value === undefined || value === '') {
|
|
|
+ return '暂无数据';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是数字,添加千位分隔符
|
|
|
+ if (typeof value === 'number' || !isNaN(value)) {
|
|
|
+ const num = Number(value);
|
|
|
+ return num.toLocaleString('zh-CN');
|
|
|
+ }
|
|
|
+
|
|
|
+ return value;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取当前时间
|
|
|
+ getCurrentTime() {
|
|
|
+ const now = new Date();
|
|
|
+ const year = now.getFullYear();
|
|
|
+ const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(now.getDate()).padStart(2, '0');
|
|
|
+ const hours = String(now.getHours()).padStart(2, '0');
|
|
|
+ const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
|
+ const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取统计数据
|
|
|
+ getStatisticsData() {
|
|
|
+ this.loading = true;
|
|
|
+ courseLost(this.queryParams)
|
|
|
+ .then(response => {
|
|
|
+ this.handleResponse(response);
|
|
|
+ this.updateTime = this.getCurrentTime();
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('获取统计数据失败:', error);
|
|
|
+ this.$message.error('获取统计数据失败');
|
|
|
+ this.statisticsResult = null;
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.loading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理接口响应
|
|
|
+ handleResponse(response) {
|
|
|
+ // 根据实际接口返回结构调整
|
|
|
+ if (response.data && response.data.lostData !== undefined) {
|
|
|
+ this.statisticsResult = response.data;
|
|
|
+ } else if (response.rows && response.rows.length > 0) {
|
|
|
+ this.statisticsResult = { lostData: response.rows[0].lostData };
|
|
|
+ } else if (response.total !== undefined) {
|
|
|
+ this.statisticsResult = { lostData: response.total };
|
|
|
+ } else if (typeof response === 'object') {
|
|
|
+ this.statisticsResult = { lostData: response.data };;
|
|
|
+ } else {
|
|
|
+ this.statisticsResult = { lostData: response.data };
|
|
|
+ }
|
|
|
+ // 如果没有mockData字段,创建默认结构
|
|
|
+ if (!this.statisticsResult || this.statisticsResult.lostData === undefined) {
|
|
|
+ this.statisticsResult = { lostData: '暂无数据' };
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 刷新数据
|
|
|
+ refreshData() {
|
|
|
+ this.getStatisticsData();
|
|
|
+ this.$message.success('数据已刷新');
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示字段信息
|
|
|
+ showFieldInfo() {
|
|
|
+ this.infoDialogVisible = true;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 查询
|
|
|
+ handleQuery() {
|
|
|
+/* if (!this.queryParams.sTime || !this.queryParams.eTime) {
|
|
|
+ this.$message.warning("请选择时间范围");
|
|
|
+ return;
|
|
|
+ } */
|
|
|
+ this.getStatisticsData();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重置查询
|
|
|
+ resetQuery() {
|
|
|
+ this.$refs.queryForm.resetFields();
|
|
|
+ this.createTime = null;
|
|
|
+ this.queryParams.sTime = null;
|
|
|
+ this.queryParams.eTime = null;
|
|
|
+ this.queryParams.courseId = null;
|
|
|
+ this.videoList = [];
|
|
|
+ this.getStatisticsData();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 导出数据
|
|
|
+ handleExport() {
|
|
|
+ if (!this.queryParams.sTime || !this.queryParams.eTime) {
|
|
|
+ this.$message.warning("请选择时间范围");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$confirm('确定要导出当前统计数据吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ this.exportLoading = true;
|
|
|
+ return statisticsExport(this.queryParams);
|
|
|
+ })
|
|
|
+ .then(response => {
|
|
|
+ this.download(response.msg);
|
|
|
+ this.$message.success('导出成功');
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 用户取消导出
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.exportLoading = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.app-container {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索卡片样式
|
|
|
+.search-card {
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ ::v-deep .el-card__body {
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-buttons {
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 统计卡片样式
|
|
|
+.statistics-row {
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistics-card {
|
|
|
+ min-height: 400px;
|
|
|
+
|
|
|
+ ::v-deep .el-card__header {
|
|
|
+ padding: 18px 20px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 8px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-actions {
|
|
|
+ float: right;
|
|
|
+ margin-top: -6px;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ padding: 0;
|
|
|
+ margin-left: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 数据内容区域
|
|
|
+.loading-content {
|
|
|
+ padding: 40px 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.data-content {
|
|
|
+ padding: 0px 20px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.data-header {
|
|
|
+ margin-bottom: 30px;
|
|
|
+
|
|
|
+ .data-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+
|
|
|
+ .title-text {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #606266;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-subtitle {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.data-display {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 20px;
|
|
|
+ margin: 40px 0;
|
|
|
+ padding: 40px;
|
|
|
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+
|
|
|
+ .data-value {
|
|
|
+ font-size: 64px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #409eff;
|
|
|
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-unit {
|
|
|
+ .el-tag {
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.data-footer {
|
|
|
+ margin-top: 40px;
|
|
|
+ padding-top: 20px;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+
|
|
|
+ .data-meta {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 30px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+
|
|
|
+ .meta-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+
|
|
|
+ i {
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 空状态
|
|
|
+.empty-content {
|
|
|
+ padding: 80px 20px;
|
|
|
+}
|
|
|
+
|
|
|
+// 字段信息对话框样式
|
|
|
+.field-info {
|
|
|
+ .info-item {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding-bottom: 16px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ padding-bottom: 0;
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-label {
|
|
|
+ width: 100px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-value {
|
|
|
+ flex: 1;
|
|
|
+ color: #606266;
|
|
|
+ word-break: break-word;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式设计
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .search-card ::v-deep .el-card__body {
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-display {
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 30px 20px;
|
|
|
+
|
|
|
+ .data-value {
|
|
|
+ font-size: 48px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-footer .data-meta {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-buttons {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ margin-left: 0 !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|